wogiflow 1.0.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/.workflow/agents/reviewer.md +81 -0
- package/.workflow/agents/security.md +94 -0
- package/.workflow/agents/story-writer.md +58 -0
- package/.workflow/bridges/base-bridge.js +395 -0
- package/.workflow/bridges/claude-bridge.js +434 -0
- package/.workflow/bridges/index.js +130 -0
- package/.workflow/lib/assumption-detector.js +481 -0
- package/.workflow/lib/config-substitution.js +371 -0
- package/.workflow/lib/failure-categories.js +478 -0
- package/.workflow/state/app-map.md.template +15 -0
- package/.workflow/state/architecture.md.template +24 -0
- package/.workflow/state/component-index.json.template +5 -0
- package/.workflow/state/decisions.md.template +15 -0
- package/.workflow/state/feedback-patterns.md.template +9 -0
- package/.workflow/state/knowledge-sync.json.template +6 -0
- package/.workflow/state/progress.md.template +14 -0
- package/.workflow/state/ready.json.template +7 -0
- package/.workflow/state/request-log.md.template +14 -0
- package/.workflow/state/session-state.json.template +11 -0
- package/.workflow/state/stack.md.template +33 -0
- package/.workflow/state/testing.md.template +36 -0
- package/.workflow/templates/claude-md.hbs +257 -0
- package/.workflow/templates/correction-report.md +67 -0
- package/.workflow/templates/gemini-md.hbs +52 -0
- package/README.md +1802 -0
- package/bin/flow +205 -0
- package/lib/index.js +33 -0
- package/lib/installer.js +467 -0
- package/lib/release-channel.js +269 -0
- package/lib/skill-registry.js +526 -0
- package/lib/upgrader.js +401 -0
- package/lib/utils.js +305 -0
- package/package.json +64 -0
- package/scripts/flow +985 -0
- package/scripts/flow-adaptive-learning.js +1259 -0
- package/scripts/flow-aggregate.js +488 -0
- package/scripts/flow-archive +133 -0
- package/scripts/flow-auto-context.js +1015 -0
- package/scripts/flow-auto-learn.js +615 -0
- package/scripts/flow-bridge.js +223 -0
- package/scripts/flow-browser-suggest.js +316 -0
- package/scripts/flow-bug.js +247 -0
- package/scripts/flow-cascade.js +711 -0
- package/scripts/flow-changelog +85 -0
- package/scripts/flow-checkpoint.js +483 -0
- package/scripts/flow-cli.js +403 -0
- package/scripts/flow-code-intelligence.js +760 -0
- package/scripts/flow-complexity.js +502 -0
- package/scripts/flow-config-set.js +152 -0
- package/scripts/flow-constants.js +157 -0
- package/scripts/flow-context +152 -0
- package/scripts/flow-context-init.js +482 -0
- package/scripts/flow-context-monitor.js +384 -0
- package/scripts/flow-context-scoring.js +886 -0
- package/scripts/flow-correct.js +458 -0
- package/scripts/flow-damage-control.js +985 -0
- package/scripts/flow-deps +101 -0
- package/scripts/flow-diff.js +700 -0
- package/scripts/flow-done +151 -0
- package/scripts/flow-done.js +489 -0
- package/scripts/flow-durable-session.js +1541 -0
- package/scripts/flow-entropy-monitor.js +345 -0
- package/scripts/flow-export-profile +349 -0
- package/scripts/flow-export-scanner.js +1046 -0
- package/scripts/flow-figma-confirm.js +400 -0
- package/scripts/flow-figma-extract.js +496 -0
- package/scripts/flow-figma-generate.js +683 -0
- package/scripts/flow-figma-index.js +909 -0
- package/scripts/flow-figma-match.js +617 -0
- package/scripts/flow-figma-mcp-server.js +518 -0
- package/scripts/flow-figma-pipeline.js +414 -0
- package/scripts/flow-file-ops.js +301 -0
- package/scripts/flow-gate-confidence.js +825 -0
- package/scripts/flow-guided-edit.js +659 -0
- package/scripts/flow-health +185 -0
- package/scripts/flow-health.js +413 -0
- package/scripts/flow-hooks.js +556 -0
- package/scripts/flow-http-client.js +249 -0
- package/scripts/flow-hybrid-detect.js +167 -0
- package/scripts/flow-hybrid-interactive.js +591 -0
- package/scripts/flow-hybrid-test.js +152 -0
- package/scripts/flow-import-profile +439 -0
- package/scripts/flow-init +253 -0
- package/scripts/flow-instruction-richness.js +827 -0
- package/scripts/flow-jira-integration.js +579 -0
- package/scripts/flow-knowledge-router.js +522 -0
- package/scripts/flow-knowledge-sync.js +589 -0
- package/scripts/flow-linear-integration.js +631 -0
- package/scripts/flow-links.js +774 -0
- package/scripts/flow-log-manager.js +559 -0
- package/scripts/flow-loop-enforcer.js +1246 -0
- package/scripts/flow-loop-retry-learning.js +630 -0
- package/scripts/flow-lsp.js +923 -0
- package/scripts/flow-map-index +348 -0
- package/scripts/flow-map-sync +201 -0
- package/scripts/flow-memory-blocks.js +668 -0
- package/scripts/flow-memory-compactor.js +350 -0
- package/scripts/flow-memory-db.js +1110 -0
- package/scripts/flow-memory-sync.js +484 -0
- package/scripts/flow-metrics.js +353 -0
- package/scripts/flow-migrate-ids.js +370 -0
- package/scripts/flow-model-adapter.js +802 -0
- package/scripts/flow-model-router.js +884 -0
- package/scripts/flow-models.js +1231 -0
- package/scripts/flow-morning.js +517 -0
- package/scripts/flow-multi-approach.js +660 -0
- package/scripts/flow-new-feature +86 -0
- package/scripts/flow-onboard +1042 -0
- package/scripts/flow-orchestrate-llm.js +459 -0
- package/scripts/flow-orchestrate.js +3592 -0
- package/scripts/flow-output.js +123 -0
- package/scripts/flow-parallel-detector.js +399 -0
- package/scripts/flow-parallel-dispatch.js +987 -0
- package/scripts/flow-parallel.js +428 -0
- package/scripts/flow-pattern-enforcer.js +600 -0
- package/scripts/flow-prd-manager.js +282 -0
- package/scripts/flow-progress.js +323 -0
- package/scripts/flow-project-analyzer.js +975 -0
- package/scripts/flow-prompt-composer.js +487 -0
- package/scripts/flow-providers.js +1381 -0
- package/scripts/flow-queue.js +308 -0
- package/scripts/flow-ready +82 -0
- package/scripts/flow-ready.js +189 -0
- package/scripts/flow-regression.js +396 -0
- package/scripts/flow-response-parser.js +450 -0
- package/scripts/flow-resume.js +284 -0
- package/scripts/flow-rules-sync.js +439 -0
- package/scripts/flow-run-trace.js +718 -0
- package/scripts/flow-safety.js +587 -0
- package/scripts/flow-search +104 -0
- package/scripts/flow-security.js +481 -0
- package/scripts/flow-session-end +106 -0
- package/scripts/flow-session-end.js +437 -0
- package/scripts/flow-session-state.js +671 -0
- package/scripts/flow-setup-hooks +216 -0
- package/scripts/flow-setup-hooks.js +377 -0
- package/scripts/flow-skill-create.js +329 -0
- package/scripts/flow-skill-creator.js +572 -0
- package/scripts/flow-skill-generator.js +1046 -0
- package/scripts/flow-skill-learn.js +880 -0
- package/scripts/flow-skill-matcher.js +578 -0
- package/scripts/flow-spec-generator.js +820 -0
- package/scripts/flow-stack-wizard.js +895 -0
- package/scripts/flow-standup +162 -0
- package/scripts/flow-start +74 -0
- package/scripts/flow-start.js +235 -0
- package/scripts/flow-status +110 -0
- package/scripts/flow-status.js +301 -0
- package/scripts/flow-step-browser.js +83 -0
- package/scripts/flow-step-changelog.js +217 -0
- package/scripts/flow-step-comments.js +306 -0
- package/scripts/flow-step-complexity.js +234 -0
- package/scripts/flow-step-coverage.js +218 -0
- package/scripts/flow-step-knowledge.js +193 -0
- package/scripts/flow-step-pr-tests.js +364 -0
- package/scripts/flow-step-regression.js +89 -0
- package/scripts/flow-step-review.js +516 -0
- package/scripts/flow-step-security.js +162 -0
- package/scripts/flow-step-silent-failures.js +290 -0
- package/scripts/flow-step-simplifier.js +346 -0
- package/scripts/flow-story +105 -0
- package/scripts/flow-story.js +500 -0
- package/scripts/flow-suspend.js +252 -0
- package/scripts/flow-sync-daemon.js +654 -0
- package/scripts/flow-task-analyzer.js +606 -0
- package/scripts/flow-team-dashboard.js +748 -0
- package/scripts/flow-team-sync.js +752 -0
- package/scripts/flow-team.js +977 -0
- package/scripts/flow-tech-options.js +528 -0
- package/scripts/flow-templates.js +812 -0
- package/scripts/flow-tiered-learning.js +728 -0
- package/scripts/flow-trace +204 -0
- package/scripts/flow-transcript-chunking.js +1106 -0
- package/scripts/flow-transcript-digest.js +7918 -0
- package/scripts/flow-transcript-language.js +465 -0
- package/scripts/flow-transcript-parsing.js +1085 -0
- package/scripts/flow-transcript-stories.js +2194 -0
- package/scripts/flow-update-map +224 -0
- package/scripts/flow-utils.js +2242 -0
- package/scripts/flow-verification.js +644 -0
- package/scripts/flow-verify.js +1177 -0
- package/scripts/flow-voice-input.js +638 -0
- package/scripts/flow-watch +168 -0
- package/scripts/flow-workflow-steps.js +521 -0
- package/scripts/flow-workflow.js +1029 -0
- package/scripts/flow-worktree.js +489 -0
- package/scripts/hooks/adapters/base-adapter.js +102 -0
- package/scripts/hooks/adapters/claude-code.js +359 -0
- package/scripts/hooks/adapters/index.js +79 -0
- package/scripts/hooks/core/component-check.js +341 -0
- package/scripts/hooks/core/index.js +35 -0
- package/scripts/hooks/core/loop-check.js +241 -0
- package/scripts/hooks/core/session-context.js +294 -0
- package/scripts/hooks/core/task-gate.js +177 -0
- package/scripts/hooks/core/validation.js +230 -0
- package/scripts/hooks/entry/claude-code/post-tool-use.js +65 -0
- package/scripts/hooks/entry/claude-code/pre-tool-use.js +89 -0
- package/scripts/hooks/entry/claude-code/session-end.js +87 -0
- package/scripts/hooks/entry/claude-code/session-start.js +46 -0
- package/scripts/hooks/entry/claude-code/stop.js +43 -0
- package/scripts/postinstall.js +139 -0
- package/templates/browser-test-flow.json +56 -0
- package/templates/bug-report.md +43 -0
- package/templates/component-detail.md +42 -0
- package/templates/component.stories.tsx +49 -0
- package/templates/context/constraints.md +83 -0
- package/templates/context/conventions.md +177 -0
- package/templates/context/stack.md +60 -0
- package/templates/correction-report.md +90 -0
- package/templates/feature-proposal.md +35 -0
- package/templates/hybrid/_base.md +254 -0
- package/templates/hybrid/_patterns.md +45 -0
- package/templates/hybrid/create-component.md +127 -0
- package/templates/hybrid/create-file.md +56 -0
- package/templates/hybrid/create-hook.md +145 -0
- package/templates/hybrid/create-service.md +70 -0
- package/templates/hybrid/fix-bug.md +33 -0
- package/templates/hybrid/modify-file.md +55 -0
- package/templates/story.md +68 -0
- package/templates/task.json +56 -0
- package/templates/trace.md +69 -0
|
@@ -0,0 +1,526 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Wogi Flow Skill Registry
|
|
5
|
+
*
|
|
6
|
+
* Handles skill installation and management with `flow skill`.
|
|
7
|
+
* Fetches skills from a GitHub-based registry, validates them,
|
|
8
|
+
* and installs them to the project's .claude/skills/ directory.
|
|
9
|
+
*
|
|
10
|
+
* @module lib/skill-registry
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
const fs = require('fs');
|
|
14
|
+
const path = require('path');
|
|
15
|
+
|
|
16
|
+
// Shared utilities
|
|
17
|
+
const {
|
|
18
|
+
findProjectRoot,
|
|
19
|
+
safeJsonParse,
|
|
20
|
+
safeReadJson,
|
|
21
|
+
httpsGet,
|
|
22
|
+
validatePath,
|
|
23
|
+
safeWriteFile
|
|
24
|
+
} = require('./utils');
|
|
25
|
+
|
|
26
|
+
// Registry configuration
|
|
27
|
+
const REGISTRY_CONFIG = {
|
|
28
|
+
baseUrl: 'https://raw.githubusercontent.com/Wogi-Git/wogi-flow-skills',
|
|
29
|
+
branch: 'main',
|
|
30
|
+
manifestFile: 'manifest.json',
|
|
31
|
+
indexFile: 'index.json'
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
// Local cache settings
|
|
35
|
+
const CACHE_DIR = '.workflow/cache/skills';
|
|
36
|
+
const CACHE_TTL = 3600000; // 1 hour in milliseconds
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Parse command line arguments with bounds checking
|
|
40
|
+
* @param {string[]} args - Command line arguments
|
|
41
|
+
* @returns {Object} Parsed options
|
|
42
|
+
*/
|
|
43
|
+
function parseArgs(args) {
|
|
44
|
+
const options = {
|
|
45
|
+
command: args[0] || 'list',
|
|
46
|
+
skillName: args[1] || null,
|
|
47
|
+
version: null,
|
|
48
|
+
force: false,
|
|
49
|
+
help: false
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
for (let i = 0; i < args.length; i++) {
|
|
53
|
+
const arg = args[i];
|
|
54
|
+
|
|
55
|
+
if (arg === '--version' || arg === '-v') {
|
|
56
|
+
// Bounds check before accessing next argument
|
|
57
|
+
if (i + 1 >= args.length) {
|
|
58
|
+
console.error('Error: --version requires a value');
|
|
59
|
+
options.help = true;
|
|
60
|
+
break;
|
|
61
|
+
}
|
|
62
|
+
options.version = args[++i];
|
|
63
|
+
} else if (arg === '--force' || arg === '-f') {
|
|
64
|
+
options.force = true;
|
|
65
|
+
} else if (arg === '--help' || arg === '-h') {
|
|
66
|
+
options.help = true;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
return options;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Show help message
|
|
75
|
+
*/
|
|
76
|
+
function showHelp() {
|
|
77
|
+
console.log(`
|
|
78
|
+
Usage: flow skill <command> [options]
|
|
79
|
+
|
|
80
|
+
Manage skills from the Wogi Flow registry.
|
|
81
|
+
|
|
82
|
+
Commands:
|
|
83
|
+
list List available skills from registry
|
|
84
|
+
add <name> Install a skill
|
|
85
|
+
remove <name> Remove an installed skill
|
|
86
|
+
update [name] Update skill(s) to latest version
|
|
87
|
+
info <name> Show skill details
|
|
88
|
+
|
|
89
|
+
Options:
|
|
90
|
+
--version, -v <ver> Install specific version
|
|
91
|
+
--force, -f Force reinstall or overwrite
|
|
92
|
+
--help, -h Show this help message
|
|
93
|
+
|
|
94
|
+
Examples:
|
|
95
|
+
flow skill list # List all available skills
|
|
96
|
+
flow skill add react # Install react skill
|
|
97
|
+
flow skill add nestjs -v 1.2.0 # Install specific version
|
|
98
|
+
flow skill remove react # Remove skill
|
|
99
|
+
flow skill update # Update all skills
|
|
100
|
+
flow skill info react # Show skill details
|
|
101
|
+
`);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// findProjectRoot and httpsGet are imported from ./utils
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Fetch with caching
|
|
108
|
+
* @param {string} url - URL to fetch
|
|
109
|
+
* @param {string} cacheKey - Cache key
|
|
110
|
+
* @param {string} projectRoot - Project root directory
|
|
111
|
+
* @returns {Promise<string>} Response body
|
|
112
|
+
*/
|
|
113
|
+
async function fetchWithCache(url, cacheKey, projectRoot) {
|
|
114
|
+
const cacheDir = path.join(projectRoot, CACHE_DIR);
|
|
115
|
+
const cachePath = path.join(cacheDir, `${cacheKey}.json`);
|
|
116
|
+
|
|
117
|
+
// Check cache
|
|
118
|
+
if (fs.existsSync(cachePath)) {
|
|
119
|
+
const cached = safeReadJson(cachePath);
|
|
120
|
+
if (cached && Date.now() - cached.timestamp < CACHE_TTL) {
|
|
121
|
+
return cached.data;
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// Fetch fresh
|
|
126
|
+
const data = await httpsGet(url);
|
|
127
|
+
|
|
128
|
+
// Save to cache
|
|
129
|
+
try {
|
|
130
|
+
fs.mkdirSync(cacheDir, { recursive: true });
|
|
131
|
+
fs.writeFileSync(cachePath, JSON.stringify({
|
|
132
|
+
timestamp: Date.now(),
|
|
133
|
+
data
|
|
134
|
+
}));
|
|
135
|
+
} catch {
|
|
136
|
+
// Cache write failed, continue anyway
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
return data;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Fetch skill index from registry
|
|
144
|
+
* @param {string} projectRoot - Project root directory
|
|
145
|
+
* @returns {Promise<Object>} Skill index
|
|
146
|
+
*/
|
|
147
|
+
async function fetchSkillIndex(projectRoot) {
|
|
148
|
+
const url = `${REGISTRY_CONFIG.baseUrl}/${REGISTRY_CONFIG.branch}/${REGISTRY_CONFIG.indexFile}`;
|
|
149
|
+
|
|
150
|
+
try {
|
|
151
|
+
const data = await fetchWithCache(url, 'index', projectRoot);
|
|
152
|
+
const parsed = safeJsonParse(data);
|
|
153
|
+
if (!parsed) {
|
|
154
|
+
throw new Error('Invalid index data');
|
|
155
|
+
}
|
|
156
|
+
return parsed;
|
|
157
|
+
} catch (err) {
|
|
158
|
+
// Return mock index for development/offline
|
|
159
|
+
return {
|
|
160
|
+
version: '1.0',
|
|
161
|
+
skills: {
|
|
162
|
+
react: {
|
|
163
|
+
name: 'react',
|
|
164
|
+
title: 'React',
|
|
165
|
+
description: 'React component patterns and best practices',
|
|
166
|
+
version: '1.0.0',
|
|
167
|
+
author: 'Wogi-Git'
|
|
168
|
+
},
|
|
169
|
+
nestjs: {
|
|
170
|
+
name: 'nestjs',
|
|
171
|
+
title: 'NestJS',
|
|
172
|
+
description: 'NestJS module patterns with entities, DTOs, services',
|
|
173
|
+
version: '1.0.0',
|
|
174
|
+
author: 'Wogi-Git'
|
|
175
|
+
},
|
|
176
|
+
python: {
|
|
177
|
+
name: 'python',
|
|
178
|
+
title: 'Python',
|
|
179
|
+
description: 'Python/FastAPI patterns and best practices',
|
|
180
|
+
version: '1.0.0',
|
|
181
|
+
author: 'Wogi-Git'
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
};
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* Fetch skill manifest from registry
|
|
190
|
+
* @param {string} skillName - Skill name
|
|
191
|
+
* @param {string} projectRoot - Project root directory
|
|
192
|
+
* @returns {Promise<Object>} Skill manifest
|
|
193
|
+
*/
|
|
194
|
+
async function fetchSkillManifest(skillName, projectRoot) {
|
|
195
|
+
const url = `${REGISTRY_CONFIG.baseUrl}/${REGISTRY_CONFIG.branch}/skills/${skillName}/${REGISTRY_CONFIG.manifestFile}`;
|
|
196
|
+
|
|
197
|
+
try {
|
|
198
|
+
const data = await fetchWithCache(url, `manifest-${skillName}`, projectRoot);
|
|
199
|
+
const parsed = safeJsonParse(data);
|
|
200
|
+
if (!parsed) {
|
|
201
|
+
throw new Error('Invalid manifest data');
|
|
202
|
+
}
|
|
203
|
+
return parsed;
|
|
204
|
+
} catch (err) {
|
|
205
|
+
throw new Error(`Skill '${skillName}' not found in registry`);
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
/**
|
|
210
|
+
* Download skill files
|
|
211
|
+
* @param {string} skillName - Skill name
|
|
212
|
+
* @param {Object} manifest - Skill manifest
|
|
213
|
+
* @param {string} projectRoot - Project root directory
|
|
214
|
+
* @returns {Promise<Object>} Downloaded files
|
|
215
|
+
*/
|
|
216
|
+
async function downloadSkillFiles(skillName, manifest, projectRoot) {
|
|
217
|
+
const files = {};
|
|
218
|
+
const baseUrl = `${REGISTRY_CONFIG.baseUrl}/${REGISTRY_CONFIG.branch}/skills/${skillName}`;
|
|
219
|
+
|
|
220
|
+
// Standard skill files
|
|
221
|
+
const standardFiles = ['skill.md', 'patterns.md', 'anti-patterns.md', 'learnings.md'];
|
|
222
|
+
const filesToDownload = manifest.files || standardFiles;
|
|
223
|
+
|
|
224
|
+
for (const file of filesToDownload) {
|
|
225
|
+
try {
|
|
226
|
+
const url = `${baseUrl}/${file}`;
|
|
227
|
+
const content = await httpsGet(url);
|
|
228
|
+
files[file] = content;
|
|
229
|
+
} catch {
|
|
230
|
+
// File doesn't exist, skip
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
return files;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
/**
|
|
238
|
+
* Get installed skills
|
|
239
|
+
* @param {string} projectRoot - Project root directory
|
|
240
|
+
* @returns {Object} Installed skills map
|
|
241
|
+
*/
|
|
242
|
+
function getInstalledSkills(projectRoot) {
|
|
243
|
+
const skillsDir = path.join(projectRoot, '.claude', 'skills');
|
|
244
|
+
const installed = {};
|
|
245
|
+
|
|
246
|
+
if (!fs.existsSync(skillsDir)) {
|
|
247
|
+
return installed;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
const entries = fs.readdirSync(skillsDir, { withFileTypes: true });
|
|
251
|
+
|
|
252
|
+
for (const entry of entries) {
|
|
253
|
+
if (entry.isDirectory()) {
|
|
254
|
+
const manifestPath = path.join(skillsDir, entry.name, 'manifest.json');
|
|
255
|
+
const manifest = safeReadJson(manifestPath);
|
|
256
|
+
if (manifest) {
|
|
257
|
+
installed[entry.name] = manifest;
|
|
258
|
+
} else {
|
|
259
|
+
installed[entry.name] = { name: entry.name, version: 'unknown' };
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
return installed;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
/**
|
|
268
|
+
* List available skills
|
|
269
|
+
* @param {string} projectRoot - Project root directory
|
|
270
|
+
*/
|
|
271
|
+
async function listSkills(projectRoot) {
|
|
272
|
+
console.log('\nš¦ Available Skills\n');
|
|
273
|
+
|
|
274
|
+
const index = await fetchSkillIndex(projectRoot);
|
|
275
|
+
const installed = getInstalledSkills(projectRoot);
|
|
276
|
+
|
|
277
|
+
const skills = Object.values(index.skills || {});
|
|
278
|
+
|
|
279
|
+
if (skills.length === 0) {
|
|
280
|
+
console.log(' No skills available in registry');
|
|
281
|
+
return;
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
for (const skill of skills) {
|
|
285
|
+
const isInstalled = installed[skill.name];
|
|
286
|
+
const status = isInstalled ? 'ā' : ' ';
|
|
287
|
+
const versionInfo = isInstalled
|
|
288
|
+
? `(installed: ${isInstalled.version})`
|
|
289
|
+
: `(v${skill.version})`;
|
|
290
|
+
|
|
291
|
+
console.log(` ${status} ${skill.name.padEnd(15)} ${versionInfo}`);
|
|
292
|
+
console.log(` ${skill.description}`);
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
console.log('\nUse `flow skill add <name>` to install a skill');
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
/**
|
|
299
|
+
* Install a skill
|
|
300
|
+
* @param {string} skillName - Skill name
|
|
301
|
+
* @param {string} projectRoot - Project root directory
|
|
302
|
+
* @param {Object} options - Installation options
|
|
303
|
+
*/
|
|
304
|
+
async function addSkill(skillName, projectRoot, options) {
|
|
305
|
+
const skillsDir = path.join(projectRoot, '.claude', 'skills', skillName);
|
|
306
|
+
|
|
307
|
+
// Check if already installed
|
|
308
|
+
if (fs.existsSync(skillsDir) && !options.force) {
|
|
309
|
+
console.log(`Skill '${skillName}' is already installed.`);
|
|
310
|
+
console.log('Use --force to reinstall.');
|
|
311
|
+
return;
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
console.log(`\nInstalling skill: ${skillName}\n`);
|
|
315
|
+
|
|
316
|
+
// Fetch manifest
|
|
317
|
+
let manifest;
|
|
318
|
+
try {
|
|
319
|
+
manifest = await fetchSkillManifest(skillName, projectRoot);
|
|
320
|
+
} catch (err) {
|
|
321
|
+
// Use index info if manifest not found
|
|
322
|
+
const index = await fetchSkillIndex(projectRoot);
|
|
323
|
+
if (index.skills && index.skills[skillName]) {
|
|
324
|
+
manifest = index.skills[skillName];
|
|
325
|
+
} else {
|
|
326
|
+
console.error(`Error: ${err.message}`);
|
|
327
|
+
process.exit(1);
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
// Download files
|
|
332
|
+
console.log(' Downloading files...');
|
|
333
|
+
const files = await downloadSkillFiles(skillName, manifest, projectRoot);
|
|
334
|
+
|
|
335
|
+
if (Object.keys(files).length === 0) {
|
|
336
|
+
console.error('Error: No skill files found');
|
|
337
|
+
process.exit(1);
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
// Create skill directory
|
|
341
|
+
fs.mkdirSync(skillsDir, { recursive: true });
|
|
342
|
+
|
|
343
|
+
// Write files with path validation (prevents path traversal)
|
|
344
|
+
for (const [filename, content] of Object.entries(files)) {
|
|
345
|
+
// Use basename to prevent path traversal attacks like "../../../etc/passwd"
|
|
346
|
+
const safeFilename = path.basename(filename);
|
|
347
|
+
const targetPath = validatePath(skillsDir, safeFilename);
|
|
348
|
+
if (!targetPath) {
|
|
349
|
+
console.error(` Warning: Skipping invalid filename '${filename}'`);
|
|
350
|
+
continue;
|
|
351
|
+
}
|
|
352
|
+
fs.writeFileSync(targetPath, content);
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
// Write manifest (safe - we control the filename)
|
|
356
|
+
const localManifest = {
|
|
357
|
+
...manifest,
|
|
358
|
+
installedAt: new Date().toISOString(),
|
|
359
|
+
installedVersion: manifest.version
|
|
360
|
+
};
|
|
361
|
+
fs.writeFileSync(
|
|
362
|
+
path.join(skillsDir, 'manifest.json'),
|
|
363
|
+
JSON.stringify(localManifest, null, 2)
|
|
364
|
+
);
|
|
365
|
+
|
|
366
|
+
console.log(` ā Installed ${Object.keys(files).length} files`);
|
|
367
|
+
console.log(`\nā
Skill '${skillName}' installed successfully!\n`);
|
|
368
|
+
console.log(`Files: .claude/skills/${skillName}/`);
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
/**
|
|
372
|
+
* Remove a skill
|
|
373
|
+
* @param {string} skillName - Skill name
|
|
374
|
+
* @param {string} projectRoot - Project root directory
|
|
375
|
+
*/
|
|
376
|
+
function removeSkill(skillName, projectRoot) {
|
|
377
|
+
const skillsDir = path.join(projectRoot, '.claude', 'skills', skillName);
|
|
378
|
+
|
|
379
|
+
if (!fs.existsSync(skillsDir)) {
|
|
380
|
+
console.log(`Skill '${skillName}' is not installed.`);
|
|
381
|
+
return;
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
// Remove directory recursively
|
|
385
|
+
fs.rmSync(skillsDir, { recursive: true });
|
|
386
|
+
|
|
387
|
+
console.log(`ā Removed skill: ${skillName}`);
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
/**
|
|
391
|
+
* Update skills
|
|
392
|
+
* @param {string|null} skillName - Skill name or null for all
|
|
393
|
+
* @param {string} projectRoot - Project root directory
|
|
394
|
+
*/
|
|
395
|
+
async function updateSkills(skillName, projectRoot) {
|
|
396
|
+
const installed = getInstalledSkills(projectRoot);
|
|
397
|
+
|
|
398
|
+
if (Object.keys(installed).length === 0) {
|
|
399
|
+
console.log('No skills installed.');
|
|
400
|
+
return;
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
const skillsToUpdate = skillName
|
|
404
|
+
? [skillName]
|
|
405
|
+
: Object.keys(installed);
|
|
406
|
+
|
|
407
|
+
console.log('\nš Updating skills...\n');
|
|
408
|
+
|
|
409
|
+
const index = await fetchSkillIndex(projectRoot);
|
|
410
|
+
|
|
411
|
+
for (const name of skillsToUpdate) {
|
|
412
|
+
if (!installed[name]) {
|
|
413
|
+
console.log(` ā ${name}: not installed`);
|
|
414
|
+
continue;
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
const registryVersion = index.skills?.[name]?.version || 'unknown';
|
|
418
|
+
const installedVersion = installed[name].version || 'unknown';
|
|
419
|
+
|
|
420
|
+
if (registryVersion === installedVersion && registryVersion !== 'unknown') {
|
|
421
|
+
console.log(` ā ${name}: up to date (${installedVersion})`);
|
|
422
|
+
} else {
|
|
423
|
+
console.log(` ā ${name}: ${installedVersion} ā ${registryVersion}`);
|
|
424
|
+
await addSkill(name, projectRoot, { force: true });
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
/**
|
|
430
|
+
* Show skill info
|
|
431
|
+
* @param {string} skillName - Skill name
|
|
432
|
+
* @param {string} projectRoot - Project root directory
|
|
433
|
+
*/
|
|
434
|
+
async function showSkillInfo(skillName, projectRoot) {
|
|
435
|
+
const index = await fetchSkillIndex(projectRoot);
|
|
436
|
+
const installed = getInstalledSkills(projectRoot);
|
|
437
|
+
|
|
438
|
+
const skill = index.skills?.[skillName];
|
|
439
|
+
|
|
440
|
+
if (!skill) {
|
|
441
|
+
console.log(`Skill '${skillName}' not found in registry.`);
|
|
442
|
+
return;
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
console.log(`\nš¦ ${skill.title || skill.name}\n`);
|
|
446
|
+
console.log(` Name: ${skill.name}`);
|
|
447
|
+
console.log(` Version: ${skill.version}`);
|
|
448
|
+
console.log(` Author: ${skill.author || 'Unknown'}`);
|
|
449
|
+
console.log(` Description: ${skill.description}`);
|
|
450
|
+
|
|
451
|
+
if (installed[skillName]) {
|
|
452
|
+
console.log(`\n Status: Installed (v${installed[skillName].version})`);
|
|
453
|
+
if (installed[skillName].installedAt) {
|
|
454
|
+
console.log(` Installed: ${installed[skillName].installedAt}`);
|
|
455
|
+
}
|
|
456
|
+
} else {
|
|
457
|
+
console.log(`\n Status: Not installed`);
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
console.log('');
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
/**
|
|
464
|
+
* Main skill registry function
|
|
465
|
+
* @param {string[]} args - Command line arguments
|
|
466
|
+
*/
|
|
467
|
+
async function skill(args) {
|
|
468
|
+
const options = parseArgs(args);
|
|
469
|
+
|
|
470
|
+
if (options.help) {
|
|
471
|
+
showHelp();
|
|
472
|
+
return;
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
const projectRoot = findProjectRoot();
|
|
476
|
+
|
|
477
|
+
if (!projectRoot) {
|
|
478
|
+
console.error('Error: Not in a Wogi Flow project');
|
|
479
|
+
console.error('Use `flow init` to initialize a new project');
|
|
480
|
+
process.exit(1);
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
switch (options.command) {
|
|
484
|
+
case 'list':
|
|
485
|
+
await listSkills(projectRoot);
|
|
486
|
+
break;
|
|
487
|
+
|
|
488
|
+
case 'add':
|
|
489
|
+
if (!options.skillName) {
|
|
490
|
+
console.error('Error: Please specify a skill name');
|
|
491
|
+
console.error('Usage: flow skill add <name>');
|
|
492
|
+
process.exit(1);
|
|
493
|
+
}
|
|
494
|
+
await addSkill(options.skillName, projectRoot, options);
|
|
495
|
+
break;
|
|
496
|
+
|
|
497
|
+
case 'remove':
|
|
498
|
+
if (!options.skillName) {
|
|
499
|
+
console.error('Error: Please specify a skill name');
|
|
500
|
+
console.error('Usage: flow skill remove <name>');
|
|
501
|
+
process.exit(1);
|
|
502
|
+
}
|
|
503
|
+
removeSkill(options.skillName, projectRoot);
|
|
504
|
+
break;
|
|
505
|
+
|
|
506
|
+
case 'update':
|
|
507
|
+
await updateSkills(options.skillName, projectRoot);
|
|
508
|
+
break;
|
|
509
|
+
|
|
510
|
+
case 'info':
|
|
511
|
+
if (!options.skillName) {
|
|
512
|
+
console.error('Error: Please specify a skill name');
|
|
513
|
+
console.error('Usage: flow skill info <name>');
|
|
514
|
+
process.exit(1);
|
|
515
|
+
}
|
|
516
|
+
await showSkillInfo(options.skillName, projectRoot);
|
|
517
|
+
break;
|
|
518
|
+
|
|
519
|
+
default:
|
|
520
|
+
console.error(`Unknown command: ${options.command}`);
|
|
521
|
+
showHelp();
|
|
522
|
+
process.exit(1);
|
|
523
|
+
}
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
module.exports = { skill };
|