@tmddev/tmd 0.1.0 → 0.3.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/README.md +197 -5
- package/dist/cli.js +70 -10
- package/dist/commands/init.d.ts +2 -0
- package/dist/commands/init.js +60 -0
- package/dist/commands/pipeline.d.ts +26 -0
- package/dist/commands/pipeline.js +168 -0
- package/dist/commands/schemas.d.ts +4 -0
- package/dist/commands/schemas.js +57 -0
- package/dist/commands/skills.d.ts +1 -1
- package/dist/commands/skills.js +247 -162
- package/dist/commands/validate.d.ts +9 -0
- package/dist/commands/validate.js +179 -0
- package/dist/commands/view.d.ts +2 -0
- package/dist/commands/view.js +65 -0
- package/dist/tmd-skills +0 -0
- package/dist/types.d.ts +21 -1
- package/dist/utils/github.d.ts +18 -0
- package/dist/utils/github.js +165 -0
- package/dist/utils/paths.d.ts +1 -0
- package/dist/utils/paths.js +5 -1
- package/dist/utils/pipeline-config.d.ts +37 -0
- package/dist/utils/pipeline-config.js +117 -0
- package/dist/utils/pipeline.d.ts +81 -0
- package/dist/utils/pipeline.js +580 -0
- package/dist/utils/skills.d.ts +10 -2
- package/dist/utils/skills.js +152 -150
- package/dist/utils/skillssh.d.ts +1 -1
- package/dist/utils/skillssh.js +39 -27
- package/dist/utils/step-executor.d.ts +58 -0
- package/dist/utils/step-executor.js +374 -0
- package/dist/utils/templates.d.ts +1 -0
- package/dist/utils/templates.js +30 -0
- package/package.json +22 -21
- package/scripts/postinstall.js +11 -8
package/dist/utils/skills.js
CHANGED
|
@@ -4,129 +4,113 @@ import { getSkillsDir, getSkillDir } from './paths.js';
|
|
|
4
4
|
import { generateTaskId } from './task-id.js';
|
|
5
5
|
import yaml from 'js-yaml';
|
|
6
6
|
import chalk from 'chalk';
|
|
7
|
-
|
|
7
|
+
import { executeStep as executeStepWithExecutor } from './step-executor.js';
|
|
8
|
+
/**
|
|
9
|
+
* Shared logic to write a skill directory. Used by createSkill (tmd plan --skill).
|
|
10
|
+
*/
|
|
11
|
+
function writeSkillDir(skillName, description, tags, createdFrom) {
|
|
8
12
|
const skillsDir = getSkillsDir();
|
|
9
13
|
ensureDir(skillsDir);
|
|
10
|
-
// Generate skill name from description
|
|
11
|
-
const skillName = generateTaskId(description).replace(/^\d+-/, '');
|
|
12
14
|
const skillDir = getSkillDir(skillName);
|
|
13
|
-
if (existsSync(skillDir)) {
|
|
14
|
-
console.log(chalk.yellow(` Skill already exists: ${skillName}`));
|
|
15
|
-
return skillName;
|
|
16
|
-
}
|
|
17
15
|
mkdirSync(skillDir, { recursive: true });
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
As a [role], I want to [action], so as to [benefit]
|
|
16
|
+
const skillContent = `---
|
|
17
|
+
name: ${skillName}
|
|
18
|
+
description: ${description}
|
|
19
|
+
---
|
|
23
20
|
|
|
24
|
-
|
|
25
|
-
${description}
|
|
21
|
+
# ${description}
|
|
26
22
|
|
|
27
|
-
##
|
|
28
|
-
<!-- List prerequisites -->
|
|
23
|
+
## Usage
|
|
29
24
|
|
|
30
|
-
|
|
31
|
-
<!-- Define executable steps -->
|
|
32
|
-
1.
|
|
33
|
-
2.
|
|
34
|
-
3.
|
|
25
|
+
Define your skill steps in \`steps.yaml\` and configure parameters in \`config.yaml\`.
|
|
35
26
|
|
|
36
|
-
##
|
|
37
|
-
<!-- Define expected results -->
|
|
27
|
+
## Steps
|
|
38
28
|
|
|
39
|
-
|
|
40
|
-
<!-- Define how to validate success -->
|
|
29
|
+
See \`steps.yaml\` for executable steps configuration.
|
|
41
30
|
`;
|
|
42
|
-
writeFileSync(join(skillDir, '
|
|
43
|
-
// Create steps.yaml
|
|
31
|
+
writeFileSync(join(skillDir, 'SKILL.md'), skillContent);
|
|
44
32
|
const stepsContent = {
|
|
45
|
-
steps: [
|
|
46
|
-
{
|
|
47
|
-
type: 'example',
|
|
48
|
-
description: 'Example step',
|
|
49
|
-
// Add step configuration
|
|
50
|
-
}
|
|
51
|
-
]
|
|
33
|
+
steps: [{ type: 'example', description: 'Example step' }]
|
|
52
34
|
};
|
|
53
35
|
writeFileSync(join(skillDir, 'steps.yaml'), yaml.dump(stepsContent));
|
|
54
|
-
|
|
55
|
-
const configContent = {
|
|
56
|
-
// Configuration for skill execution
|
|
57
|
-
};
|
|
58
|
-
writeFileSync(join(skillDir, 'config.yaml'), yaml.dump(configContent));
|
|
59
|
-
// Create metadata.yaml
|
|
36
|
+
writeFileSync(join(skillDir, 'config.yaml'), yaml.dump({}));
|
|
60
37
|
const metadataContent = {
|
|
61
38
|
name: skillName,
|
|
62
39
|
version: '1.0.0',
|
|
63
|
-
description
|
|
64
|
-
tags
|
|
65
|
-
|
|
66
|
-
interface: {
|
|
67
|
-
input: [],
|
|
68
|
-
output: {}
|
|
69
|
-
}
|
|
40
|
+
description,
|
|
41
|
+
tags,
|
|
42
|
+
interface: { input: [], output: {} }
|
|
70
43
|
};
|
|
44
|
+
if (createdFrom != null && createdFrom !== '') {
|
|
45
|
+
metadataContent['createdFrom'] = createdFrom;
|
|
46
|
+
}
|
|
71
47
|
writeFileSync(join(skillDir, 'metadata.yaml'), yaml.dump(metadataContent));
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Create a skill from a plan (tmd plan --skill). Generates name from description.
|
|
51
|
+
* Options.tags is applied when provided; options.taskId is used as createdFrom when provided.
|
|
52
|
+
*/
|
|
53
|
+
export function createSkill(description, taskId, options) {
|
|
54
|
+
const skillsDir = getSkillsDir();
|
|
55
|
+
ensureDir(skillsDir);
|
|
56
|
+
const skillName = generateTaskId(description).replace(/^\d+-/, '');
|
|
57
|
+
const skillDir = getSkillDir(skillName);
|
|
58
|
+
if (existsSync(skillDir)) {
|
|
59
|
+
console.log(chalk.yellow(` Skill already exists: ${skillName}`));
|
|
60
|
+
return skillName;
|
|
61
|
+
}
|
|
62
|
+
const tags = options?.tags ?? [];
|
|
63
|
+
writeSkillDir(skillName, description, tags, taskId);
|
|
72
64
|
return skillName;
|
|
73
65
|
}
|
|
74
|
-
function executeStep(step, stepIndex) {
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
break;
|
|
88
|
-
case 'script':
|
|
89
|
-
console.log(chalk.yellow(` Script execution: ${step.script ?? 'unknown'}`));
|
|
90
|
-
// TODO: Implement script execution
|
|
91
|
-
break;
|
|
92
|
-
case 'api':
|
|
93
|
-
console.log(chalk.yellow(` API call: ${step.endpoint ?? 'unknown'}`));
|
|
94
|
-
// TODO: Implement API calls
|
|
95
|
-
break;
|
|
96
|
-
case 'image':
|
|
97
|
-
console.log(chalk.yellow(` Image processing: ${step.operation ?? 'process'}`));
|
|
98
|
-
// TODO: Implement image processing
|
|
99
|
-
break;
|
|
100
|
-
default:
|
|
101
|
-
console.log(chalk.yellow(` Unknown step type: ${step.type ?? 'undefined'}`));
|
|
66
|
+
async function executeStep(step, stepIndex, taskId, workingDir) {
|
|
67
|
+
const startTime = Date.now();
|
|
68
|
+
try {
|
|
69
|
+
const context = {
|
|
70
|
+
taskId,
|
|
71
|
+
workingDir,
|
|
72
|
+
dryRun: false
|
|
73
|
+
};
|
|
74
|
+
const result = await executeStepWithExecutor(step, context);
|
|
75
|
+
if (result.success) {
|
|
76
|
+
console.log(chalk.green(` ✓ Step ${String(stepIndex + 1)}: ${step.description ?? step.type}`));
|
|
77
|
+
if (result.output) {
|
|
78
|
+
console.log(chalk.gray(` ${result.output}`));
|
|
102
79
|
}
|
|
103
|
-
const duration = Date.now() - startTime;
|
|
104
|
-
resolve({
|
|
105
|
-
stepIndex,
|
|
106
|
-
success: true,
|
|
107
|
-
duration
|
|
108
|
-
});
|
|
109
80
|
}
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
success: false,
|
|
116
|
-
error: errorMessage,
|
|
117
|
-
duration
|
|
118
|
-
});
|
|
81
|
+
else {
|
|
82
|
+
console.log(chalk.red(` ✗ Step ${String(stepIndex + 1)}: ${step.description ?? step.type}`));
|
|
83
|
+
if (result.error) {
|
|
84
|
+
console.log(chalk.red(` ${result.error}`));
|
|
85
|
+
}
|
|
119
86
|
}
|
|
120
|
-
|
|
87
|
+
return {
|
|
88
|
+
stepIndex,
|
|
89
|
+
success: result.success,
|
|
90
|
+
...(result.error != null ? { error: result.error } : {}),
|
|
91
|
+
duration: result.duration
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
catch (error) {
|
|
95
|
+
const duration = Date.now() - startTime;
|
|
96
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
97
|
+
console.log(chalk.red(` ✗ Step ${String(stepIndex + 1)}: ${errorMessage}`));
|
|
98
|
+
return {
|
|
99
|
+
stepIndex,
|
|
100
|
+
success: false,
|
|
101
|
+
error: errorMessage,
|
|
102
|
+
duration
|
|
103
|
+
};
|
|
104
|
+
}
|
|
121
105
|
}
|
|
122
|
-
async function executeStepsConcurrently(steps, maxConcurrency
|
|
106
|
+
async function executeStepsConcurrently(steps, maxConcurrency, taskId, workingDir) {
|
|
123
107
|
const results = [];
|
|
124
108
|
const executing = [];
|
|
125
109
|
for (let i = 0; i < steps.length; i++) {
|
|
126
110
|
const step = steps[i];
|
|
127
111
|
if (!step)
|
|
128
112
|
continue;
|
|
129
|
-
const stepDeps = step
|
|
113
|
+
const stepDeps = Array.isArray(step['depends']) ? step['depends'] : [];
|
|
130
114
|
// Check if dependencies are satisfied (simplified - assumes sequential if dependencies exist)
|
|
131
115
|
const hasDependencies = Array.isArray(stepDeps) && stepDeps.length > 0;
|
|
132
116
|
if (hasDependencies) {
|
|
@@ -136,60 +120,60 @@ async function executeStepsConcurrently(steps, maxConcurrency = Infinity) {
|
|
|
136
120
|
await Promise.all(executing);
|
|
137
121
|
executing.length = 0;
|
|
138
122
|
}
|
|
139
|
-
const promise = executeStep(step, i);
|
|
123
|
+
const promise = executeStep(step, i, taskId, workingDir);
|
|
140
124
|
executing.push(promise);
|
|
141
125
|
if (executing.length >= maxConcurrency) {
|
|
142
126
|
const completed = await Promise.race(executing);
|
|
143
127
|
results.push(completed);
|
|
144
|
-
//
|
|
145
|
-
|
|
146
|
-
// The promise is already resolved by Promise.race, so we can safely remove it
|
|
147
|
-
const removedPromise = executing.shift();
|
|
148
|
-
// Explicitly mark as handled to satisfy ESLint
|
|
149
|
-
void removedPromise;
|
|
128
|
+
// eslint-disable-next-line @typescript-eslint/no-floating-promises -- pool mutation; remaining promises are handled by Promise.allSettled below
|
|
129
|
+
executing.splice(executing.indexOf(promise), 1);
|
|
150
130
|
}
|
|
151
131
|
}
|
|
152
132
|
// Wait for remaining steps
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
}
|
|
166
|
-
else if (typeof reason === 'string') {
|
|
167
|
-
errorMessage = reason;
|
|
168
|
-
}
|
|
169
|
-
else if (reason === null || reason === undefined) {
|
|
170
|
-
errorMessage = 'Unknown error';
|
|
171
|
-
}
|
|
172
|
-
else {
|
|
173
|
-
// For other types, use JSON.stringify if possible, otherwise fallback
|
|
174
|
-
try {
|
|
175
|
-
errorMessage = JSON.stringify(reason);
|
|
176
|
-
}
|
|
177
|
-
catch {
|
|
178
|
-
errorMessage = 'Unknown error';
|
|
179
|
-
}
|
|
180
|
-
}
|
|
181
|
-
results.push({
|
|
182
|
-
stepIndex: -1,
|
|
183
|
-
success: false,
|
|
184
|
-
error: errorMessage,
|
|
185
|
-
duration: 0
|
|
186
|
-
});
|
|
187
|
-
}
|
|
133
|
+
const remainingResults = await Promise.allSettled(executing);
|
|
134
|
+
for (const result of remainingResults) {
|
|
135
|
+
if (result.status === 'fulfilled') {
|
|
136
|
+
results.push(result.value);
|
|
137
|
+
}
|
|
138
|
+
else {
|
|
139
|
+
results.push({
|
|
140
|
+
stepIndex: -1,
|
|
141
|
+
success: false,
|
|
142
|
+
error: result.reason instanceof Error ? result.reason.message : String(result.reason),
|
|
143
|
+
duration: 0
|
|
144
|
+
});
|
|
188
145
|
}
|
|
189
146
|
}
|
|
190
147
|
return results.sort((a, b) => a.stepIndex - b.stepIndex);
|
|
191
148
|
}
|
|
192
|
-
|
|
149
|
+
function replaceVariables(obj, params) {
|
|
150
|
+
if (typeof obj === 'string') {
|
|
151
|
+
// Replace ${variable} with param value
|
|
152
|
+
const result = obj.replace(/\$\{(\w+)\}/g, (match, key) => {
|
|
153
|
+
return params[key] ?? match;
|
|
154
|
+
});
|
|
155
|
+
// Try to convert to number if the original was a number-like string
|
|
156
|
+
if (/^\d+$/.test(result)) {
|
|
157
|
+
const num = Number(result);
|
|
158
|
+
if (!isNaN(num)) {
|
|
159
|
+
return num;
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
return result;
|
|
163
|
+
}
|
|
164
|
+
else if (Array.isArray(obj)) {
|
|
165
|
+
return obj.map(item => replaceVariables(item, params));
|
|
166
|
+
}
|
|
167
|
+
else if (obj && typeof obj === 'object') {
|
|
168
|
+
const result = {};
|
|
169
|
+
for (const [key, value] of Object.entries(obj)) {
|
|
170
|
+
result[key] = replaceVariables(value, params);
|
|
171
|
+
}
|
|
172
|
+
return result;
|
|
173
|
+
}
|
|
174
|
+
return obj;
|
|
175
|
+
}
|
|
176
|
+
export async function executeSkill(skillName, taskId, parallel = false, maxConcurrency = Infinity, params = {}) {
|
|
193
177
|
const skillDir = getSkillDir(skillName);
|
|
194
178
|
if (!existsSync(skillDir)) {
|
|
195
179
|
console.error(chalk.red(`✗ Skill not found: ${skillName}`));
|
|
@@ -201,17 +185,39 @@ export async function executeSkill(skillName, _taskId, parallel = false, maxConc
|
|
|
201
185
|
return;
|
|
202
186
|
}
|
|
203
187
|
try {
|
|
188
|
+
// Load config.yaml for default values
|
|
189
|
+
const configPath = join(skillDir, 'config.yaml');
|
|
190
|
+
let configDefaults = {};
|
|
191
|
+
if (existsSync(configPath)) {
|
|
192
|
+
try {
|
|
193
|
+
const config = yaml.load(readFileSync(configPath, 'utf-8'));
|
|
194
|
+
configDefaults = config.defaults ?? {};
|
|
195
|
+
}
|
|
196
|
+
catch {
|
|
197
|
+
// Ignore config parsing errors
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
// Merge config defaults with provided params (params take precedence)
|
|
201
|
+
const mergedParams = { ...configDefaults, ...params };
|
|
202
|
+
// Convert all values to strings for variable replacement
|
|
203
|
+
const stringParams = {};
|
|
204
|
+
for (const [key, value] of Object.entries(mergedParams)) {
|
|
205
|
+
stringParams[key] = typeof value === 'string' ? value : String(value);
|
|
206
|
+
}
|
|
204
207
|
const steps = yaml.load(readFileSync(stepsPath, 'utf-8'));
|
|
205
208
|
console.log(chalk.blue(` Executing skill: ${skillName}`));
|
|
206
|
-
console.log(chalk.gray(` Steps: ${String(steps.steps
|
|
209
|
+
console.log(chalk.gray(` Steps: ${String((steps.steps != null && Array.isArray(steps.steps) ? steps.steps : []).length)}`));
|
|
207
210
|
console.log(chalk.gray(` Mode: ${parallel ? 'concurrent' : 'sequential'}`));
|
|
208
|
-
|
|
211
|
+
const workingDir = skillDir;
|
|
212
|
+
// Replace variables in steps with merged parameters
|
|
213
|
+
const processedSteps = steps.steps != null ? replaceVariables(steps.steps, stringParams) : steps.steps;
|
|
214
|
+
if (processedSteps && Array.isArray(processedSteps)) {
|
|
209
215
|
if (parallel) {
|
|
210
216
|
// Execute steps concurrently
|
|
211
|
-
const results = await executeStepsConcurrently(
|
|
217
|
+
const results = await executeStepsConcurrently(processedSteps, maxConcurrency, taskId, workingDir);
|
|
212
218
|
const successCount = results.filter(r => r.success).length;
|
|
213
219
|
const failCount = results.length - successCount;
|
|
214
|
-
console.log(chalk.blue(` Execution Summary: ${
|
|
220
|
+
console.log(chalk.blue(` Execution Summary: ${successCount.toString()} successful, ${failCount.toString()} failed`));
|
|
215
221
|
if (failCount > 0) {
|
|
216
222
|
const failedSteps = results.filter(r => !r.success);
|
|
217
223
|
throw new Error(`Skill execution failed: ${String(failedSteps.length)} step(s) failed`);
|
|
@@ -219,15 +225,12 @@ export async function executeSkill(skillName, _taskId, parallel = false, maxConc
|
|
|
219
225
|
}
|
|
220
226
|
else {
|
|
221
227
|
// Execute steps sequentially (original behavior)
|
|
222
|
-
for (let i = 0; i <
|
|
223
|
-
const step =
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
console.log(chalk.gray(` Step ${String(i + 1)}/${String(steps.steps.length)}: ${step.type ?? 'unknown'}`));
|
|
227
|
-
const result = await executeStep(step, i);
|
|
228
|
+
for (let i = 0; i < processedSteps.length; i++) {
|
|
229
|
+
const step = processedSteps[i];
|
|
230
|
+
console.log(chalk.gray(` Step ${String(i + 1)}/${String(processedSteps.length)}: ${step.type}`));
|
|
231
|
+
const result = await executeStep(step, i, taskId, workingDir);
|
|
228
232
|
if (!result.success) {
|
|
229
|
-
|
|
230
|
-
throw new Error(`Step ${String(i + 1)} failed: ${errorMsg}`);
|
|
233
|
+
throw new Error(`Step ${String(i + 1)} failed: ${result.error ?? ''}`);
|
|
231
234
|
}
|
|
232
235
|
}
|
|
233
236
|
}
|
|
@@ -235,8 +238,7 @@ export async function executeSkill(skillName, _taskId, parallel = false, maxConc
|
|
|
235
238
|
console.log(chalk.green(` ✓ Skill execution completed: ${skillName}`));
|
|
236
239
|
}
|
|
237
240
|
catch (e) {
|
|
238
|
-
|
|
239
|
-
console.error(chalk.red(`✗ Error executing skill: ${errorMessage}`));
|
|
241
|
+
console.error(chalk.red(`✗ Error executing skill: ${String(e)}`));
|
|
240
242
|
throw e;
|
|
241
243
|
}
|
|
242
244
|
}
|
package/dist/utils/skillssh.d.ts
CHANGED
|
@@ -7,6 +7,6 @@ interface SkillsshResult {
|
|
|
7
7
|
/**
|
|
8
8
|
* Fetches and converts a skill from skills.sh to TMD format
|
|
9
9
|
*/
|
|
10
|
-
export declare function addSkillFromSkillssh(ownerRepo: string): Promise<SkillsshResult>;
|
|
10
|
+
export declare function addSkillFromSkillssh(ownerRepo: string, skillName?: string): Promise<SkillsshResult>;
|
|
11
11
|
export {};
|
|
12
12
|
//# sourceMappingURL=skillssh.d.ts.map
|
package/dist/utils/skillssh.js
CHANGED
|
@@ -5,7 +5,7 @@ import yaml from 'js-yaml';
|
|
|
5
5
|
/**
|
|
6
6
|
* Fetches and converts a skill from skills.sh to TMD format
|
|
7
7
|
*/
|
|
8
|
-
export async function addSkillFromSkillssh(ownerRepo) {
|
|
8
|
+
export async function addSkillFromSkillssh(ownerRepo, skillName) {
|
|
9
9
|
// Validate owner/repo format
|
|
10
10
|
const parts = ownerRepo.split('/');
|
|
11
11
|
if (parts.length !== 2 || !parts[0] || !parts[1]) {
|
|
@@ -15,21 +15,22 @@ export async function addSkillFromSkillssh(ownerRepo) {
|
|
|
15
15
|
};
|
|
16
16
|
}
|
|
17
17
|
const [owner, repo] = parts;
|
|
18
|
-
|
|
18
|
+
// Use provided skill name or derive from repo
|
|
19
|
+
const finalSkillName = skillName ?? repo;
|
|
19
20
|
// Check if skill already exists
|
|
20
|
-
const skillDir = getSkillDir(
|
|
21
|
+
const skillDir = getSkillDir(finalSkillName);
|
|
21
22
|
if (existsSync(skillDir)) {
|
|
22
23
|
return {
|
|
23
24
|
success: false,
|
|
24
|
-
error: `Skill already exists: ${
|
|
25
|
+
error: `Skill already exists: ${finalSkillName}`
|
|
25
26
|
};
|
|
26
27
|
}
|
|
27
28
|
// Fetch skill content from GitHub
|
|
28
|
-
const skillContent = await fetchSkillContent(owner, repo);
|
|
29
|
+
const skillContent = await fetchSkillContent(owner, repo, skillName);
|
|
29
30
|
if (!skillContent.success) {
|
|
30
31
|
return {
|
|
31
32
|
success: false,
|
|
32
|
-
error: skillContent.error ?? '
|
|
33
|
+
error: skillContent.error ?? 'Failed to fetch skill'
|
|
33
34
|
};
|
|
34
35
|
}
|
|
35
36
|
// Create skill directory
|
|
@@ -39,21 +40,28 @@ export async function addSkillFromSkillssh(ownerRepo) {
|
|
|
39
40
|
}
|
|
40
41
|
mkdirSync(skillDir, { recursive: true });
|
|
41
42
|
// Convert and save skill files
|
|
42
|
-
saveSkillFiles(skillDir,
|
|
43
|
+
saveSkillFiles(skillDir, finalSkillName, ownerRepo, skillContent.content ?? '', skillName);
|
|
43
44
|
return {
|
|
44
45
|
success: true,
|
|
45
|
-
skillName,
|
|
46
|
+
skillName: finalSkillName,
|
|
46
47
|
skillDir
|
|
47
48
|
};
|
|
48
49
|
}
|
|
49
|
-
async function fetchSkillContent(owner, repo) {
|
|
50
|
-
//
|
|
51
|
-
const possiblePaths = [
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
50
|
+
async function fetchSkillContent(owner, repo, skillName) {
|
|
51
|
+
// Build possible paths
|
|
52
|
+
const possiblePaths = [];
|
|
53
|
+
if (skillName) {
|
|
54
|
+
// If skill name is provided, try subdirectory paths (skills.sh format)
|
|
55
|
+
const branches = ['main', 'master'];
|
|
56
|
+
for (const branch of branches) {
|
|
57
|
+
possiblePaths.push(`https://raw.githubusercontent.com/${owner}/${repo}/${branch}/skills/${skillName}/SKILL.md`, `https://raw.githubusercontent.com/${owner}/${repo}/${branch}/skills/${skillName}/skill.md`, `https://raw.githubusercontent.com/${owner}/${repo}/${branch}/skills/${skillName}/README.md`);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
// Also try root-level paths
|
|
61
|
+
const branches = ['main', 'master'];
|
|
62
|
+
for (const branch of branches) {
|
|
63
|
+
possiblePaths.push(`https://raw.githubusercontent.com/${owner}/${repo}/${branch}/SKILL.md`, `https://raw.githubusercontent.com/${owner}/${repo}/${branch}/skill.md`, `https://raw.githubusercontent.com/${owner}/${repo}/${branch}/README.md`);
|
|
64
|
+
}
|
|
57
65
|
for (const url of possiblePaths) {
|
|
58
66
|
try {
|
|
59
67
|
const response = await fetch(url);
|
|
@@ -66,30 +74,34 @@ async function fetchSkillContent(owner, repo) {
|
|
|
66
74
|
// Continue to next URL
|
|
67
75
|
}
|
|
68
76
|
}
|
|
77
|
+
const skillHint = skillName ? ` (skill: ${skillName})` : '';
|
|
69
78
|
return {
|
|
70
79
|
success: false,
|
|
71
|
-
error: `Could not fetch skill from ${owner}/${repo}. Check that the repository exists and is public.`
|
|
80
|
+
error: `Could not fetch skill from ${owner}/${repo}${skillHint}. Check that the repository exists and is public.`
|
|
72
81
|
};
|
|
73
82
|
}
|
|
74
|
-
function saveSkillFiles(skillDir, skillName, ownerRepo, content) {
|
|
83
|
+
function saveSkillFiles(skillDir, skillName, ownerRepo, content, _originalSkillName) {
|
|
75
84
|
// Parse frontmatter if present
|
|
76
85
|
const { frontmatter, body } = parseFrontmatter(content);
|
|
77
|
-
//
|
|
78
|
-
const
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
86
|
+
// Extract name and description from frontmatter or use defaults
|
|
87
|
+
const skillNameFromFrontmatter = typeof frontmatter['name'] === 'string' ? frontmatter['name'] : skillName;
|
|
88
|
+
const skillDescription = typeof frontmatter['description'] === 'string' ? frontmatter['description'] : `Skill imported from skills.sh: ${ownerRepo}`;
|
|
89
|
+
// Create SKILL.md (skills.sh format) with frontmatter
|
|
90
|
+
const skillMd = `---
|
|
91
|
+
name: ${skillNameFromFrontmatter}
|
|
92
|
+
description: ${skillDescription}
|
|
93
|
+
---
|
|
82
94
|
|
|
83
|
-
## Description
|
|
84
95
|
${body || 'No description available.'}
|
|
85
96
|
`;
|
|
86
|
-
writeFileSync(join(skillDir, '
|
|
97
|
+
writeFileSync(join(skillDir, 'SKILL.md'), skillMd);
|
|
87
98
|
// Create metadata.yaml with source attribution
|
|
99
|
+
// Use frontmatter values if available, otherwise use defaults
|
|
88
100
|
const version = typeof frontmatter['version'] === 'string' ? frontmatter['version'] : '1.0.0';
|
|
89
101
|
const description = typeof frontmatter['description'] === 'string' ? frontmatter['description'] : `Skill imported from skills.sh: ${ownerRepo}`;
|
|
90
102
|
const tags = Array.isArray(frontmatter['tags']) ? frontmatter['tags'] : [];
|
|
91
103
|
const metadata = {
|
|
92
|
-
name:
|
|
104
|
+
name: skillNameFromFrontmatter,
|
|
93
105
|
version,
|
|
94
106
|
description,
|
|
95
107
|
tags,
|
|
@@ -112,7 +124,7 @@ ${body || 'No description available.'}
|
|
|
112
124
|
steps: [
|
|
113
125
|
{
|
|
114
126
|
type: 'procedural',
|
|
115
|
-
description: 'This skill contains procedural knowledge. See
|
|
127
|
+
description: 'This skill contains procedural knowledge. See SKILL.md for details.'
|
|
116
128
|
}
|
|
117
129
|
]
|
|
118
130
|
};
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Step executors for automated task and skill execution
|
|
3
|
+
*/
|
|
4
|
+
export interface StepResult {
|
|
5
|
+
success: boolean;
|
|
6
|
+
output?: string;
|
|
7
|
+
error?: string;
|
|
8
|
+
exitCode?: number;
|
|
9
|
+
duration: number;
|
|
10
|
+
data?: Record<string, unknown>;
|
|
11
|
+
}
|
|
12
|
+
export interface ExecutionContext {
|
|
13
|
+
taskId: string;
|
|
14
|
+
workingDir: string;
|
|
15
|
+
env?: Record<string, string>;
|
|
16
|
+
dryRun?: boolean;
|
|
17
|
+
}
|
|
18
|
+
export interface Step {
|
|
19
|
+
type: string;
|
|
20
|
+
description?: string;
|
|
21
|
+
[key: string]: unknown;
|
|
22
|
+
}
|
|
23
|
+
export interface StepExecutor {
|
|
24
|
+
type: string;
|
|
25
|
+
execute(step: Step, context: ExecutionContext): Promise<StepResult>;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Bash step executor - executes shell commands
|
|
29
|
+
*/
|
|
30
|
+
export declare class BashStepExecutor implements StepExecutor {
|
|
31
|
+
type: string;
|
|
32
|
+
execute(step: Step, context: ExecutionContext): Promise<StepResult>;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* File step executor - performs file operations
|
|
36
|
+
*/
|
|
37
|
+
export declare class FileStepExecutor implements StepExecutor {
|
|
38
|
+
type: string;
|
|
39
|
+
execute(step: Step, context: ExecutionContext): Promise<StepResult>;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* API step executor - makes HTTP requests
|
|
43
|
+
*/
|
|
44
|
+
export declare class ApiStepExecutor implements StepExecutor {
|
|
45
|
+
type: string;
|
|
46
|
+
execute(step: Step, context: ExecutionContext): Promise<StepResult>;
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Script step executor - executes Node.js scripts
|
|
50
|
+
*/
|
|
51
|
+
export declare class ScriptStepExecutor implements StepExecutor {
|
|
52
|
+
type: string;
|
|
53
|
+
execute(step: Step, context: ExecutionContext): Promise<StepResult>;
|
|
54
|
+
}
|
|
55
|
+
export declare function getStepExecutor(type: string): Promise<StepExecutor | undefined>;
|
|
56
|
+
export declare function registerStepExecutor(executor: StepExecutor): void;
|
|
57
|
+
export declare function executeStep(step: Step, context: ExecutionContext): Promise<StepResult>;
|
|
58
|
+
//# sourceMappingURL=step-executor.d.ts.map
|