nova-terminal-assistant 0.1.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.
Potentially problematic release.
This version of nova-terminal-assistant might be problematic. Click here for more details.
- package/README.md +358 -0
- package/bin/nova +38 -0
- package/bin/nova.js +12 -0
- package/package.json +67 -0
- package/src/cli/commands/SmartCompletion.ts +458 -0
- package/src/cli/index.ts +5 -0
- package/src/cli/startup/IFlowRepl.ts +212 -0
- package/src/cli/startup/InkBasedRepl.ts +1056 -0
- package/src/cli/startup/InteractiveRepl.ts +2833 -0
- package/src/cli/startup/NovaApp.ts +1861 -0
- package/src/cli/startup/index.ts +4 -0
- package/src/cli/startup/parseArgs.ts +293 -0
- package/src/cli/test-modules.ts +27 -0
- package/src/cli/ui/IFlowDropdown.ts +425 -0
- package/src/cli/ui/ModernReplUI.ts +276 -0
- package/src/cli/ui/SimpleSelector2.ts +215 -0
- package/src/cli/ui/components/ConfirmDialog.ts +176 -0
- package/src/cli/ui/components/ErrorPanel.ts +364 -0
- package/src/cli/ui/components/InkAppRunner.tsx +67 -0
- package/src/cli/ui/components/InkComponents.tsx +613 -0
- package/src/cli/ui/components/NovaInkApp.tsx +312 -0
- package/src/cli/ui/components/ProgressBar.ts +177 -0
- package/src/cli/ui/components/ProgressIndicator.ts +298 -0
- package/src/cli/ui/components/QuickActions.ts +396 -0
- package/src/cli/ui/components/SimpleErrorPanel.ts +231 -0
- package/src/cli/ui/components/StatusBar.ts +194 -0
- package/src/cli/ui/components/ThinkingBlockRenderer.ts +401 -0
- package/src/cli/ui/components/index.ts +27 -0
- package/src/cli/ui/ink-prototype.tsx +347 -0
- package/src/cli/utils/CliUI.ts +336 -0
- package/src/cli/utils/CompletionHelper.ts +388 -0
- package/src/cli/utils/EnhancedCompleter.test.ts +226 -0
- package/src/cli/utils/EnhancedCompleter.ts +513 -0
- package/src/cli/utils/ErrorEnhancer.ts +429 -0
- package/src/cli/utils/OutputFormatter.ts +193 -0
- package/src/cli/utils/index.ts +9 -0
- package/src/core/agents/AgentOrchestrator.ts +515 -0
- package/src/core/agents/index.ts +17 -0
- package/src/core/audit/AuditLogger.ts +509 -0
- package/src/core/audit/index.ts +11 -0
- package/src/core/auth/AuthManager.d.ts.map +1 -0
- package/src/core/auth/AuthManager.ts +138 -0
- package/src/core/auth/index.d.ts.map +1 -0
- package/src/core/auth/index.ts +2 -0
- package/src/core/config/ConfigManager.d.ts.map +1 -0
- package/src/core/config/ConfigManager.test.ts +183 -0
- package/src/core/config/ConfigManager.ts +1219 -0
- package/src/core/config/index.d.ts.map +1 -0
- package/src/core/config/index.ts +1 -0
- package/src/core/context/ContextBuilder.d.ts.map +1 -0
- package/src/core/context/ContextBuilder.ts +171 -0
- package/src/core/context/ContextCompressor.d.ts.map +1 -0
- package/src/core/context/ContextCompressor.ts +642 -0
- package/src/core/context/LayeredMemoryManager.ts +657 -0
- package/src/core/context/MemoryDiscovery.d.ts.map +1 -0
- package/src/core/context/MemoryDiscovery.ts +175 -0
- package/src/core/context/defaultSystemPrompt.d.ts.map +1 -0
- package/src/core/context/defaultSystemPrompt.ts +35 -0
- package/src/core/context/index.d.ts.map +1 -0
- package/src/core/context/index.ts +22 -0
- package/src/core/extensions/SkillGenerator.ts +421 -0
- package/src/core/extensions/SkillInstaller.d.ts.map +1 -0
- package/src/core/extensions/SkillInstaller.ts +257 -0
- package/src/core/extensions/SkillRegistry.d.ts.map +1 -0
- package/src/core/extensions/SkillRegistry.ts +361 -0
- package/src/core/extensions/SkillValidator.ts +525 -0
- package/src/core/extensions/index.ts +15 -0
- package/src/core/index.d.ts.map +1 -0
- package/src/core/index.ts +42 -0
- package/src/core/mcp/McpManager.d.ts.map +1 -0
- package/src/core/mcp/McpManager.ts +632 -0
- package/src/core/mcp/index.d.ts.map +1 -0
- package/src/core/mcp/index.ts +2 -0
- package/src/core/model/ModelClient.d.ts.map +1 -0
- package/src/core/model/ModelClient.ts +217 -0
- package/src/core/model/ModelConnectionTester.ts +363 -0
- package/src/core/model/ModelValidator.ts +348 -0
- package/src/core/model/index.d.ts.map +1 -0
- package/src/core/model/index.ts +6 -0
- package/src/core/model/providers/AnthropicProvider.d.ts.map +1 -0
- package/src/core/model/providers/AnthropicProvider.ts +279 -0
- package/src/core/model/providers/CodingPlanProvider.d.ts.map +1 -0
- package/src/core/model/providers/CodingPlanProvider.ts +210 -0
- package/src/core/model/providers/OllamaCloudProvider.d.ts.map +1 -0
- package/src/core/model/providers/OllamaCloudProvider.ts +405 -0
- package/src/core/model/providers/OllamaManager.d.ts.map +1 -0
- package/src/core/model/providers/OllamaManager.ts +201 -0
- package/src/core/model/providers/OllamaProvider.d.ts.map +1 -0
- package/src/core/model/providers/OllamaProvider.ts +73 -0
- package/src/core/model/providers/OpenAICompatibleProvider.d.ts.map +1 -0
- package/src/core/model/providers/OpenAICompatibleProvider.ts +327 -0
- package/src/core/model/providers/OpenAIProvider.d.ts.map +1 -0
- package/src/core/model/providers/OpenAIProvider.ts +29 -0
- package/src/core/model/providers/index.d.ts.map +1 -0
- package/src/core/model/providers/index.ts +12 -0
- package/src/core/model/types.d.ts.map +1 -0
- package/src/core/model/types.ts +77 -0
- package/src/core/security/ApprovalManager.d.ts.map +1 -0
- package/src/core/security/ApprovalManager.ts +174 -0
- package/src/core/security/FileFilter.d.ts.map +1 -0
- package/src/core/security/FileFilter.ts +141 -0
- package/src/core/security/HookExecutor.d.ts.map +1 -0
- package/src/core/security/HookExecutor.ts +178 -0
- package/src/core/security/SandboxExecutor.ts +447 -0
- package/src/core/security/index.d.ts.map +1 -0
- package/src/core/security/index.ts +8 -0
- package/src/core/session/AgentLoop.d.ts.map +1 -0
- package/src/core/session/AgentLoop.ts +501 -0
- package/src/core/session/SessionManager.d.ts.map +1 -0
- package/src/core/session/SessionManager.test.ts +183 -0
- package/src/core/session/SessionManager.ts +460 -0
- package/src/core/session/index.d.ts.map +1 -0
- package/src/core/session/index.ts +3 -0
- package/src/core/telemetry/Telemetry.d.ts.map +1 -0
- package/src/core/telemetry/Telemetry.ts +90 -0
- package/src/core/telemetry/TelemetryService.ts +531 -0
- package/src/core/telemetry/index.d.ts.map +1 -0
- package/src/core/telemetry/index.ts +12 -0
- package/src/core/testing/AutoFixer.ts +385 -0
- package/src/core/testing/ErrorAnalyzer.ts +499 -0
- package/src/core/testing/TestRunner.ts +265 -0
- package/src/core/testing/agent-cli-tests.ts +538 -0
- package/src/core/testing/index.ts +11 -0
- package/src/core/tools/ToolRegistry.d.ts.map +1 -0
- package/src/core/tools/ToolRegistry.test.ts +206 -0
- package/src/core/tools/ToolRegistry.ts +260 -0
- package/src/core/tools/impl/EditFileTool.d.ts.map +1 -0
- package/src/core/tools/impl/EditFileTool.ts +97 -0
- package/src/core/tools/impl/ListDirectoryTool.d.ts.map +1 -0
- package/src/core/tools/impl/ListDirectoryTool.ts +142 -0
- package/src/core/tools/impl/MemoryTool.d.ts.map +1 -0
- package/src/core/tools/impl/MemoryTool.ts +102 -0
- package/src/core/tools/impl/ReadFileTool.d.ts.map +1 -0
- package/src/core/tools/impl/ReadFileTool.ts +58 -0
- package/src/core/tools/impl/SearchContentTool.d.ts.map +1 -0
- package/src/core/tools/impl/SearchContentTool.ts +94 -0
- package/src/core/tools/impl/SearchFileTool.d.ts.map +1 -0
- package/src/core/tools/impl/SearchFileTool.ts +61 -0
- package/src/core/tools/impl/ShellTool.d.ts.map +1 -0
- package/src/core/tools/impl/ShellTool.ts +118 -0
- package/src/core/tools/impl/TaskTool.d.ts.map +1 -0
- package/src/core/tools/impl/TaskTool.ts +207 -0
- package/src/core/tools/impl/TodoTool.d.ts.map +1 -0
- package/src/core/tools/impl/TodoTool.ts +122 -0
- package/src/core/tools/impl/WebFetchTool.d.ts.map +1 -0
- package/src/core/tools/impl/WebFetchTool.ts +103 -0
- package/src/core/tools/impl/WebSearchTool.d.ts.map +1 -0
- package/src/core/tools/impl/WebSearchTool.ts +89 -0
- package/src/core/tools/impl/WriteFileTool.d.ts.map +1 -0
- package/src/core/tools/impl/WriteFileTool.ts +49 -0
- package/src/core/tools/impl/index.d.ts.map +1 -0
- package/src/core/tools/impl/index.ts +16 -0
- package/src/core/tools/index.d.ts.map +1 -0
- package/src/core/tools/index.ts +7 -0
- package/src/core/tools/schemas/execution.d.ts.map +1 -0
- package/src/core/tools/schemas/execution.ts +42 -0
- package/src/core/tools/schemas/file.d.ts.map +1 -0
- package/src/core/tools/schemas/file.ts +119 -0
- package/src/core/tools/schemas/index.d.ts.map +1 -0
- package/src/core/tools/schemas/index.ts +11 -0
- package/src/core/tools/schemas/memory.d.ts.map +1 -0
- package/src/core/tools/schemas/memory.ts +52 -0
- package/src/core/tools/schemas/orchestration.d.ts.map +1 -0
- package/src/core/tools/schemas/orchestration.ts +44 -0
- package/src/core/tools/schemas/search.d.ts.map +1 -0
- package/src/core/tools/schemas/search.ts +112 -0
- package/src/core/tools/schemas/todo.d.ts.map +1 -0
- package/src/core/tools/schemas/todo.ts +32 -0
- package/src/core/tools/schemas/web.d.ts.map +1 -0
- package/src/core/tools/schemas/web.ts +86 -0
- package/src/core/types/config.d.ts.map +1 -0
- package/src/core/types/config.ts +200 -0
- package/src/core/types/errors.d.ts.map +1 -0
- package/src/core/types/errors.ts +204 -0
- package/src/core/types/index.d.ts.map +1 -0
- package/src/core/types/index.ts +8 -0
- package/src/core/types/session.d.ts.map +1 -0
- package/src/core/types/session.ts +216 -0
- package/src/core/types/tools.d.ts.map +1 -0
- package/src/core/types/tools.ts +157 -0
- package/src/core/utils/CheckpointManager.d.ts.map +1 -0
- package/src/core/utils/CheckpointManager.ts +327 -0
- package/src/core/utils/Logger.d.ts.map +1 -0
- package/src/core/utils/Logger.ts +98 -0
- package/src/core/utils/RetryManager.ts +471 -0
- package/src/core/utils/TokenCounter.d.ts.map +1 -0
- package/src/core/utils/TokenCounter.ts +414 -0
- package/src/core/utils/VectorMemoryStore.ts +440 -0
- package/src/core/utils/helpers.d.ts.map +1 -0
- package/src/core/utils/helpers.ts +89 -0
- package/src/core/utils/index.d.ts.map +1 -0
- package/src/core/utils/index.ts +19 -0
|
@@ -0,0 +1,257 @@
|
|
|
1
|
+
// ============================================================================
|
|
2
|
+
// SkillInstaller - Install skills from GitHub repositories
|
|
3
|
+
// ============================================================================
|
|
4
|
+
|
|
5
|
+
import * as fs from 'node:fs';
|
|
6
|
+
import * as path from 'node:path';
|
|
7
|
+
import * as os from 'node:os';
|
|
8
|
+
import { execSync } from 'node:child_process';
|
|
9
|
+
|
|
10
|
+
export interface SkillInstallOptions {
|
|
11
|
+
/** GitHub repository URL or shorthand (e.g., "obra/superpowers") */
|
|
12
|
+
source: string;
|
|
13
|
+
/** Target directory for installation (default: ~/.nova/skills/) */
|
|
14
|
+
targetDir?: string;
|
|
15
|
+
/** Specific skill names to install (default: all) */
|
|
16
|
+
skills?: string[];
|
|
17
|
+
/** Force overwrite existing skills */
|
|
18
|
+
force?: boolean;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export interface InstalledSkill {
|
|
22
|
+
name: string;
|
|
23
|
+
path: string;
|
|
24
|
+
source: string;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Install skills from GitHub repositories
|
|
29
|
+
*
|
|
30
|
+
* Supports formats:
|
|
31
|
+
* - "obra/superpowers" -> https://github.com/obra/superpowers
|
|
32
|
+
* - "https://github.com/obra/superpowers"
|
|
33
|
+
* - Full GitHub URL
|
|
34
|
+
*/
|
|
35
|
+
export class SkillInstaller {
|
|
36
|
+
private baseDir: string;
|
|
37
|
+
|
|
38
|
+
constructor(baseDir?: string) {
|
|
39
|
+
this.baseDir = baseDir || path.join(os.homedir(), '.nova', 'skills');
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Install skills from a GitHub repository
|
|
44
|
+
*/
|
|
45
|
+
async install(options: SkillInstallOptions): Promise<InstalledSkill[]> {
|
|
46
|
+
const { source, targetDir, skills: specificSkills, force } = options;
|
|
47
|
+
const target = targetDir || this.baseDir;
|
|
48
|
+
|
|
49
|
+
// Parse GitHub URL
|
|
50
|
+
const repoUrl = this.parseGitHubUrl(source);
|
|
51
|
+
const repoName = repoUrl.split('/').pop() || 'unknown';
|
|
52
|
+
|
|
53
|
+
console.log(`Installing skills from ${repoUrl}...`);
|
|
54
|
+
|
|
55
|
+
// Create temp directory for cloning
|
|
56
|
+
const tempDir = path.join(os.tmpdir(), `nova-skills-${Date.now()}`);
|
|
57
|
+
|
|
58
|
+
try {
|
|
59
|
+
// Clone repository
|
|
60
|
+
console.log(` Cloning repository...`);
|
|
61
|
+
execSync(`git clone --depth 1 ${repoUrl} "${tempDir}"`, { stdio: 'pipe' });
|
|
62
|
+
|
|
63
|
+
// Find skills directory
|
|
64
|
+
const skillsDir = this.findSkillsDirectory(tempDir);
|
|
65
|
+
if (!skillsDir) {
|
|
66
|
+
throw new Error(`No skills directory found in ${repoName}`);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// Get all skills
|
|
70
|
+
const availableSkills = this.listSkills(skillsDir);
|
|
71
|
+
const toInstall = specificSkills
|
|
72
|
+
? availableSkills.filter(s => specificSkills.includes(s))
|
|
73
|
+
: availableSkills;
|
|
74
|
+
|
|
75
|
+
if (toInstall.length === 0) {
|
|
76
|
+
throw new Error(`No matching skills found`);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// Install each skill
|
|
80
|
+
const installed: InstalledSkill[] = [];
|
|
81
|
+
for (const skillName of toInstall) {
|
|
82
|
+
const srcPath = path.join(skillsDir, skillName);
|
|
83
|
+
const destPath = path.join(target, skillName);
|
|
84
|
+
|
|
85
|
+
// Check if exists
|
|
86
|
+
if (fs.existsSync(destPath) && !force) {
|
|
87
|
+
console.log(` ⚠ ${skillName} already exists, use --force to overwrite`);
|
|
88
|
+
continue;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// Copy skill
|
|
92
|
+
this.copySkill(srcPath, destPath);
|
|
93
|
+
installed.push({ name: skillName, path: destPath, source: repoUrl });
|
|
94
|
+
console.log(` ✓ Installed: ${skillName}`);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
return installed;
|
|
98
|
+
} finally {
|
|
99
|
+
// Cleanup temp directory
|
|
100
|
+
this.rmrf(tempDir);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Parse various Git repository URL formats (GitHub, Gitee, etc.)
|
|
106
|
+
*/
|
|
107
|
+
private parseGitHubUrl(source: string): string {
|
|
108
|
+
// Already a full URL (GitHub, Gitee, GitLab, etc.)
|
|
109
|
+
if (source.startsWith('https://') || source.startsWith('git@')) {
|
|
110
|
+
return source;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// Gitee shorthand: "gitee:owner/repo"
|
|
114
|
+
if (source.startsWith('gitee:')) {
|
|
115
|
+
const repoPath = source.substring(6); // Remove 'gitee:'
|
|
116
|
+
return `https://gitee.com/${repoPath}.git`;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// GitHub shorthand: "owner/repo"
|
|
120
|
+
if (source.match(/^[\w-]+\/[\w-]+$/)) {
|
|
121
|
+
return `https://github.com/${source}.git`;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// Assume it's a GitHub shorthand
|
|
125
|
+
return `https://github.com/${source}.git`;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Find the skills directory in a repository
|
|
130
|
+
*/
|
|
131
|
+
private findSkillsDirectory(repoDir: string): string | null {
|
|
132
|
+
// Common skill directory names
|
|
133
|
+
const candidates = ['skills', '.claude/skills', 'plugins', '.skills'];
|
|
134
|
+
|
|
135
|
+
for (const name of candidates) {
|
|
136
|
+
const fullPath = path.join(repoDir, name);
|
|
137
|
+
if (fs.existsSync(fullPath) && fs.statSync(fullPath).isDirectory()) {
|
|
138
|
+
// Verify it contains SKILL.md files
|
|
139
|
+
const entries = fs.readdirSync(fullPath);
|
|
140
|
+
const hasSkills = entries.some(entry => {
|
|
141
|
+
const skillPath = path.join(fullPath, entry);
|
|
142
|
+
return fs.statSync(skillPath).isDirectory() &&
|
|
143
|
+
fs.existsSync(path.join(skillPath, 'SKILL.md'));
|
|
144
|
+
});
|
|
145
|
+
if (hasSkills) return fullPath;
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
return null;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* List all skill directories
|
|
154
|
+
*/
|
|
155
|
+
private listSkills(skillsDir: string): string[] {
|
|
156
|
+
return fs.readdirSync(skillsDir).filter(entry => {
|
|
157
|
+
const skillPath = path.join(skillsDir, entry);
|
|
158
|
+
return fs.statSync(skillPath).isDirectory() &&
|
|
159
|
+
fs.existsSync(path.join(skillPath, 'SKILL.md'));
|
|
160
|
+
});
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* Copy a skill directory
|
|
165
|
+
*/
|
|
166
|
+
private copySkill(src: string, dest: string): void {
|
|
167
|
+
// Create destination
|
|
168
|
+
fs.mkdirSync(dest, { recursive: true });
|
|
169
|
+
|
|
170
|
+
// Copy all files
|
|
171
|
+
const entries = fs.readdirSync(src, { withFileTypes: true });
|
|
172
|
+
for (const entry of entries) {
|
|
173
|
+
const srcPath = path.join(src, entry.name);
|
|
174
|
+
const destPath = path.join(dest, entry.name);
|
|
175
|
+
|
|
176
|
+
if (entry.isDirectory()) {
|
|
177
|
+
this.copySkill(srcPath, destPath);
|
|
178
|
+
} else {
|
|
179
|
+
fs.copyFileSync(srcPath, destPath);
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
/**
|
|
185
|
+
* Recursive delete
|
|
186
|
+
*/
|
|
187
|
+
private rmrf(dir: string): void {
|
|
188
|
+
if (!fs.existsSync(dir)) return;
|
|
189
|
+
|
|
190
|
+
const entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
191
|
+
for (const entry of entries) {
|
|
192
|
+
const fullPath = path.join(dir, entry.name);
|
|
193
|
+
if (entry.isDirectory()) {
|
|
194
|
+
this.rmrf(fullPath);
|
|
195
|
+
} else {
|
|
196
|
+
fs.unlinkSync(fullPath);
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
fs.rmdirSync(dir);
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
/**
|
|
203
|
+
* List installed skills
|
|
204
|
+
*/
|
|
205
|
+
listInstalled(): string[] {
|
|
206
|
+
if (!fs.existsSync(this.baseDir)) return [];
|
|
207
|
+
return fs.readdirSync(this.baseDir).filter(name => {
|
|
208
|
+
return fs.existsSync(path.join(this.baseDir, name, 'SKILL.md'));
|
|
209
|
+
});
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
/**
|
|
213
|
+
* Uninstall a skill
|
|
214
|
+
*/
|
|
215
|
+
uninstall(skillName: string): boolean {
|
|
216
|
+
const skillPath = path.join(this.baseDir, skillName);
|
|
217
|
+
if (!fs.existsSync(skillPath)) return false;
|
|
218
|
+
this.rmrf(skillPath);
|
|
219
|
+
return true;
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
/**
|
|
224
|
+
* Popular skill repositories
|
|
225
|
+
*/
|
|
226
|
+
export const POPULAR_SKILL_REPOS = {
|
|
227
|
+
superpowers: {
|
|
228
|
+
// Default to Gitee mirror for better accessibility in China
|
|
229
|
+
url: 'gitee:anderson2/superpowers',
|
|
230
|
+
description: 'Agentic skills framework - TDD, debugging, code review, planning',
|
|
231
|
+
skills: [
|
|
232
|
+
'brainstorming',
|
|
233
|
+
'writing-plans',
|
|
234
|
+
'executing-plans',
|
|
235
|
+
'test-driven-development',
|
|
236
|
+
'systematic-debugging',
|
|
237
|
+
'requesting-code-review',
|
|
238
|
+
'receiving-code-review',
|
|
239
|
+
'using-git-worktrees',
|
|
240
|
+
'finishing-a-development-branch',
|
|
241
|
+
'subagent-driven-development',
|
|
242
|
+
'verification-before-completion',
|
|
243
|
+
'writing-skills',
|
|
244
|
+
],
|
|
245
|
+
},
|
|
246
|
+
};
|
|
247
|
+
|
|
248
|
+
/**
|
|
249
|
+
* Quick install function
|
|
250
|
+
*/
|
|
251
|
+
export async function installSuperpowers(baseDir?: string): Promise<InstalledSkill[]> {
|
|
252
|
+
const installer = new SkillInstaller(baseDir);
|
|
253
|
+
return installer.install({
|
|
254
|
+
source: 'gitee:anderson2/superpowers',
|
|
255
|
+
force: false,
|
|
256
|
+
});
|
|
257
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"SkillRegistry.d.ts","sourceRoot":"","sources":["SkillRegistry.ts"],"names":[],"mappings":"AAUA,MAAM,WAAW,aAAa;IAC5B,8BAA8B;IAC9B,IAAI,EAAE,MAAM,CAAC;IACb,iCAAiC;IACjC,WAAW,EAAE,MAAM,CAAC;IACpB,aAAa;IACb,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,uBAAuB;IACvB,OAAO,EAAE,MAAM,CAAC;IAChB,yCAAyC;IACzC,IAAI,EAAE,MAAM,EAAE,CAAC;IACf,8CAA8C;IAC9C,SAAS,CAAC,EAAE,MAAM,EAAE,CAAC;IACrB,oCAAoC;IACpC,aAAa,CAAC,EAAE,MAAM,EAAE,CAAC;IACzB,4CAA4C;IAC5C,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,oBAAoB;IACpB,SAAS,EAAE,MAAM,CAAC;IAClB,uBAAuB;IACvB,SAAS,EAAE,MAAM,CAAC;IAClB,yBAAyB;IACzB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,uBAAuB;IACvB,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,eAAe;IAC9B,qBAAqB;IACrB,QAAQ,EAAE,aAAa,CAAC;IACxB,uDAAuD;IACvD,OAAO,EAAE,MAAM,CAAC;IAChB,8BAA8B;IAC9B,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACjC,iCAAiC;IACjC,UAAU,CAAC,EAAE,MAAM,EAAE,CAAC;CACvB;AAED,MAAM,WAAW,iBAAiB;IAChC,0BAA0B;IAC1B,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,qBAAqB;IACrB,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC;IAChB,uCAAuC;IACvC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,oCAAoC;IACpC,oBAAoB,CAAC,EAAE,OAAO,CAAC;IAC/B,sBAAsB;IACtB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAID,qBAAa,aAAa;IACxB,OAAO,CAAC,SAAS,CAAS;IAC1B,OAAO,CAAC,KAAK,CAAsC;IACnD,OAAO,CAAC,WAAW,CAAS;gBAEhB,SAAS,CAAC,EAAE,MAAM;IAI9B;;OAEG;IACG,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC;IAyBjC;;OAEG;IACG,QAAQ,CAAC,KAAK,EAAE,eAAe,GAAG,OAAO,CAAC,IAAI,CAAC;IAuBrD;;OAEG;IACG,GAAG,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,eAAe,GAAG,IAAI,CAAC;IAYxD;;OAEG;IACG,MAAM,CAAC,MAAM,EAAE,iBAAiB,GAAG,OAAO,CAAC,eAAe,EAAE,CAAC;IA4DnE;;OAEG;IACG,IAAI,IAAI,OAAO,CAAC,eAAe,EAAE,CAAC;IAOxC;;OAEG;IACG,MAAM,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAa5C;;OAEG;IACH,YAAY,IAAI,MAAM;IAItB;;OAEG;IACH,OAAO,CAAC,YAAY;IAkBpB;;OAEG;YACW,SAAS;IAkCvB;;OAEG;IACH,OAAO,CAAC,YAAY;IAwDpB;;OAEG;YACW,eAAe;CAG9B"}
|
|
@@ -0,0 +1,361 @@
|
|
|
1
|
+
// ============================================================================
|
|
2
|
+
// SkillRegistry - Register, discover, and manage agent skills
|
|
3
|
+
// Reference: WorkBuddy skill system with metadata tracking
|
|
4
|
+
// ============================================================================
|
|
5
|
+
|
|
6
|
+
import { readFile, writeFile, mkdir, readdir, stat, rm } from 'node:fs/promises';
|
|
7
|
+
import { join, resolve, dirname } from 'node:path';
|
|
8
|
+
|
|
9
|
+
// --- Types ---
|
|
10
|
+
|
|
11
|
+
export interface SkillMetadata {
|
|
12
|
+
/** Unique skill identifier */
|
|
13
|
+
name: string;
|
|
14
|
+
/** Human-readable description */
|
|
15
|
+
description: string;
|
|
16
|
+
/** Author */
|
|
17
|
+
author?: string;
|
|
18
|
+
/** Version (semver) */
|
|
19
|
+
version: string;
|
|
20
|
+
/** Tags for categorization and search */
|
|
21
|
+
tags: string[];
|
|
22
|
+
/** Supported model providers (empty = all) */
|
|
23
|
+
providers?: string[];
|
|
24
|
+
/** Required tools for this skill */
|
|
25
|
+
requiredTools?: string[];
|
|
26
|
+
/** Whether this skill was auto-generated */
|
|
27
|
+
autoGenerated?: boolean;
|
|
28
|
+
/** Creation date */
|
|
29
|
+
createdAt: string;
|
|
30
|
+
/** Last update date */
|
|
31
|
+
updatedAt: string;
|
|
32
|
+
/** File size in bytes */
|
|
33
|
+
sizeBytes?: number;
|
|
34
|
+
/** Reviewed at date */
|
|
35
|
+
reviewedAt?: string;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export interface SkillDefinition {
|
|
39
|
+
/** Skill metadata */
|
|
40
|
+
metadata: SkillMetadata;
|
|
41
|
+
/** SKILL.md content (the skill prompt/instructions) */
|
|
42
|
+
content: string;
|
|
43
|
+
/** Associated script files */
|
|
44
|
+
scripts?: Record<string, string>;
|
|
45
|
+
/** Associated reference files */
|
|
46
|
+
references?: string[];
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export interface SkillSearchParams {
|
|
50
|
+
/** Search query string */
|
|
51
|
+
query?: string;
|
|
52
|
+
/** Filter by tags */
|
|
53
|
+
tags?: string[];
|
|
54
|
+
/** Filter by provider compatibility */
|
|
55
|
+
provider?: string;
|
|
56
|
+
/** Include auto-generated skills */
|
|
57
|
+
includeAutoGenerated?: boolean;
|
|
58
|
+
/** Maximum results */
|
|
59
|
+
limit?: number;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// --- SkillRegistry ---
|
|
63
|
+
|
|
64
|
+
export class SkillRegistry {
|
|
65
|
+
private skillsDir: string;
|
|
66
|
+
private cache = new Map<string, SkillDefinition>();
|
|
67
|
+
private initialized = false;
|
|
68
|
+
|
|
69
|
+
constructor(skillsDir?: string) {
|
|
70
|
+
this.skillsDir = skillsDir || join(process.env.HOME || process.env.USERPROFILE || '~', '.nova-cli', 'skills');
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Initialize the registry by loading all skills from disk.
|
|
75
|
+
*/
|
|
76
|
+
async initialize(): Promise<void> {
|
|
77
|
+
try {
|
|
78
|
+
await mkdir(this.skillsDir, { recursive: true });
|
|
79
|
+
} catch {
|
|
80
|
+
// Directory may already exist
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
const entries = await readdir(this.skillsDir, { withFileTypes: true });
|
|
84
|
+
for (const entry of entries) {
|
|
85
|
+
if (entry.isDirectory()) {
|
|
86
|
+
const skillPath = join(this.skillsDir, entry.name);
|
|
87
|
+
try {
|
|
88
|
+
const skill = await this.loadSkill(entry.name);
|
|
89
|
+
if (skill) {
|
|
90
|
+
this.cache.set(entry.name, skill);
|
|
91
|
+
}
|
|
92
|
+
} catch {
|
|
93
|
+
// Skip invalid skills
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
this.initialized = true;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Register a new skill.
|
|
103
|
+
*/
|
|
104
|
+
async register(skill: SkillDefinition): Promise<void> {
|
|
105
|
+
const skillDir = join(this.skillsDir, skill.metadata.name);
|
|
106
|
+
await mkdir(skillDir, { recursive: true });
|
|
107
|
+
|
|
108
|
+
// Write SKILL.md
|
|
109
|
+
const skillMdPath = join(skillDir, 'SKILL.md');
|
|
110
|
+
const fullContent = this.buildSkillMd(skill.metadata, skill.content);
|
|
111
|
+
await writeFile(skillMdPath, fullContent, 'utf-8');
|
|
112
|
+
|
|
113
|
+
// Write script files
|
|
114
|
+
if (skill.scripts) {
|
|
115
|
+
const scriptsDir = join(skillDir, 'scripts');
|
|
116
|
+
await mkdir(scriptsDir, { recursive: true });
|
|
117
|
+
for (const [filename, content] of Object.entries(skill.scripts)) {
|
|
118
|
+
await writeFile(join(scriptsDir, filename), content, 'utf-8');
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// Update cache
|
|
123
|
+
skill.metadata.updatedAt = new Date().toISOString();
|
|
124
|
+
this.cache.set(skill.metadata.name, skill);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Get a skill by name.
|
|
129
|
+
*/
|
|
130
|
+
async get(name: string): Promise<SkillDefinition | null> {
|
|
131
|
+
if (!this.initialized) {
|
|
132
|
+
await this.initialize();
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
if (this.cache.has(name)) {
|
|
136
|
+
return this.cache.get(name)!;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
return this.loadSkill(name);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Search for skills matching the given criteria.
|
|
144
|
+
*/
|
|
145
|
+
async search(params: SkillSearchParams): Promise<SkillDefinition[]> {
|
|
146
|
+
if (!this.initialized) {
|
|
147
|
+
await this.initialize();
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
let results = Array.from(this.cache.values());
|
|
151
|
+
|
|
152
|
+
// Filter by auto-generated
|
|
153
|
+
if (!params.includeAutoGenerated) {
|
|
154
|
+
results = results.filter((s) => !s.metadata.autoGenerated);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// Filter by provider
|
|
158
|
+
if (params.provider) {
|
|
159
|
+
results = results.filter(
|
|
160
|
+
(s) =>
|
|
161
|
+
!s.metadata.providers ||
|
|
162
|
+
s.metadata.providers.length === 0 ||
|
|
163
|
+
s.metadata.providers.includes(params.provider!)
|
|
164
|
+
);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
// Filter by tags
|
|
168
|
+
if (params.tags && params.tags.length > 0) {
|
|
169
|
+
results = results.filter((s) =>
|
|
170
|
+
params.tags!.some((tag) => s.metadata.tags.includes(tag))
|
|
171
|
+
);
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
// Filter by query
|
|
175
|
+
if (params.query) {
|
|
176
|
+
const query = params.query.toLowerCase();
|
|
177
|
+
results = results.filter(
|
|
178
|
+
(s) =>
|
|
179
|
+
s.metadata.name.toLowerCase().includes(query) ||
|
|
180
|
+
s.metadata.description.toLowerCase().includes(query) ||
|
|
181
|
+
s.metadata.tags.some((t) => t.toLowerCase().includes(query))
|
|
182
|
+
);
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
// Sort by relevance (name match first, then description match)
|
|
186
|
+
if (params.query) {
|
|
187
|
+
const query = params.query.toLowerCase();
|
|
188
|
+
results.sort((a, b) => {
|
|
189
|
+
const aNameMatch = a.metadata.name.toLowerCase().includes(query) ? 2 : 0;
|
|
190
|
+
const bNameMatch = b.metadata.name.toLowerCase().includes(query) ? 2 : 0;
|
|
191
|
+
const aDescMatch = a.metadata.description.toLowerCase().includes(query) ? 1 : 0;
|
|
192
|
+
const bDescMatch = b.metadata.description.toLowerCase().includes(query) ? 1 : 0;
|
|
193
|
+
return (bNameMatch + bDescMatch) - (aNameMatch + aDescMatch);
|
|
194
|
+
});
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
// Limit results
|
|
198
|
+
if (params.limit) {
|
|
199
|
+
results = results.slice(0, params.limit);
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
return results;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
/**
|
|
206
|
+
* List all registered skills.
|
|
207
|
+
*/
|
|
208
|
+
async list(): Promise<SkillDefinition[]> {
|
|
209
|
+
if (!this.initialized) {
|
|
210
|
+
await this.initialize();
|
|
211
|
+
}
|
|
212
|
+
return Array.from(this.cache.values());
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
/**
|
|
216
|
+
* Remove a skill.
|
|
217
|
+
*/
|
|
218
|
+
async remove(name: string): Promise<boolean> {
|
|
219
|
+
const skillDir = join(this.skillsDir, name);
|
|
220
|
+
|
|
221
|
+
try {
|
|
222
|
+
await rm(skillDir, { recursive: true, force: true });
|
|
223
|
+
} catch {
|
|
224
|
+
return false;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
this.cache.delete(name);
|
|
228
|
+
return true;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
/**
|
|
232
|
+
* Get the skills directory path.
|
|
233
|
+
*/
|
|
234
|
+
getSkillsDir(): string {
|
|
235
|
+
return this.skillsDir;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
/**
|
|
239
|
+
* Build the full SKILL.md content with metadata header.
|
|
240
|
+
*/
|
|
241
|
+
private buildSkillMd(metadata: SkillMetadata, content: string): string {
|
|
242
|
+
const header = `---
|
|
243
|
+
name: ${metadata.name}
|
|
244
|
+
description: ${metadata.description}
|
|
245
|
+
version: ${metadata.version}
|
|
246
|
+
author: ${metadata.author || 'unknown'}
|
|
247
|
+
tags: ${metadata.tags.join(', ')}
|
|
248
|
+
${metadata.providers ? `providers: ${metadata.providers.join(', ')}` : ''}
|
|
249
|
+
${metadata.requiredTools ? `requiredTools: ${metadata.requiredTools.join(', ')}` : ''}
|
|
250
|
+
${metadata.autoGenerated ? 'autoGenerated: true' : ''}
|
|
251
|
+
createdAt: ${metadata.createdAt}
|
|
252
|
+
updatedAt: ${metadata.updatedAt}
|
|
253
|
+
---
|
|
254
|
+
|
|
255
|
+
`;
|
|
256
|
+
return header + content;
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
/**
|
|
260
|
+
* Load a skill from disk.
|
|
261
|
+
*/
|
|
262
|
+
private async loadSkill(name: string): Promise<SkillDefinition | null> {
|
|
263
|
+
const skillDir = join(this.skillsDir, name);
|
|
264
|
+
const skillMdPath = join(skillDir, 'SKILL.md');
|
|
265
|
+
|
|
266
|
+
try {
|
|
267
|
+
const rawContent = await readFile(skillMdPath, 'utf-8');
|
|
268
|
+
const { metadata, content } = this.parseSkillMd(rawContent);
|
|
269
|
+
|
|
270
|
+
// Load scripts
|
|
271
|
+
const scripts: Record<string, string> = {};
|
|
272
|
+
const scriptsDir = join(skillDir, 'scripts');
|
|
273
|
+
try {
|
|
274
|
+
const entries = await readdir(scriptsDir);
|
|
275
|
+
for (const entry of entries) {
|
|
276
|
+
if (entry.endsWith('.ts') || entry.endsWith('.js') || entry.endsWith('.sh') || entry.endsWith('.py')) {
|
|
277
|
+
scripts[entry] = await readFile(join(scriptsDir, entry), 'utf-8');
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
} catch {
|
|
281
|
+
// No scripts directory
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
const fileStat = await stat(skillMdPath);
|
|
285
|
+
|
|
286
|
+
return {
|
|
287
|
+
metadata: { ...metadata, sizeBytes: fileStat.size },
|
|
288
|
+
content,
|
|
289
|
+
scripts: Object.keys(scripts).length > 0 ? scripts : undefined,
|
|
290
|
+
};
|
|
291
|
+
} catch {
|
|
292
|
+
return null;
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
/**
|
|
297
|
+
* Parse SKILL.md content into metadata and body.
|
|
298
|
+
*/
|
|
299
|
+
private parseSkillMd(rawContent: string): {
|
|
300
|
+
metadata: SkillMetadata;
|
|
301
|
+
content: string;
|
|
302
|
+
} {
|
|
303
|
+
const frontmatterMatch = rawContent.match(/^---\n([\s\S]*?)\n---\n([\s\S]*)$/);
|
|
304
|
+
if (!frontmatterMatch) {
|
|
305
|
+
return {
|
|
306
|
+
metadata: {
|
|
307
|
+
name: 'unknown',
|
|
308
|
+
description: 'No description',
|
|
309
|
+
version: '0.0.0',
|
|
310
|
+
tags: [],
|
|
311
|
+
createdAt: new Date().toISOString(),
|
|
312
|
+
updatedAt: new Date().toISOString(),
|
|
313
|
+
},
|
|
314
|
+
content: rawContent,
|
|
315
|
+
};
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
const frontmatter = frontmatterMatch[1];
|
|
319
|
+
const content = frontmatterMatch[2];
|
|
320
|
+
|
|
321
|
+
const parseField = (field: string): string => {
|
|
322
|
+
const match = frontmatter.match(new RegExp(`${field}:\\s*(.+)`));
|
|
323
|
+
return match ? match[1].trim() : '';
|
|
324
|
+
};
|
|
325
|
+
|
|
326
|
+
const parseArray = (field: string): string[] => {
|
|
327
|
+
const raw = parseField(field);
|
|
328
|
+
if (!raw) return [];
|
|
329
|
+
return raw.split(',').map((s) => s.trim()).filter(Boolean);
|
|
330
|
+
};
|
|
331
|
+
|
|
332
|
+
const parseBool = (field: string): boolean => {
|
|
333
|
+
const raw = parseField(field);
|
|
334
|
+
return raw.toLowerCase() === 'true';
|
|
335
|
+
};
|
|
336
|
+
|
|
337
|
+
const name = parseField('name') || 'unknown';
|
|
338
|
+
return {
|
|
339
|
+
metadata: {
|
|
340
|
+
name,
|
|
341
|
+
description: parseField('description'),
|
|
342
|
+
version: parseField('version') || '0.0.0',
|
|
343
|
+
author: parseField('author') || undefined,
|
|
344
|
+
tags: parseArray('tags'),
|
|
345
|
+
providers: parseArray('providers').length > 0 ? parseArray('providers') : undefined,
|
|
346
|
+
requiredTools: parseArray('requiredTools').length > 0 ? parseArray('requiredTools') : undefined,
|
|
347
|
+
autoGenerated: parseBool('autoGenerated'),
|
|
348
|
+
createdAt: parseField('createdAt') || new Date().toISOString(),
|
|
349
|
+
updatedAt: parseField('updatedAt') || new Date().toISOString(),
|
|
350
|
+
},
|
|
351
|
+
content,
|
|
352
|
+
};
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
/**
|
|
356
|
+
* Recursively remove a directory.
|
|
357
|
+
*/
|
|
358
|
+
private async removeDirectory(dirPath: string): Promise<void> {
|
|
359
|
+
await rm(dirPath, { recursive: true, force: true });
|
|
360
|
+
}
|
|
361
|
+
}
|