@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.
- package/.eslintrc.json +6 -4
- package/AGENTS.md +58 -0
- package/CHANGELOG.md +25 -0
- package/README.md +11 -6
- package/dist/bin/droid.js +384 -170
- package/dist/commands/config.d.ts +15 -1
- package/dist/commands/config.d.ts.map +1 -1
- package/dist/commands/exec.d.ts +10 -0
- package/dist/commands/exec.d.ts.map +1 -0
- package/dist/commands/tui.d.ts.map +1 -1
- package/dist/index.js +171 -33
- package/dist/lib/migrations.d.ts.map +1 -1
- package/dist/lib/skills.d.ts.map +1 -1
- package/dist/tools/codex/TOOL.yaml +1 -1
- package/dist/tools/codex/skills/droid-codex/SKILL.md +81 -65
- package/dist/tools/codex/skills/droid-codex/references/creating.md +13 -51
- package/dist/tools/codex/skills/droid-codex/references/decisions.md +15 -19
- package/dist/tools/codex/skills/droid-codex/references/topics.md +14 -12
- package/dist/tools/codex/skills/droid-codex/scripts/git-finish-write.d.ts +31 -0
- package/dist/tools/codex/skills/droid-codex/scripts/git-finish-write.d.ts.map +1 -0
- package/dist/tools/codex/skills/droid-codex/scripts/git-finish-write.ts +236 -0
- package/dist/tools/codex/skills/droid-codex/scripts/git-preamble.d.ts +20 -0
- package/dist/tools/codex/skills/droid-codex/scripts/git-preamble.d.ts.map +1 -0
- package/dist/tools/codex/skills/droid-codex/scripts/git-preamble.ts +156 -0
- package/dist/tools/codex/skills/droid-codex/scripts/git-scripts.test.ts +364 -0
- package/dist/tools/codex/skills/droid-codex/scripts/git-start-write.d.ts +23 -0
- package/dist/tools/codex/skills/droid-codex/scripts/git-start-write.d.ts.map +1 -0
- package/dist/tools/codex/skills/droid-codex/scripts/git-start-write.ts +172 -0
- package/package.json +1 -1
- package/src/bin/droid.ts +9 -0
- package/src/commands/config.ts +38 -4
- package/src/commands/exec.ts +96 -0
- package/src/commands/install.ts +1 -1
- package/src/commands/setup.ts +6 -6
- package/src/commands/tui.tsx +254 -175
- package/src/lib/migrations.ts +103 -10
- package/src/lib/quotes.ts +6 -6
- package/src/lib/skills.ts +168 -45
- package/src/tools/codex/TOOL.yaml +1 -1
- package/src/tools/codex/skills/droid-codex/SKILL.md +81 -65
- package/src/tools/codex/skills/droid-codex/references/creating.md +13 -51
- package/src/tools/codex/skills/droid-codex/references/decisions.md +15 -19
- package/src/tools/codex/skills/droid-codex/references/topics.md +14 -12
- package/src/tools/codex/skills/droid-codex/scripts/git-finish-write.ts +236 -0
- package/src/tools/codex/skills/droid-codex/scripts/git-preamble.ts +156 -0
- package/src/tools/codex/skills/droid-codex/scripts/git-scripts.test.ts +364 -0
- package/src/tools/codex/skills/droid-codex/scripts/git-start-write.ts +172 -0
package/src/lib/migrations.ts
CHANGED
|
@@ -1,8 +1,22 @@
|
|
|
1
|
-
import {
|
|
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 {
|
|
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(
|
|
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(
|
|
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 =
|
|
155
|
-
|
|
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 {
|
|
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
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
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
|
-
|
|
14
|
-
|
|
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 {
|
|
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 {
|
|
7
|
-
|
|
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(
|
|
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 =
|
|
65
|
-
|
|
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 =
|
|
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(
|
|
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(
|
|
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<{
|
|
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): {
|
|
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 {
|
|
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 {
|
|
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): {
|
|
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 {
|
|
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(
|
|
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
|
-
|
|
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(
|
|
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
|
|
455
|
-
.filter(dirent => dirent.
|
|
456
|
-
.map(dirent => dirent.name);
|
|
457
|
-
for (const agentName of
|
|
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 =>
|
|
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(
|
|
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
|
|
518
|
-
.filter(dirent => dirent.
|
|
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
|
|
522
|
-
const
|
|
523
|
-
const result = installAgentFromPath(
|
|
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(
|
|
650
|
+
console.warn(
|
|
651
|
+
`Warning: Migration failed for ${toolName}: ${migrationResult.error}`,
|
|
652
|
+
);
|
|
552
653
|
}
|
|
553
654
|
|
|
554
|
-
return {
|
|
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): {
|
|
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(
|
|
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(
|
|
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 {
|
|
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(
|
|
676
|
-
|
|
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 {
|
|
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 {
|
|
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.
|
|
3
|
+
version: 0.1.2
|
|
4
4
|
status: beta
|
|
5
5
|
|
|
6
6
|
includes:
|