@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.
@@ -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
@@ -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
@@ -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
- export function planCommand(description, options) {
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
- ensureDir(planDir);
16
- // Create plan.md
17
- const planContent = renderTemplate(planTemplate, { description });
18
- writeFileSync(join(planDir, 'plan.md'), planContent);
19
- // Create resources.md
20
- const resourcesContent = renderTemplate(resourcesTemplate, { description });
21
- writeFileSync(join(planDir, 'resources.md'), resourcesContent);
22
- // Initialize task status
23
- updateTaskStatus(taskId, 'planning');
24
- console.log(chalk.green(`✓ Created plan for task: ${taskId}`));
25
- console.log(chalk.blue(` Directory: ${planDir}`));
26
- // OpenSpec integration
27
- if (options.openspec) {
28
- const changeId = createOpenSpecChange(description, taskId);
29
- if (changeId) {
30
- console.log(chalk.green(` Linked to OpenSpec change: ${changeId}`));
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,7 @@
1
+ export interface ServerCommandOptions {
2
+ host?: string;
3
+ port?: number;
4
+ projectRoot?: string;
5
+ }
6
+ export declare function serverCommand(options: ServerCommandOptions): void;
7
+ //# sourceMappingURL=server.d.ts.map
@@ -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
@@ -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. Create one with: tmd plan "description" --skill'));
57
- console.log(chalk.gray('Or add from skills.sh: tmd skills add <owner/repo>'));
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
- let sourceTag = '';
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 === 'skillssh') {
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.skill)
216
+ if (options?.skill)
165
217
  console.log(chalk.gray(` Skill: ${options.skill}`));
166
- if (options.branch)
218
+ if (options?.branch)
167
219
  console.log(chalk.gray(` Branch: ${options.branch}`));
168
220
  console.log('─'.repeat(60));
169
- const args = ['import-github', name, ...base(), ...(options.skill ? ['--skill', options.skill] : []), ...(options.branch ? ['--branch', options.branch] : []), ...(options.force ? ['--force'] : []), ...(options.all ? ['--all'] : [])];
170
- const out = runTmdSkills(args);
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
- else {
176
- console.log(chalk.blue(`\nAdding skill from skills.sh: ${name}${options?.skill != null ? ` (skill: ${options.skill})` : ''}`));
177
- console.log('─'.repeat(60));
178
- const args = ['import-skillssh', name, ...base(), ...(options?.skill ? ['--skill', options.skill] : []), ...(options?.all ? ['--all'] : [])];
179
- const out = runTmdSkills(args);
180
- const skill = JSON.parse(out);
181
- console.log(chalk.green(`✓ Skill added: ${skill.name}`));
182
- console.log(chalk.gray(` Location: ${getSkillDir(skill.name)}`));
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
- console.error(chalk.red('✗ Skill name required for update'));
198
- process.exit(1);
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, create, update, remove'));
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
@@ -0,0 +1,2 @@
1
+ export declare function uiCommand(): void;
2
+ //# sourceMappingURL=ui.d.ts.map