@tmddev/tmd 0.3.0 → 0.3.2
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 +56 -215
- package/dist/cli.js +292 -6
- package/dist/commands/config.d.ts +11 -0
- package/dist/commands/config.js +199 -0
- package/dist/commands/feishu.d.ts +13 -0
- package/dist/commands/feishu.js +388 -0
- package/dist/commands/knowledge.d.ts +8 -0
- package/dist/commands/knowledge.js +117 -0
- package/dist/commands/plan.d.ts +1 -1
- package/dist/commands/plan.js +140 -19
- package/dist/commands/server.d.ts +7 -0
- package/dist/commands/server.js +38 -0
- package/dist/commands/skills.js +116 -30
- package/dist/commands/spec.d.ts +19 -0
- package/dist/commands/spec.js +73 -0
- package/dist/commands/ui.d.ts +2 -0
- package/dist/commands/ui.js +33 -0
- package/dist/commands/validate.js +31 -0
- package/dist/tmd-config +0 -0
- package/dist/tmd-feishu +0 -0
- package/dist/tmd-knowledge +0 -0
- package/dist/tmd-plan-gen +0 -0
- package/dist/tmd-server +0 -0
- package/dist/tmd-skills +0 -0
- package/dist/tmd-spec +0 -0
- package/dist/types.d.ts +8 -2
- package/dist/utils/execution-plan.d.ts +56 -0
- package/dist/utils/execution-plan.js +109 -0
- package/dist/utils/llm-plan.d.ts +38 -0
- package/dist/utils/llm-plan.js +159 -0
- package/dist/utils/openspec.js +2 -2
- package/dist/utils/paths.d.ts +5 -0
- package/dist/utils/paths.js +11 -0
- package/dist/utils/skills.js +1 -2
- package/dist/utils/task-status.d.ts +1 -0
- package/dist/utils/task-status.js +11 -0
- package/package.json +3 -4
- package/dist/utils/skillssh.d.ts +0 -12
- package/dist/utils/skillssh.js +0 -147
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import { existsSync, readdirSync, readFileSync } from 'fs';
|
|
2
|
+
import { join, resolve, dirname } from 'path';
|
|
3
|
+
import chalk from 'chalk';
|
|
4
|
+
import { spawnSync } from 'child_process';
|
|
5
|
+
import { fileURLToPath } from 'url';
|
|
6
|
+
const _dirname = dirname(fileURLToPath(import.meta.url));
|
|
7
|
+
const TMD_KNOWLEDGE_BIN = join(_dirname, '..', process.platform === 'win32' ? 'tmd-knowledge.exe' : 'tmd-knowledge');
|
|
8
|
+
function runTmdKnowledge(args) {
|
|
9
|
+
if (!existsSync(TMD_KNOWLEDGE_BIN)) {
|
|
10
|
+
console.error(chalk.red('✗ tmd-knowledge binary not found. Run pnpm build.'));
|
|
11
|
+
process.exit(1);
|
|
12
|
+
}
|
|
13
|
+
const r = spawnSync(TMD_KNOWLEDGE_BIN, args, {
|
|
14
|
+
encoding: 'utf8',
|
|
15
|
+
stdio: ['ignore', 'pipe', 'inherit'],
|
|
16
|
+
});
|
|
17
|
+
if (r.status !== 0) {
|
|
18
|
+
process.exit(r.status ?? 1);
|
|
19
|
+
}
|
|
20
|
+
return r.stdout;
|
|
21
|
+
}
|
|
22
|
+
function estimateTokens(content) {
|
|
23
|
+
// Rough estimation: Chinese ~1.5 tokens per character, English ~0.75 tokens per word
|
|
24
|
+
// For mixed content, use a conservative estimate: ~2 bytes per token for UTF-8
|
|
25
|
+
const bytes = Buffer.byteLength(content, 'utf8');
|
|
26
|
+
return Math.ceil(bytes / 2);
|
|
27
|
+
}
|
|
28
|
+
function readTextFiles(dir) {
|
|
29
|
+
const files = new Map();
|
|
30
|
+
const entries = readdirSync(dir, { withFileTypes: true });
|
|
31
|
+
for (const entry of entries) {
|
|
32
|
+
if (entry.isFile()) {
|
|
33
|
+
const ext = entry.name.split('.').pop()?.toLowerCase();
|
|
34
|
+
if (ext === 'txt' || ext === 'md') {
|
|
35
|
+
const filePath = join(dir, entry.name);
|
|
36
|
+
try {
|
|
37
|
+
const content = readFileSync(filePath, 'utf8');
|
|
38
|
+
files.set(entry.name, content);
|
|
39
|
+
}
|
|
40
|
+
catch (error) {
|
|
41
|
+
console.warn(chalk.yellow(`Warning: Could not read ${entry.name}: ${error}`));
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
return files;
|
|
47
|
+
}
|
|
48
|
+
export async function knowledgeCommand(action, options) {
|
|
49
|
+
if (action === 'create') {
|
|
50
|
+
const { knowledgeId, dir, agentId = 'summarizer' } = options;
|
|
51
|
+
if (!knowledgeId) {
|
|
52
|
+
console.error(chalk.red('✗ --knowledge-id is required'));
|
|
53
|
+
process.exit(1);
|
|
54
|
+
}
|
|
55
|
+
if (!dir) {
|
|
56
|
+
console.error(chalk.red('✗ --dir is required'));
|
|
57
|
+
process.exit(1);
|
|
58
|
+
}
|
|
59
|
+
const sourceDir = resolve(process.cwd(), dir);
|
|
60
|
+
if (!existsSync(sourceDir)) {
|
|
61
|
+
console.error(chalk.red(`✗ Directory does not exist: ${sourceDir}`));
|
|
62
|
+
process.exit(1);
|
|
63
|
+
}
|
|
64
|
+
console.log(chalk.blue(`\nCreating knowledge entry: ${knowledgeId}`));
|
|
65
|
+
console.log(chalk.gray(`Source directory: ${sourceDir}`));
|
|
66
|
+
console.log(chalk.gray(`Using agent: ${agentId}`));
|
|
67
|
+
// Read text files
|
|
68
|
+
const files = readTextFiles(sourceDir);
|
|
69
|
+
if (files.size === 0) {
|
|
70
|
+
console.warn(chalk.yellow('⚠ No .txt or .md files found in directory'));
|
|
71
|
+
}
|
|
72
|
+
console.log(chalk.blue(`\nFound ${files.size} file(s)`));
|
|
73
|
+
// Estimate tokens and check for large files
|
|
74
|
+
let totalTokens = 0;
|
|
75
|
+
const largeFiles = [];
|
|
76
|
+
for (const [name, content] of files.entries()) {
|
|
77
|
+
const tokens = estimateTokens(content);
|
|
78
|
+
totalTokens += tokens;
|
|
79
|
+
if (tokens > 100000) {
|
|
80
|
+
largeFiles.push({ name, tokens });
|
|
81
|
+
}
|
|
82
|
+
console.log(chalk.gray(` ${name}: ~${tokens.toLocaleString()} tokens`));
|
|
83
|
+
}
|
|
84
|
+
if (largeFiles.length > 0) {
|
|
85
|
+
console.log(chalk.yellow(`\n⚠ Warning: ${largeFiles.length} file(s) exceed 100K tokens and will be chunked:`));
|
|
86
|
+
for (const file of largeFiles) {
|
|
87
|
+
console.log(chalk.yellow(` ${file.name}: ~${file.tokens.toLocaleString()} tokens`));
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
console.log(chalk.blue('\nCreating knowledge entry with LLM summarization...'));
|
|
91
|
+
// Call Rust binary to create knowledge entry with LLM summarization
|
|
92
|
+
const cwd = process.cwd();
|
|
93
|
+
// Find knowledge.db - try .tmd/knowledge.db first, then fallback to project root
|
|
94
|
+
const knowledgeDbPath = join(cwd, '.tmd', 'knowledge.db');
|
|
95
|
+
const args = [
|
|
96
|
+
'create-from-dir',
|
|
97
|
+
'--project-root', cwd,
|
|
98
|
+
'--knowledge-id', knowledgeId,
|
|
99
|
+
'--source-dir', sourceDir,
|
|
100
|
+
'--agent-id', agentId,
|
|
101
|
+
'--knowledge-db', knowledgeDbPath,
|
|
102
|
+
];
|
|
103
|
+
try {
|
|
104
|
+
runTmdKnowledge(args);
|
|
105
|
+
console.log(chalk.green(`✓ Knowledge entry created: ${knowledgeId}`));
|
|
106
|
+
}
|
|
107
|
+
catch (error) {
|
|
108
|
+
console.error(chalk.red(`✗ Failed to create knowledge entry: ${error}`));
|
|
109
|
+
process.exit(1);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
else {
|
|
113
|
+
console.error(chalk.red(`Unknown action: ${action}`));
|
|
114
|
+
process.exit(1);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
//# sourceMappingURL=knowledge.js.map
|
package/dist/commands/plan.d.ts
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
import type { PlanCommandOptions } from '../types.js';
|
|
2
|
-
export declare function planCommand(description: string, options: PlanCommandOptions): void
|
|
2
|
+
export declare function planCommand(description: string, options: PlanCommandOptions): Promise<void>;
|
|
3
3
|
//# sourceMappingURL=plan.d.ts.map
|
package/dist/commands/plan.js
CHANGED
|
@@ -1,34 +1,155 @@
|
|
|
1
1
|
import { ensureDir, getPlanDir } from '../utils/paths.js';
|
|
2
2
|
import { generateTaskId } from '../utils/task-id.js';
|
|
3
3
|
import { planTemplate, resourcesTemplate, renderTemplate } from '../utils/templates.js';
|
|
4
|
-
import { updateTaskStatus } from '../utils/task-status.js';
|
|
5
|
-
import { createOpenSpecChange } from '../utils/openspec.js';
|
|
4
|
+
import { updateTaskMetadata, updateTaskStatus } from '../utils/task-status.js';
|
|
6
5
|
import { createSkill } from '../utils/skills.js';
|
|
7
6
|
import { extractTasks } from '../utils/tasks.js';
|
|
8
7
|
import { validateTasks } from '../utils/task-validator.js';
|
|
8
|
+
import { isOpenSpecProject, generateLLMPlan, showGenerationProgress } from '../utils/llm-plan.js';
|
|
9
|
+
import { executionPlanFromGeneratedTasks, validateExecutionPlan } from '../utils/execution-plan.js';
|
|
9
10
|
import { writeFileSync, existsSync } from 'fs';
|
|
10
11
|
import { join } from 'path';
|
|
11
12
|
import chalk from 'chalk';
|
|
12
|
-
|
|
13
|
+
function injectTasksIntoPlan(planContent, tasks) {
|
|
14
|
+
const marker = '## OpenSpec Change';
|
|
15
|
+
const i = planContent.indexOf(marker);
|
|
16
|
+
if (i < 0)
|
|
17
|
+
return planContent;
|
|
18
|
+
const before = planContent.slice(0, i);
|
|
19
|
+
const after = planContent.slice(i);
|
|
20
|
+
const tasksBlock = tasks.length
|
|
21
|
+
? tasks.map((t, idx) => `- [ ] ${String(idx + 1)}. ${t.title}`).join('\n') + '\n'
|
|
22
|
+
: '- [ ] 1.\n- [ ] 2.\n- [ ] 3.\n';
|
|
23
|
+
// Replace everything after "## Tasks" heading up to the next section with a fresh list.
|
|
24
|
+
const tasksHeading = /##\s*Tasks[\s\S]*$/i;
|
|
25
|
+
if (!tasksHeading.test(before))
|
|
26
|
+
return planContent;
|
|
27
|
+
const replaced = before.replace(tasksHeading, (m) => {
|
|
28
|
+
// Keep the original Tasks heading and guidance comments (everything up to the first checklist item),
|
|
29
|
+
// then replace checklist with generated tasks.
|
|
30
|
+
const lines = m.split('\n');
|
|
31
|
+
const out = [];
|
|
32
|
+
let inChecklist = false;
|
|
33
|
+
for (const line of lines) {
|
|
34
|
+
if (/^\s*-\s+\[\s?[x ]?\s?]\s+/.test(line)) {
|
|
35
|
+
inChecklist = true;
|
|
36
|
+
continue;
|
|
37
|
+
}
|
|
38
|
+
if (!inChecklist)
|
|
39
|
+
out.push(line);
|
|
40
|
+
}
|
|
41
|
+
// Ensure there's exactly one blank line before the checklist.
|
|
42
|
+
if (out.length > 0 && out[out.length - 1]?.trim() !== '')
|
|
43
|
+
out.push('');
|
|
44
|
+
out.push(tasksBlock.trimEnd());
|
|
45
|
+
out.push('');
|
|
46
|
+
return out.join('\n');
|
|
47
|
+
});
|
|
48
|
+
return replaced + after;
|
|
49
|
+
}
|
|
50
|
+
export async function planCommand(description, options) {
|
|
13
51
|
const taskId = generateTaskId(description);
|
|
14
52
|
const planDir = getPlanDir(taskId);
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
53
|
+
// Check if spec features are enabled for this project
|
|
54
|
+
const isOpenSpec = isOpenSpecProject();
|
|
55
|
+
if (isOpenSpec) {
|
|
56
|
+
// LLM-powered proposal generation
|
|
57
|
+
const agentId = options.agentId || 'planner';
|
|
58
|
+
console.log(chalk.blue(`🤖 Using agent '${agentId}' to generate proposal...\n`));
|
|
59
|
+
const progress = showGenerationProgress();
|
|
60
|
+
progress.setStage('Analyzing requirements...');
|
|
61
|
+
const result = await generateLLMPlan({
|
|
62
|
+
description,
|
|
63
|
+
projectRoot: '.',
|
|
64
|
+
agentId,
|
|
65
|
+
onStage: (stage) => progress.setStage(stage),
|
|
66
|
+
});
|
|
67
|
+
progress.stop();
|
|
68
|
+
if (!result.success) {
|
|
69
|
+
console.log(chalk.red('\n✗ Failed to generate proposal:'));
|
|
70
|
+
console.log(chalk.red(` ${result.error}`));
|
|
71
|
+
console.log(chalk.yellow('\n💡 Falling back to simple plan generation...\n'));
|
|
72
|
+
// Fall back to simple plan generation
|
|
73
|
+
ensureDir(planDir);
|
|
74
|
+
const planContent = renderTemplate(planTemplate, { description });
|
|
75
|
+
writeFileSync(join(planDir, 'plan.md'), planContent);
|
|
76
|
+
const resourcesContent = renderTemplate(resourcesTemplate, { description });
|
|
77
|
+
writeFileSync(join(planDir, 'resources.md'), resourcesContent);
|
|
78
|
+
updateTaskStatus(taskId, 'planning');
|
|
79
|
+
console.log(chalk.green(`✓ Created simple plan for task: ${taskId}`));
|
|
80
|
+
console.log(chalk.blue(` Directory: ${planDir}`));
|
|
31
81
|
}
|
|
82
|
+
else {
|
|
83
|
+
// Success! LLM generated the proposal
|
|
84
|
+
console.log(chalk.green('\n✓ Proposal generated successfully!'));
|
|
85
|
+
if (result.changeId) {
|
|
86
|
+
console.log(chalk.blue(` Change ID: ${result.changeId}`));
|
|
87
|
+
console.log(chalk.blue(` Location: .tmd/changes/${result.changeId}/`));
|
|
88
|
+
}
|
|
89
|
+
if (result.tasksCount) {
|
|
90
|
+
console.log(chalk.blue(` Tasks: ${result.tasksCount}`));
|
|
91
|
+
}
|
|
92
|
+
if (result.specsCount) {
|
|
93
|
+
console.log(chalk.blue(` Spec deltas: ${result.specsCount}`));
|
|
94
|
+
}
|
|
95
|
+
if (result.hasDesign) {
|
|
96
|
+
console.log(chalk.blue(` Includes: design.md`));
|
|
97
|
+
}
|
|
98
|
+
// Also create the .tmd/plan directory for task tracking
|
|
99
|
+
ensureDir(planDir);
|
|
100
|
+
const planContentBase = renderTemplate(planTemplate, { description });
|
|
101
|
+
const planContent = result.tasks ? injectTasksIntoPlan(planContentBase, result.tasks) : planContentBase;
|
|
102
|
+
writeFileSync(join(planDir, 'plan.md'), planContent);
|
|
103
|
+
const resourcesContent = renderTemplate(resourcesTemplate, { description });
|
|
104
|
+
writeFileSync(join(planDir, 'resources.md'), resourcesContent);
|
|
105
|
+
// Create machine-readable execution plan artifact
|
|
106
|
+
if (result.tasks && result.tasks.length > 0) {
|
|
107
|
+
const execPlan = executionPlanFromGeneratedTasks({
|
|
108
|
+
taskId,
|
|
109
|
+
tasks: result.tasks,
|
|
110
|
+
defaultAgentId: options.assignAgent || 'executor',
|
|
111
|
+
generatedBy: 'tmd plan',
|
|
112
|
+
});
|
|
113
|
+
const issues = validateExecutionPlan(execPlan);
|
|
114
|
+
const hasError = issues.some((x) => x.level === 'error');
|
|
115
|
+
if (issues.length > 0) {
|
|
116
|
+
console.log(chalk.blue('\nExecution Plan Validation:'));
|
|
117
|
+
for (const it of issues) {
|
|
118
|
+
const p = it.level === 'error' ? chalk.red('error') : chalk.yellow('warning');
|
|
119
|
+
console.log(` ${p}: ${it.message}`);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
if (hasError && options.strictDeps) {
|
|
123
|
+
console.error(chalk.red('\n✗ Execution plan dependency validation failed (--strict-deps)'));
|
|
124
|
+
process.exit(1);
|
|
125
|
+
}
|
|
126
|
+
writeFileSync(join(planDir, 'execution-plan.json'), JSON.stringify(execPlan, null, 2));
|
|
127
|
+
}
|
|
128
|
+
else {
|
|
129
|
+
console.log(chalk.yellow('\n⚠️ No generated tasks were returned; execution-plan.json was not created.'));
|
|
130
|
+
}
|
|
131
|
+
updateTaskStatus(taskId, 'planning');
|
|
132
|
+
if (result.changeId) {
|
|
133
|
+
updateTaskMetadata(taskId, { openspecChange: result.changeId });
|
|
134
|
+
}
|
|
135
|
+
console.log(chalk.green(`\n✓ Created task tracking: ${taskId}`));
|
|
136
|
+
console.log(chalk.blue(` Directory: ${planDir}`));
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
else {
|
|
140
|
+
// Simple plan generation (spec features not enabled)
|
|
141
|
+
ensureDir(planDir);
|
|
142
|
+
// Create plan.md
|
|
143
|
+
const planContent = renderTemplate(planTemplate, { description });
|
|
144
|
+
writeFileSync(join(planDir, 'plan.md'), planContent);
|
|
145
|
+
// Create resources.md
|
|
146
|
+
const resourcesContent = renderTemplate(resourcesTemplate, { description });
|
|
147
|
+
writeFileSync(join(planDir, 'resources.md'), resourcesContent);
|
|
148
|
+
// Initialize task status
|
|
149
|
+
updateTaskStatus(taskId, 'planning');
|
|
150
|
+
console.log(chalk.green(`✓ Created plan for task: ${taskId}`));
|
|
151
|
+
console.log(chalk.blue(` Directory: ${planDir}`));
|
|
152
|
+
console.log(chalk.dim(' (Spec features not enabled - using simple templates)'));
|
|
32
153
|
}
|
|
33
154
|
// Skill creation
|
|
34
155
|
if (options.skill) {
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { spawn } from 'child_process';
|
|
2
|
+
import { existsSync } from 'fs';
|
|
3
|
+
import { dirname, join } from 'path';
|
|
4
|
+
import { fileURLToPath } from 'url';
|
|
5
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
6
|
+
const __dirname = dirname(__filename);
|
|
7
|
+
const TMD_SERVER_BIN = join(__dirname, '..', process.platform === 'win32' ? 'tmd-server.exe' : 'tmd-server');
|
|
8
|
+
export function serverCommand(options) {
|
|
9
|
+
if (!existsSync(TMD_SERVER_BIN)) {
|
|
10
|
+
console.error('✗ tmd-server binary not found. Run pnpm run build.');
|
|
11
|
+
process.exit(1);
|
|
12
|
+
}
|
|
13
|
+
const host = options.host ?? '127.0.0.1';
|
|
14
|
+
const port = options.port ?? 13300;
|
|
15
|
+
const projectRoot = options.projectRoot ?? process.cwd();
|
|
16
|
+
const args = ['--host', host, '--port', String(port), '--project-root', projectRoot];
|
|
17
|
+
const child = spawn(TMD_SERVER_BIN, args, { stdio: 'inherit' });
|
|
18
|
+
const forwardSignal = (sig) => {
|
|
19
|
+
// Forward to child (best-effort), then exit with same signal semantics.
|
|
20
|
+
try {
|
|
21
|
+
child.kill(sig);
|
|
22
|
+
}
|
|
23
|
+
catch {
|
|
24
|
+
// ignore
|
|
25
|
+
}
|
|
26
|
+
};
|
|
27
|
+
process.on('SIGINT', () => forwardSignal('SIGINT'));
|
|
28
|
+
process.on('SIGTERM', () => forwardSignal('SIGTERM'));
|
|
29
|
+
child.on('close', (code, signal) => {
|
|
30
|
+
// If child exits due to signal, reflect it.
|
|
31
|
+
if (signal) {
|
|
32
|
+
// Node doesn't allow setting exit signal directly; exit non-zero.
|
|
33
|
+
process.exit(1);
|
|
34
|
+
}
|
|
35
|
+
process.exit(code ?? 1);
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
//# sourceMappingURL=server.js.map
|
package/dist/commands/skills.js
CHANGED
|
@@ -22,6 +22,15 @@ function runTmdSkills(args) {
|
|
|
22
22
|
}
|
|
23
23
|
return r.stdout;
|
|
24
24
|
}
|
|
25
|
+
function runTmdSkillsInherit(args) {
|
|
26
|
+
if (!existsSync(TMD_SKILLS_BIN)) {
|
|
27
|
+
console.error(chalk.red('✗ tmd-skills binary not found. Run pnpm build.'));
|
|
28
|
+
process.exit(1);
|
|
29
|
+
}
|
|
30
|
+
const r = spawnSync(TMD_SKILLS_BIN, args, { stdio: 'inherit' });
|
|
31
|
+
if (r.status !== 0)
|
|
32
|
+
process.exit(r.status ?? 1);
|
|
33
|
+
}
|
|
25
34
|
function runTmdSkillsRemove(skillName, all) {
|
|
26
35
|
if (!existsSync(TMD_SKILLS_BIN)) {
|
|
27
36
|
console.error(chalk.red('✗ tmd-skills binary not found. Run pnpm build.'));
|
|
@@ -50,21 +59,38 @@ export async function skillsCommand(action, name, options) {
|
|
|
50
59
|
const cwd = process.cwd();
|
|
51
60
|
const base = () => ['--project-root', cwd];
|
|
52
61
|
if (!action || action === 'list') {
|
|
62
|
+
// Default: show npx canonical store (project `./skills/` or `~/skills/` with --global).
|
|
63
|
+
const outNpx = runTmdSkills([
|
|
64
|
+
'list-npx',
|
|
65
|
+
...base(),
|
|
66
|
+
...(options?.global ? ['--global'] : []),
|
|
67
|
+
...tagArgs(options?.tag),
|
|
68
|
+
]);
|
|
69
|
+
const canonical = JSON.parse(outNpx || '[]');
|
|
70
|
+
if (canonical.length > 0) {
|
|
71
|
+
console.log(chalk.bold('\nInstalled Skills (canonical store):'));
|
|
72
|
+
console.log('─'.repeat(60));
|
|
73
|
+
for (const s of canonical) {
|
|
74
|
+
console.log(` ${chalk.cyan(s.name)}`);
|
|
75
|
+
if (s.description)
|
|
76
|
+
console.log(` ${chalk.gray(s.description)}`);
|
|
77
|
+
}
|
|
78
|
+
console.log('─'.repeat(60));
|
|
79
|
+
console.log(chalk.gray(`Total: ${String(canonical.length)} skill(s)`));
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
// Fallback: show TMD-managed skills (useful for skills created via tmd).
|
|
53
83
|
const out = runTmdSkills(['list', ...base(), ...tagArgs(options?.tag)]);
|
|
54
84
|
const skills = JSON.parse(out || '[]');
|
|
55
85
|
if (skills.length === 0) {
|
|
56
|
-
console.log(chalk.yellow('No skills found.
|
|
57
|
-
console.log(chalk.gray('
|
|
86
|
+
console.log(chalk.yellow('No skills found.'));
|
|
87
|
+
console.log(chalk.gray('Install with: tmd skills add <owner/repo>'));
|
|
58
88
|
return;
|
|
59
89
|
}
|
|
60
|
-
console.log(chalk.bold('\nAvailable Skills:'));
|
|
90
|
+
console.log(chalk.bold('\nAvailable Skills (tmd-managed):'));
|
|
61
91
|
console.log('─'.repeat(60));
|
|
62
92
|
for (const s of skills) {
|
|
63
|
-
|
|
64
|
-
if (s.source === 'skillssh')
|
|
65
|
-
sourceTag = chalk.magenta(' [skills.sh]');
|
|
66
|
-
else if (s.source === 'github')
|
|
67
|
-
sourceTag = chalk.blue(' [github]');
|
|
93
|
+
const sourceTag = s.source === 'github' ? chalk.blue(' [github]') : '';
|
|
68
94
|
const repoInfo = s.repo ? chalk.gray(` (${s.repo})`) : '';
|
|
69
95
|
console.log(` ${chalk.cyan(s.name)}${sourceTag}${repoInfo}`);
|
|
70
96
|
console.log(` ${chalk.gray(s.description || 'No description')}`);
|
|
@@ -101,13 +127,7 @@ export async function skillsCommand(action, name, options) {
|
|
|
101
127
|
}
|
|
102
128
|
console.log(chalk.bold('\nMetadata:'));
|
|
103
129
|
console.log(` Version: ${skill.version ?? 'N/A'}`);
|
|
104
|
-
if (skill.source === '
|
|
105
|
-
console.log(` Source: ${chalk.magenta('skills.sh')}`);
|
|
106
|
-
console.log(` Repository: ${skill.repo ?? 'N/A'}`);
|
|
107
|
-
if (skill.imported_at)
|
|
108
|
-
console.log(` Imported: ${skill.imported_at}`);
|
|
109
|
-
}
|
|
110
|
-
else if (skill.source === 'github') {
|
|
130
|
+
if (skill.source === 'github') {
|
|
111
131
|
console.log(` Source: ${chalk.blue('GitHub')}`);
|
|
112
132
|
console.log(` Repository: ${skill.repo ?? 'N/A'}`);
|
|
113
133
|
if (skill.branch)
|
|
@@ -154,6 +174,38 @@ export async function skillsCommand(action, name, options) {
|
|
|
154
174
|
}
|
|
155
175
|
void invokeSkill(name, options?.input, skillsDir);
|
|
156
176
|
}
|
|
177
|
+
else if (action === 'find') {
|
|
178
|
+
runTmdSkillsInherit(['npx-find', ...(name ? [name] : [])]);
|
|
179
|
+
return;
|
|
180
|
+
}
|
|
181
|
+
else if (action === 'check') {
|
|
182
|
+
runTmdSkillsInherit(['npx-check']);
|
|
183
|
+
return;
|
|
184
|
+
}
|
|
185
|
+
else if (action === 'generate-lock') {
|
|
186
|
+
runTmdSkillsInherit(['npx-generate-lock']);
|
|
187
|
+
return;
|
|
188
|
+
}
|
|
189
|
+
else if (action === 'init') {
|
|
190
|
+
runTmdSkillsInherit(['npx-init', ...(name ? [name] : [])]);
|
|
191
|
+
return;
|
|
192
|
+
}
|
|
193
|
+
else if (action === 'sync') {
|
|
194
|
+
// Sync npx canonical store into .agents/.tmd and upsert to .tmd/skills.db.
|
|
195
|
+
// - default source store: ./skills
|
|
196
|
+
// - with -g/--global: ~/skills
|
|
197
|
+
const out = runTmdSkills([
|
|
198
|
+
'sync-npx',
|
|
199
|
+
...base(),
|
|
200
|
+
...(name ? ['--source', name] : []),
|
|
201
|
+
...(options?.global ? ['--global'] : []),
|
|
202
|
+
...(options?.all ? ['--all'] : []),
|
|
203
|
+
...(options?.force ? ['--force'] : []),
|
|
204
|
+
]);
|
|
205
|
+
const skills = JSON.parse(out || '[]');
|
|
206
|
+
console.log(chalk.green(`✓ Synced ${String(skills.length)} skill(s) from npx canonical store`));
|
|
207
|
+
return;
|
|
208
|
+
}
|
|
157
209
|
else if (action === 'add') {
|
|
158
210
|
if (!name) {
|
|
159
211
|
console.error(chalk.red('✗ Repository required (format: owner/repo)'));
|
|
@@ -161,26 +213,48 @@ export async function skillsCommand(action, name, options) {
|
|
|
161
213
|
}
|
|
162
214
|
if (options?.github) {
|
|
163
215
|
console.log(chalk.blue(`\nAdding skill from GitHub: ${name}`));
|
|
164
|
-
if (options
|
|
216
|
+
if (options?.skill)
|
|
165
217
|
console.log(chalk.gray(` Skill: ${options.skill}`));
|
|
166
|
-
if (options
|
|
218
|
+
if (options?.branch)
|
|
167
219
|
console.log(chalk.gray(` Branch: ${options.branch}`));
|
|
168
220
|
console.log('─'.repeat(60));
|
|
169
|
-
const
|
|
170
|
-
|
|
221
|
+
const ghArgs = [
|
|
222
|
+
'import-github',
|
|
223
|
+
name,
|
|
224
|
+
...base(),
|
|
225
|
+
...(options?.skill ? ['--skill', options.skill] : []),
|
|
226
|
+
...(options?.branch ? ['--branch', options.branch] : []),
|
|
227
|
+
...(options?.force ? ['--force'] : []),
|
|
228
|
+
...(options?.all ? ['--all'] : []),
|
|
229
|
+
];
|
|
230
|
+
const out = runTmdSkills(ghArgs);
|
|
171
231
|
const skill = JSON.parse(out);
|
|
172
232
|
console.log(chalk.green(`✓ Skill added: ${skill.name}`));
|
|
173
233
|
console.log(chalk.gray(` Location: ${getSkillDir(skill.name)}`));
|
|
234
|
+
return;
|
|
174
235
|
}
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
236
|
+
// Default: use Rust `tmd-skills npx-add` (it invokes npx, syncs to .tmd/.agents, and upserts to .tmd/skills.db;
|
|
237
|
+
// if npx fails, it falls back to GitHub import inside the Rust binary).
|
|
238
|
+
const args = [
|
|
239
|
+
'npx-add',
|
|
240
|
+
name,
|
|
241
|
+
...base(),
|
|
242
|
+
...(options?.global ? ['--global'] : []),
|
|
243
|
+
...(options?.yes ? ['--yes'] : []),
|
|
244
|
+
...(options?.list ? ['--list'] : []),
|
|
245
|
+
...(options?.all ? ['--all'] : []),
|
|
246
|
+
...(options?.force ? ['--force'] : []),
|
|
247
|
+
...(options?.branch ? ['--branch', options.branch] : []),
|
|
248
|
+
...(options?.agent ? options.agent.flatMap((a) => ['--agent', a]) : []),
|
|
249
|
+
...(options?.skill ? ['--skill', options.skill] : []),
|
|
250
|
+
];
|
|
251
|
+
if (options?.list) {
|
|
252
|
+
runTmdSkillsInherit(args);
|
|
253
|
+
return;
|
|
183
254
|
}
|
|
255
|
+
const out = runTmdSkills(args);
|
|
256
|
+
const synced = JSON.parse(out || '[]');
|
|
257
|
+
console.log(chalk.green(`✓ Skill(s) installed and synced: ${String(synced.length)}`));
|
|
184
258
|
}
|
|
185
259
|
else if (action === 'create') {
|
|
186
260
|
if (!name) {
|
|
@@ -193,9 +267,21 @@ export async function skillsCommand(action, name, options) {
|
|
|
193
267
|
console.log(chalk.gray(` Location: ${getSkillDir(name)}`));
|
|
194
268
|
}
|
|
195
269
|
else if (action === 'update') {
|
|
270
|
+
// Two modes:
|
|
271
|
+
// - `tmd skills update` (no name): delegate to `npx skills update` and re-sync DB.
|
|
272
|
+
// - `tmd skills update <name> --description/--tags`: update TMD metadata via Rust.
|
|
196
273
|
if (!name) {
|
|
197
|
-
|
|
198
|
-
|
|
274
|
+
const out = runTmdSkills([
|
|
275
|
+
'npx-update',
|
|
276
|
+
...base(),
|
|
277
|
+
...(options?.global ? ['--global'] : []),
|
|
278
|
+
...(options?.yes ? ['--yes'] : []),
|
|
279
|
+
...(options?.all ? ['--all'] : []),
|
|
280
|
+
...(options?.agent ? options.agent.flatMap((a) => ['--agent', a]) : []),
|
|
281
|
+
]);
|
|
282
|
+
const synced = JSON.parse(out || '[]');
|
|
283
|
+
console.log(chalk.green(`✓ Updated and synced ${String(synced.length)} skill(s)`));
|
|
284
|
+
return;
|
|
199
285
|
}
|
|
200
286
|
const o = options ?? {};
|
|
201
287
|
const hasDesc = o.description != null;
|
|
@@ -231,7 +317,7 @@ export async function skillsCommand(action, name, options) {
|
|
|
231
317
|
}
|
|
232
318
|
else {
|
|
233
319
|
console.error(chalk.red(`✗ Unknown action: ${action}`));
|
|
234
|
-
console.log(chalk.gray('Available actions: list, search, show, invoke, add,
|
|
320
|
+
console.log(chalk.gray('Available actions: list, search, show, invoke, add, find, check, update, init, generate-lock, sync, create, remove'));
|
|
235
321
|
process.exit(1);
|
|
236
322
|
}
|
|
237
323
|
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
export interface SpecValidateOptions {
|
|
2
|
+
all?: boolean;
|
|
3
|
+
specs?: boolean;
|
|
4
|
+
changes?: boolean;
|
|
5
|
+
strict?: boolean;
|
|
6
|
+
json?: boolean;
|
|
7
|
+
noInteractive?: boolean;
|
|
8
|
+
}
|
|
9
|
+
export declare function specListCommand(opts?: {
|
|
10
|
+
json?: boolean;
|
|
11
|
+
}): void;
|
|
12
|
+
export declare function specShowCommand(specId: string, opts?: {
|
|
13
|
+
json?: boolean;
|
|
14
|
+
}): void;
|
|
15
|
+
export declare function specValidateCommand(item: string | undefined, opts?: SpecValidateOptions): void;
|
|
16
|
+
export declare function specArchiveCommand(changeId: string, opts?: {
|
|
17
|
+
yes?: boolean;
|
|
18
|
+
}): void;
|
|
19
|
+
//# sourceMappingURL=spec.d.ts.map
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { existsSync } from 'fs';
|
|
2
|
+
import { spawnSync } from 'child_process';
|
|
3
|
+
import { fileURLToPath } from 'url';
|
|
4
|
+
import { dirname, join } from 'path';
|
|
5
|
+
import chalk from 'chalk';
|
|
6
|
+
const _dirname = dirname(fileURLToPath(import.meta.url));
|
|
7
|
+
const TMD_SPEC_BIN = join(_dirname, '..', process.platform === 'win32' ? 'tmd-spec.exe' : 'tmd-spec');
|
|
8
|
+
function runTmdSpec(args) {
|
|
9
|
+
if (!existsSync(TMD_SPEC_BIN)) {
|
|
10
|
+
console.error(chalk.red('✗ tmd-spec binary not found. Run pnpm build.'));
|
|
11
|
+
process.exit(1);
|
|
12
|
+
}
|
|
13
|
+
const r = spawnSync(TMD_SPEC_BIN, args, { encoding: 'utf8', stdio: ['ignore', 'pipe', 'inherit'] });
|
|
14
|
+
if (r.status !== 0)
|
|
15
|
+
process.exit(r.status ?? 1);
|
|
16
|
+
return r.stdout;
|
|
17
|
+
}
|
|
18
|
+
export function specListCommand(opts = {}) {
|
|
19
|
+
const cwd = process.cwd();
|
|
20
|
+
const out = runTmdSpec(['list', '--project-root', cwd, ...(opts.json ? ['--json'] : [])]);
|
|
21
|
+
if (opts.json) {
|
|
22
|
+
console.log(out.trim() === '' ? '[]' : out);
|
|
23
|
+
return;
|
|
24
|
+
}
|
|
25
|
+
const specs = JSON.parse(out || '[]');
|
|
26
|
+
if (specs.length === 0) {
|
|
27
|
+
console.log(chalk.yellow('No specs found. Create `.tmd/specs/<capability>/spec.md` to enable spec mode.'));
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
console.log(chalk.bold('\nSpecs:'));
|
|
31
|
+
for (const s of specs)
|
|
32
|
+
console.log(` ${chalk.cyan(s.id)} ${chalk.gray(`requirements ${String(s.requirements)}`)}`);
|
|
33
|
+
}
|
|
34
|
+
export function specShowCommand(specId, opts = {}) {
|
|
35
|
+
if (!specId) {
|
|
36
|
+
console.error(chalk.red('✗ spec-id required'));
|
|
37
|
+
process.exit(1);
|
|
38
|
+
}
|
|
39
|
+
const cwd = process.cwd();
|
|
40
|
+
const out = runTmdSpec(['show', specId, '--project-root', cwd, ...(opts.json ? ['--json'] : [])]);
|
|
41
|
+
console.log(out);
|
|
42
|
+
}
|
|
43
|
+
export function specValidateCommand(item, opts = {}) {
|
|
44
|
+
const cwd = process.cwd();
|
|
45
|
+
const args = ['validate', ...(item ? [item] : []), '--project-root', cwd];
|
|
46
|
+
if (opts.all)
|
|
47
|
+
args.push('--all');
|
|
48
|
+
if (opts.specs)
|
|
49
|
+
args.push('--specs');
|
|
50
|
+
if (opts.changes)
|
|
51
|
+
args.push('--changes');
|
|
52
|
+
if (opts.strict)
|
|
53
|
+
args.push('--strict');
|
|
54
|
+
if (opts.noInteractive)
|
|
55
|
+
args.push('--no-interactive');
|
|
56
|
+
if (opts.json)
|
|
57
|
+
args.push('--json');
|
|
58
|
+
const out = runTmdSpec(args);
|
|
59
|
+
console.log(out);
|
|
60
|
+
}
|
|
61
|
+
export function specArchiveCommand(changeId, opts = {}) {
|
|
62
|
+
if (!changeId) {
|
|
63
|
+
console.error(chalk.red('✗ change-id required'));
|
|
64
|
+
process.exit(1);
|
|
65
|
+
}
|
|
66
|
+
const cwd = process.cwd();
|
|
67
|
+
const args = ['archive', changeId, '--project-root', cwd];
|
|
68
|
+
if (opts.yes)
|
|
69
|
+
args.push('--yes');
|
|
70
|
+
runTmdSpec(args);
|
|
71
|
+
console.log(chalk.green(`✓ Archived change: ${changeId}`));
|
|
72
|
+
}
|
|
73
|
+
//# sourceMappingURL=spec.js.map
|