deeper-cli 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/README.md +254 -0
- package/dist/cli/index.d.ts +1 -0
- package/dist/cli/index.js +12067 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/index.d.ts +415 -0
- package/dist/index.js +1599 -0
- package/dist/index.js.map +1 -0
- package/docs/superpowers/plans/2026-05-14-deepercode-implementation.md +24 -0
- package/docs/superpowers/plans/2026-05-14-deepercode-plan.md +1248 -0
- package/docs/superpowers/specs/2026-05-14-deepercode-design.md +560 -0
- package/package.json +60 -0
- package/src/cli/bootstrap.ts +69 -0
- package/src/cli/chat-repl.ts +932 -0
- package/src/cli/commands/chat.ts +39 -0
- package/src/cli/commands/chat.tsx +39 -0
- package/src/cli/commands/config.ts +133 -0
- package/src/cli/commands/mcp.ts +172 -0
- package/src/cli/commands/run.ts +147 -0
- package/src/cli/commands/skill.ts +152 -0
- package/src/cli/index.ts +184 -0
- package/src/core/bugscan.ts +145 -0
- package/src/core/config.ts +285 -0
- package/src/core/constants.ts +49 -0
- package/src/core/eventbus.ts +202 -0
- package/src/core/logger.ts +109 -0
- package/src/core/storage.ts +96 -0
- package/src/index.ts +26 -0
- package/src/mcp/ConfigLoader.ts +74 -0
- package/src/mcp/MCPClient.ts +326 -0
- package/src/mcp/ResourceAdapter.ts +58 -0
- package/src/mcp/SSETransport.ts +133 -0
- package/src/mcp/StdioTransport.ts +116 -0
- package/src/mcp/ToolAdapter.ts +71 -0
- package/src/mcp/types.ts +58 -0
- package/src/memory/xmemory.ts +275 -0
- package/src/model/DeepSeekClient.ts +292 -0
- package/src/model/MessageBuilder.ts +155 -0
- package/src/model/RetryManager.ts +82 -0
- package/src/model/StreamHandler.ts +158 -0
- package/src/model/types.ts +86 -0
- package/src/skills/SkillCreator.ts +153 -0
- package/src/skills/SkillEngine.ts +158 -0
- package/src/skills/SkillExecutor.ts +107 -0
- package/src/skills/SkillLoader.ts +182 -0
- package/src/skills/SkillRegistry.ts +73 -0
- package/src/skills/SkillTrigger.ts +82 -0
- package/src/skills/types.ts +28 -0
- package/src/tools/ToolExecutor.ts +103 -0
- package/src/tools/ToolRegistry.ts +71 -0
- package/src/tools/ToolValidator.ts +103 -0
- package/src/tools/builtin/ai/context_summarize.ts +76 -0
- package/src/tools/builtin/ai/memory_store.ts +86 -0
- package/src/tools/builtin/ai/prompt_template.ts +71 -0
- package/src/tools/builtin/ai/skill_create.ts +53 -0
- package/src/tools/builtin/ai/subagent.ts +39 -0
- package/src/tools/builtin/ai/todo_manager.ts +157 -0
- package/src/tools/builtin/ai/token_count.ts +196 -0
- package/src/tools/builtin/ai/tool_create.ts +52 -0
- package/src/tools/builtin/code/analyze_deps.ts +72 -0
- package/src/tools/builtin/code/bug_scan.ts +80 -0
- package/src/tools/builtin/code/code_metrics.ts +111 -0
- package/src/tools/builtin/code/extract_function.ts +86 -0
- package/src/tools/builtin/code/format_code.ts +57 -0
- package/src/tools/builtin/code/generate_code.ts +75 -0
- package/src/tools/builtin/code/import_organizer.ts +82 -0
- package/src/tools/builtin/code/lint_code.ts +48 -0
- package/src/tools/builtin/code/parse_ast.ts +86 -0
- package/src/tools/builtin/code/refactor_code.ts +63 -0
- package/src/tools/builtin/code/type_check.ts +48 -0
- package/src/tools/builtin/data/chart_generate.ts +62 -0
- package/src/tools/builtin/data/csv_parse.ts +56 -0
- package/src/tools/builtin/data/data_diff.ts +79 -0
- package/src/tools/builtin/data/data_transform.ts +74 -0
- package/src/tools/builtin/data/data_validate.ts +75 -0
- package/src/tools/builtin/data/json_parse.ts +71 -0
- package/src/tools/builtin/data/template_render.ts +58 -0
- package/src/tools/builtin/data/toml_parse.ts +42 -0
- package/src/tools/builtin/data/xml_parse.ts +79 -0
- package/src/tools/builtin/data/yaml_parse.ts +42 -0
- package/src/tools/builtin/database/db_backup.ts +53 -0
- package/src/tools/builtin/database/db_restore.ts +51 -0
- package/src/tools/builtin/database/db_schema.ts +66 -0
- package/src/tools/builtin/database/nosql_query.ts +50 -0
- package/src/tools/builtin/database/orm_generate.ts +66 -0
- package/src/tools/builtin/database/redis_command.ts +46 -0
- package/src/tools/builtin/database/sql_migrate.ts +55 -0
- package/src/tools/builtin/database/sql_query.ts +60 -0
- package/src/tools/builtin/filesystem/batch_read.ts +56 -0
- package/src/tools/builtin/filesystem/batch_write.ts +67 -0
- package/src/tools/builtin/filesystem/copy_file.ts +36 -0
- package/src/tools/builtin/filesystem/create_dir.ts +30 -0
- package/src/tools/builtin/filesystem/delete_file.ts +30 -0
- package/src/tools/builtin/filesystem/diff_files.ts +47 -0
- package/src/tools/builtin/filesystem/edit_file.ts +47 -0
- package/src/tools/builtin/filesystem/file_info.ts +52 -0
- package/src/tools/builtin/filesystem/glob_find.ts +44 -0
- package/src/tools/builtin/filesystem/list_dir.ts +51 -0
- package/src/tools/builtin/filesystem/merge_files.ts +44 -0
- package/src/tools/builtin/filesystem/move_file.ts +37 -0
- package/src/tools/builtin/filesystem/read_file.ts +55 -0
- package/src/tools/builtin/filesystem/watch_file.ts +33 -0
- package/src/tools/builtin/filesystem/write_file.ts +45 -0
- package/src/tools/builtin/index.ts +244 -0
- package/src/tools/builtin/network/api_call.ts +79 -0
- package/src/tools/builtin/network/browser_action.ts +54 -0
- package/src/tools/builtin/network/check_url.ts +59 -0
- package/src/tools/builtin/network/download_file.ts +64 -0
- package/src/tools/builtin/network/graphql_query.ts +46 -0
- package/src/tools/builtin/network/http_request.ts +61 -0
- package/src/tools/builtin/network/parse_html.ts +101 -0
- package/src/tools/builtin/network/proxy_request.ts +53 -0
- package/src/tools/builtin/network/screenshot_page.ts +58 -0
- package/src/tools/builtin/network/web_fetch.ts +70 -0
- package/src/tools/builtin/network/web_search.ts +128 -0
- package/src/tools/builtin/network/websocket_connect.ts +70 -0
- package/src/tools/builtin/project/build_project.ts +68 -0
- package/src/tools/builtin/project/config_manage.ts +99 -0
- package/src/tools/builtin/project/coverage_report.ts +59 -0
- package/src/tools/builtin/project/docker_manage.ts +90 -0
- package/src/tools/builtin/project/env_manage.ts +88 -0
- package/src/tools/builtin/project/npm_manage.ts +71 -0
- package/src/tools/builtin/project/project_init.ts +59 -0
- package/src/tools/builtin/project/run_test.ts +74 -0
- package/src/tools/builtin/search/codebase_search.ts +76 -0
- package/src/tools/builtin/search/find_definition.ts +84 -0
- package/src/tools/builtin/search/find_references.ts +75 -0
- package/src/tools/builtin/search/fuzzy_find.ts +75 -0
- package/src/tools/builtin/search/grep_search.ts +90 -0
- package/src/tools/builtin/search/regex_find.ts +91 -0
- package/src/tools/builtin/search/search_docs.ts +51 -0
- package/src/tools/builtin/search/search_package.ts +50 -0
- package/src/tools/builtin/search/symbol_search.ts +82 -0
- package/src/tools/builtin/search/text_search.ts +63 -0
- package/src/tools/builtin/security/decrypt_file.ts +54 -0
- package/src/tools/builtin/security/encrypt_file.ts +52 -0
- package/src/tools/builtin/security/hash_generate.ts +48 -0
- package/src/tools/builtin/security/jwt_decode.ts +53 -0
- package/src/tools/builtin/security/secret_scan.ts +82 -0
- package/src/tools/builtin/security/vulnerability_check.ts +71 -0
- package/src/tools/builtin/shell/background_terminal.ts +38 -0
- package/src/tools/builtin/shell/check_status.ts +48 -0
- package/src/tools/builtin/shell/interactive_terminal.ts +31 -0
- package/src/tools/builtin/shell/kill_terminal.ts +29 -0
- package/src/tools/builtin/shell/list_terminals.ts +61 -0
- package/src/tools/builtin/shell/pipe_commands.ts +55 -0
- package/src/tools/builtin/shell/process-pool.ts +150 -0
- package/src/tools/builtin/shell/run_async.ts +73 -0
- package/src/tools/builtin/shell/run_command.ts +60 -0
- package/src/tools/builtin/shell/send_ctrl_keys.ts +43 -0
- package/src/tools/builtin/shell/send_keys.ts +36 -0
- package/src/tools/builtin/shell/send_text.ts +35 -0
- package/src/tools/builtin/shell/shell_script.ts +65 -0
- package/src/tools/builtin/shell/stop_command.ts +40 -0
- package/src/tools/builtin/shell/terminal_resize.ts +31 -0
- package/src/tools/builtin/shell/terminal_screenshot.ts +28 -0
- package/src/tools/builtin/system/log_viewer.ts +89 -0
- package/src/tools/builtin/system/notify_user.ts +55 -0
- package/src/tools/builtin/system/process_list.ts +66 -0
- package/src/tools/builtin/system/resource_monitor.ts +66 -0
- package/src/tools/builtin/system/system_info.ts +41 -0
- package/src/tools/tool-types.ts +97 -0
- package/src/ui/AgentTree.tsx +98 -0
- package/src/ui/App.tsx +46 -0
- package/src/ui/ChatView.tsx +278 -0
- package/src/ui/ConfirmDialog.tsx +68 -0
- package/src/ui/DiffView.tsx +64 -0
- package/src/ui/FilePreview.tsx +59 -0
- package/src/ui/InputBox.tsx +267 -0
- package/src/ui/MessageBubble.tsx +30 -0
- package/src/ui/Spinner.tsx +35 -0
- package/src/ui/StatusBar.tsx +41 -0
- package/src/ui/ToolCallCard.tsx +73 -0
- package/src/ui/ansi.ts +50 -0
- package/src/ui/markdown.ts +238 -0
- package/src/ui/themes/dark.ts +4 -0
- package/src/ui/themes/default.ts +25 -0
- package/src/ui/themes/light.ts +14 -0
- package/tests/unit/BuiltinTools.test.ts +129 -0
- package/tests/unit/BuiltinToolsIntegration.test.ts +111 -0
- package/tests/unit/FilesystemTools.test.ts +211 -0
- package/tests/unit/SkillLoader.test.ts +141 -0
- package/tests/unit/SkillRegistry.test.ts +113 -0
- package/tests/unit/ToolExecutor.test.ts +160 -0
- package/tests/unit/ToolRegistry.test.ts +103 -0
- package/tests/unit/ToolValidator.test.ts +137 -0
- package/tsconfig.json +28 -0
- package/tsup.config.ts +17 -0
- package/vitest.config.ts +20 -0
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
import { SkillRegistry } from './SkillRegistry.js';
|
|
2
|
+
import { SkillLoader } from './SkillLoader.js';
|
|
3
|
+
import { SkillExecutor } from './SkillExecutor.js';
|
|
4
|
+
import { SkillCreator } from './SkillCreator.js';
|
|
5
|
+
import { SkillTrigger } from './SkillTrigger.js';
|
|
6
|
+
import { EventBus, Events } from '../core/eventbus.js';
|
|
7
|
+
import type { Skill, SkillExecutionResult } from './types.js';
|
|
8
|
+
|
|
9
|
+
export class SkillEngine {
|
|
10
|
+
private registry: SkillRegistry;
|
|
11
|
+
private loader: SkillLoader;
|
|
12
|
+
private executor: SkillExecutor;
|
|
13
|
+
private creator: SkillCreator;
|
|
14
|
+
private trigger: SkillTrigger;
|
|
15
|
+
private eventbus: EventBus;
|
|
16
|
+
|
|
17
|
+
constructor(eventbus?: EventBus) {
|
|
18
|
+
this.registry = new SkillRegistry();
|
|
19
|
+
this.loader = new SkillLoader();
|
|
20
|
+
this.executor = new SkillExecutor();
|
|
21
|
+
this.creator = new SkillCreator();
|
|
22
|
+
this.trigger = new SkillTrigger(this.registry);
|
|
23
|
+
this.eventbus = eventbus || new EventBus();
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
getRegistry(): SkillRegistry {
|
|
27
|
+
return this.registry;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
getLoader(): SkillLoader {
|
|
31
|
+
return this.loader;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
getTrigger(): SkillTrigger {
|
|
35
|
+
return this.trigger;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
async loadAll(): Promise<number> {
|
|
39
|
+
const results = await this.loader.loadAll();
|
|
40
|
+
let count = 0;
|
|
41
|
+
|
|
42
|
+
for (const result of results) {
|
|
43
|
+
this.registry.register(result.skill);
|
|
44
|
+
count++;
|
|
45
|
+
this.eventbus.emit(Events.SKILL_LOADED, {
|
|
46
|
+
name: result.skill.meta.name,
|
|
47
|
+
source: result.source,
|
|
48
|
+
hasCode: !!result.skill.code,
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
return count;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
async reload(): Promise<number> {
|
|
56
|
+
this.registry.clear();
|
|
57
|
+
return this.loadAll();
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
async execute(name: string, context: Record<string, unknown>): Promise<SkillExecutionResult> {
|
|
61
|
+
const skill = this.registry.get(name);
|
|
62
|
+
if (!skill) {
|
|
63
|
+
return {
|
|
64
|
+
success: false,
|
|
65
|
+
output: '',
|
|
66
|
+
error: `Skill not found: ${name}`,
|
|
67
|
+
duration: 0,
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const startTime = Date.now();
|
|
72
|
+
try {
|
|
73
|
+
const result = await this.executor.execute(skill, context);
|
|
74
|
+
const duration = Date.now() - startTime;
|
|
75
|
+
|
|
76
|
+
this.eventbus.emit(Events.SKILL_EXECUTED, {
|
|
77
|
+
name: skill.meta.name,
|
|
78
|
+
success: result.success,
|
|
79
|
+
duration,
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
return { ...result, duration };
|
|
83
|
+
} catch (error) {
|
|
84
|
+
const duration = Date.now() - startTime;
|
|
85
|
+
const errMsg = error instanceof Error ? error.message : String(error);
|
|
86
|
+
return {
|
|
87
|
+
success: false,
|
|
88
|
+
output: '',
|
|
89
|
+
error: errMsg,
|
|
90
|
+
duration,
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
async createSkill(
|
|
96
|
+
name: string,
|
|
97
|
+
description: string,
|
|
98
|
+
content: string,
|
|
99
|
+
code?: string,
|
|
100
|
+
): Promise<Skill> {
|
|
101
|
+
const skill = await this.creator.create({
|
|
102
|
+
meta: {
|
|
103
|
+
name,
|
|
104
|
+
description,
|
|
105
|
+
version: '1.0.0',
|
|
106
|
+
author: 'DeeperCode AI',
|
|
107
|
+
triggers: [],
|
|
108
|
+
tools: [],
|
|
109
|
+
dependencies: [],
|
|
110
|
+
},
|
|
111
|
+
content,
|
|
112
|
+
code,
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
this.registry.register(skill);
|
|
116
|
+
|
|
117
|
+
this.eventbus.emit(Events.SKILL_CREATED, {
|
|
118
|
+
name: skill.meta.name,
|
|
119
|
+
description: skill.meta.description,
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
return skill;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
matchTriggers(input: string): Skill[] {
|
|
126
|
+
const matches = this.trigger.match(input);
|
|
127
|
+
for (const skill of matches) {
|
|
128
|
+
this.eventbus.emit(Events.SKILL_TRIGGERED, {
|
|
129
|
+
name: skill.meta.name,
|
|
130
|
+
input: input.slice(0, 100),
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
return matches;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
getSystemPrompt(context?: Record<string, unknown>): string {
|
|
137
|
+
const allSkills = this.registry.getAll();
|
|
138
|
+
if (allSkills.length === 0) return '';
|
|
139
|
+
|
|
140
|
+
let prompt = 'Available Skills:\n\n';
|
|
141
|
+
for (const skill of allSkills) {
|
|
142
|
+
prompt += `## ${skill.meta.name} (v${skill.meta.version})\n`;
|
|
143
|
+
prompt += `${skill.meta.description}\n`;
|
|
144
|
+
|
|
145
|
+
if (skill.meta.triggers.length > 0) {
|
|
146
|
+
prompt += `Triggers: ${skill.meta.triggers.join(', ')}\n`;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
prompt += `---\n${skill.content.slice(0, 1000)}\n`;
|
|
150
|
+
if (skill.content.length > 1000) {
|
|
151
|
+
prompt += '...(truncated)\n';
|
|
152
|
+
}
|
|
153
|
+
prompt += '\n';
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
return prompt;
|
|
157
|
+
}
|
|
158
|
+
}
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import type { Skill, SkillExecutionResult } from './types.js';
|
|
2
|
+
|
|
3
|
+
interface ExecutionContext {
|
|
4
|
+
skill: Skill;
|
|
5
|
+
params: Record<string, unknown>;
|
|
6
|
+
env: Record<string, string>;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export class SkillExecutor {
|
|
10
|
+
async execute(skill: Skill, context: Record<string, unknown>): Promise<SkillExecutionResult> {
|
|
11
|
+
const startTime = Date.now();
|
|
12
|
+
|
|
13
|
+
try {
|
|
14
|
+
let output = '';
|
|
15
|
+
|
|
16
|
+
if (skill.code) {
|
|
17
|
+
const codeResult = await this.runCode(skill.code, {
|
|
18
|
+
skill,
|
|
19
|
+
params: context,
|
|
20
|
+
env: process.env as Record<string, string>,
|
|
21
|
+
});
|
|
22
|
+
output += codeResult;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
if (!output) {
|
|
26
|
+
output = `Skill "${skill.meta.name}" instructions ready for AI processing.\n`;
|
|
27
|
+
output += `Description: ${skill.meta.description}\n`;
|
|
28
|
+
output += `---\n${skill.content}\n---`;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
return {
|
|
32
|
+
success: true,
|
|
33
|
+
output,
|
|
34
|
+
duration: Date.now() - startTime,
|
|
35
|
+
};
|
|
36
|
+
} catch (error) {
|
|
37
|
+
const errMsg = error instanceof Error ? error.message : String(error);
|
|
38
|
+
return {
|
|
39
|
+
success: false,
|
|
40
|
+
output: '',
|
|
41
|
+
error: errMsg,
|
|
42
|
+
duration: Date.now() - startTime,
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
async runCode(code: string, ctx: ExecutionContext): Promise<string> {
|
|
48
|
+
const lines: string[] = [];
|
|
49
|
+
|
|
50
|
+
const sandbox = {
|
|
51
|
+
console: {
|
|
52
|
+
log: (...args: unknown[]) => {
|
|
53
|
+
lines.push(args.map((a) => (typeof a === 'string' ? a : JSON.stringify(a))).join(' '));
|
|
54
|
+
},
|
|
55
|
+
warn: (...args: unknown[]) => {
|
|
56
|
+
lines.push('[WARN] ' + args.map((a) => (typeof a === 'string' ? a : JSON.stringify(a))).join(' '));
|
|
57
|
+
},
|
|
58
|
+
error: (...args: unknown[]) => {
|
|
59
|
+
lines.push('[ERROR] ' + args.map((a) => (typeof a === 'string' ? a : JSON.stringify(a))).join(' '));
|
|
60
|
+
},
|
|
61
|
+
},
|
|
62
|
+
skill: ctx.skill,
|
|
63
|
+
params: ctx.params,
|
|
64
|
+
env: ctx.env,
|
|
65
|
+
fetch: globalThis.fetch,
|
|
66
|
+
setTimeout: globalThis.setTimeout,
|
|
67
|
+
clearTimeout: globalThis.clearTimeout,
|
|
68
|
+
JSON: JSON,
|
|
69
|
+
Math: Math,
|
|
70
|
+
Date: Date,
|
|
71
|
+
Object: Object,
|
|
72
|
+
Array: Array,
|
|
73
|
+
String: String,
|
|
74
|
+
Number: Number,
|
|
75
|
+
Boolean: Boolean,
|
|
76
|
+
Map: Map,
|
|
77
|
+
Set: Set,
|
|
78
|
+
RegExp: RegExp,
|
|
79
|
+
Error: Error,
|
|
80
|
+
Promise: Promise,
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
try {
|
|
84
|
+
const wrappedCode = `
|
|
85
|
+
return (async () => {
|
|
86
|
+
${code}
|
|
87
|
+
})();
|
|
88
|
+
`;
|
|
89
|
+
|
|
90
|
+
const fn = new Function(
|
|
91
|
+
...Object.keys(sandbox),
|
|
92
|
+
wrappedCode,
|
|
93
|
+
);
|
|
94
|
+
|
|
95
|
+
const result = await fn(...Object.values(sandbox));
|
|
96
|
+
|
|
97
|
+
if (result !== undefined && result !== null) {
|
|
98
|
+
lines.push(typeof result === 'string' ? result : JSON.stringify(result, null, 2));
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
return lines.join('\n');
|
|
102
|
+
} catch (error) {
|
|
103
|
+
const errMsg = error instanceof Error ? error.message : String(error);
|
|
104
|
+
return `Code execution error: ${errMsg}\n${lines.join('\n')}`;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
}
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
import { readFile, readdir } from 'node:fs/promises';
|
|
2
|
+
import { existsSync, watch } from 'node:fs';
|
|
3
|
+
import { join } from 'node:path';
|
|
4
|
+
import { DEEPER_SKILLS_DIR, PROJECT_SKILLS_DIR } from '../core/constants.js';
|
|
5
|
+
import type { Skill, SkillMeta, SkillLoadResult } from './types.js';
|
|
6
|
+
|
|
7
|
+
function parseSimpleYaml(s: string): Record<string, unknown> {
|
|
8
|
+
const result: Record<string, unknown> = {};
|
|
9
|
+
const lines = s.split('\n');
|
|
10
|
+
let listKey = '';
|
|
11
|
+
for (const line of lines) {
|
|
12
|
+
const trimmed = line.trim();
|
|
13
|
+
if (!trimmed || trimmed.startsWith('#')) continue;
|
|
14
|
+
const listMatch = trimmed.match(/^\s*-\s+(.+)/);
|
|
15
|
+
if (listMatch && listKey) {
|
|
16
|
+
const arr = (result[listKey] as string[]) || [];
|
|
17
|
+
arr.push(listMatch[1]);
|
|
18
|
+
result[listKey] = arr;
|
|
19
|
+
continue;
|
|
20
|
+
}
|
|
21
|
+
const kv = trimmed.match(/^([^:]+):\s*(.*)/);
|
|
22
|
+
if (kv) {
|
|
23
|
+
listKey = kv[2] === '' ? kv[1].trim() : '';
|
|
24
|
+
if (!listKey) result[kv[1].trim()] = kv[2].trim();
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
return result;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
interface FrontmatterResult {
|
|
31
|
+
meta: SkillMeta;
|
|
32
|
+
body: string;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export class SkillLoader {
|
|
36
|
+
private scanDirs: string[];
|
|
37
|
+
private watchers: Array<ReturnType<typeof watch>> = [];
|
|
38
|
+
|
|
39
|
+
constructor(additionalDirs?: string[]) {
|
|
40
|
+
this.scanDirs = [DEEPER_SKILLS_DIR, PROJECT_SKILLS_DIR];
|
|
41
|
+
if (additionalDirs) {
|
|
42
|
+
this.scanDirs.push(...additionalDirs);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
async loadAll(): Promise<SkillLoadResult[]> {
|
|
47
|
+
const results: SkillLoadResult[] = [];
|
|
48
|
+
|
|
49
|
+
for (const dir of this.scanDirs) {
|
|
50
|
+
if (!existsSync(dir)) continue;
|
|
51
|
+
const dirResults = await this.scanDirectory(dir);
|
|
52
|
+
results.push(...dirResults);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
return results;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
async loadSingle(dirPath: string, skillName: string): Promise<SkillLoadResult | null> {
|
|
59
|
+
const mdPath = join(dirPath, skillName, 'skill.md');
|
|
60
|
+
const jsPath = join(dirPath, skillName, 'skill.js');
|
|
61
|
+
|
|
62
|
+
if (!existsSync(mdPath)) return null;
|
|
63
|
+
|
|
64
|
+
try {
|
|
65
|
+
const mdContent = await readFile(mdPath, 'utf-8');
|
|
66
|
+
const { meta, body } = this.parseFrontmatter(mdContent);
|
|
67
|
+
|
|
68
|
+
let code: string | undefined;
|
|
69
|
+
if (existsSync(jsPath)) {
|
|
70
|
+
code = await readFile(jsPath, 'utf-8');
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const skill: Skill = { meta, content: body, code };
|
|
74
|
+
|
|
75
|
+
return {
|
|
76
|
+
skill,
|
|
77
|
+
source: dirPath,
|
|
78
|
+
fromCodeFile: !!code,
|
|
79
|
+
};
|
|
80
|
+
} catch {
|
|
81
|
+
return null;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
startWatching(callback: (result: SkillLoadResult) => void): void {
|
|
86
|
+
for (const dir of this.scanDirs) {
|
|
87
|
+
if (!existsSync(dir)) continue;
|
|
88
|
+
|
|
89
|
+
try {
|
|
90
|
+
const watcher = watch(dir, { recursive: true }, async (_eventType, filename) => {
|
|
91
|
+
if (!filename) return;
|
|
92
|
+
const parts = filename.toString().split(/[/\\]/);
|
|
93
|
+
if (parts.length < 2) return;
|
|
94
|
+
|
|
95
|
+
const skillName = parts[0];
|
|
96
|
+
const result = await this.loadSingle(dir, skillName);
|
|
97
|
+
if (result) {
|
|
98
|
+
callback(result);
|
|
99
|
+
}
|
|
100
|
+
});
|
|
101
|
+
this.watchers.push(watcher);
|
|
102
|
+
} catch {
|
|
103
|
+
continue;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
stopWatching(): void {
|
|
109
|
+
for (const watcher of this.watchers) {
|
|
110
|
+
watcher.close();
|
|
111
|
+
}
|
|
112
|
+
this.watchers = [];
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
private async scanDirectory(dirPath: string): Promise<SkillLoadResult[]> {
|
|
116
|
+
const results: SkillLoadResult[] = [];
|
|
117
|
+
|
|
118
|
+
try {
|
|
119
|
+
const entries = await readdir(dirPath, { withFileTypes: true });
|
|
120
|
+
|
|
121
|
+
for (const entry of entries) {
|
|
122
|
+
if (!entry.isDirectory()) continue;
|
|
123
|
+
|
|
124
|
+
const skillDir = join(dirPath, entry.name);
|
|
125
|
+
const mdFile = join(skillDir, 'skill.md');
|
|
126
|
+
|
|
127
|
+
if (!existsSync(mdFile)) continue;
|
|
128
|
+
|
|
129
|
+
const result = await this.loadSingle(dirPath, entry.name);
|
|
130
|
+
if (result) {
|
|
131
|
+
results.push(result);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
} catch {
|
|
135
|
+
// Directory may not exist yet
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
return results;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
private parseFrontmatter(markdown: string): FrontmatterResult {
|
|
142
|
+
const frontmatterRegex = /^---\r?\n([\s\S]*?)\r?\n---\r?\n?([\s\S]*)$/;
|
|
143
|
+
const match = markdown.match(frontmatterRegex);
|
|
144
|
+
|
|
145
|
+
if (!match) {
|
|
146
|
+
return {
|
|
147
|
+
meta: {
|
|
148
|
+
name: 'unnamed',
|
|
149
|
+
description: 'No description',
|
|
150
|
+
version: '1.0.0',
|
|
151
|
+
author: 'unknown',
|
|
152
|
+
triggers: [],
|
|
153
|
+
tools: [],
|
|
154
|
+
dependencies: [],
|
|
155
|
+
},
|
|
156
|
+
body: markdown,
|
|
157
|
+
};
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
const yamlContent = match[1];
|
|
161
|
+
const body = match[2];
|
|
162
|
+
|
|
163
|
+
let parsed: Record<string, unknown> = {};
|
|
164
|
+
try {
|
|
165
|
+
parsed = parseSimpleYaml(yamlContent) as Record<string, unknown>;
|
|
166
|
+
} catch {
|
|
167
|
+
// If YAML parsing fails, use defaults
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
const meta: SkillMeta = {
|
|
171
|
+
name: (parsed.name as string) || 'unnamed',
|
|
172
|
+
description: (parsed.description as string) || 'No description',
|
|
173
|
+
version: (parsed.version as string) || '1.0.0',
|
|
174
|
+
author: (parsed.author as string) || 'unknown',
|
|
175
|
+
triggers: Array.isArray(parsed.triggers) ? parsed.triggers.map(String) : [],
|
|
176
|
+
tools: Array.isArray(parsed.tools) ? parsed.tools.map(String) : [],
|
|
177
|
+
dependencies: Array.isArray(parsed.dependencies) ? parsed.dependencies.map(String) : [],
|
|
178
|
+
};
|
|
179
|
+
|
|
180
|
+
return { meta, body };
|
|
181
|
+
}
|
|
182
|
+
}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import type { Skill, SkillMeta } from './types.js';
|
|
2
|
+
|
|
3
|
+
export class SkillRegistry {
|
|
4
|
+
private skills = new Map<string, Skill>();
|
|
5
|
+
|
|
6
|
+
register(skill: Skill): void {
|
|
7
|
+
if (this.skills.has(skill.meta.name)) {
|
|
8
|
+
throw new Error(`Skill already registered: ${skill.meta.name}`);
|
|
9
|
+
}
|
|
10
|
+
this.skills.set(skill.meta.name, skill);
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
unregister(name: string): boolean {
|
|
14
|
+
return this.skills.delete(name);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
get(name: string): Skill | undefined {
|
|
18
|
+
return this.skills.get(name);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
getAll(): Skill[] {
|
|
22
|
+
return Array.from(this.skills.values());
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
findByTrigger(trigger: string): Skill[] {
|
|
26
|
+
const lowerTrigger = trigger.toLowerCase();
|
|
27
|
+
const matches: Skill[] = [];
|
|
28
|
+
|
|
29
|
+
for (const skill of this.skills.values()) {
|
|
30
|
+
if (skill.meta.triggers.some((t) => lowerTrigger.includes(t.toLowerCase()))) {
|
|
31
|
+
matches.push(skill);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
return matches;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
findByTool(toolName: string): Skill[] {
|
|
39
|
+
const matches: Skill[] = [];
|
|
40
|
+
|
|
41
|
+
for (const skill of this.skills.values()) {
|
|
42
|
+
if (skill.meta.tools.includes(toolName)) {
|
|
43
|
+
matches.push(skill);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
return matches;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
findByDependency(depName: string): Skill[] {
|
|
51
|
+
const matches: Skill[] = [];
|
|
52
|
+
|
|
53
|
+
for (const skill of this.skills.values()) {
|
|
54
|
+
if (skill.meta.dependencies.includes(depName)) {
|
|
55
|
+
matches.push(skill);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
return matches;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
getMeta(): SkillMeta[] {
|
|
63
|
+
return Array.from(this.skills.values()).map((s) => s.meta);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
clear(): void {
|
|
67
|
+
this.skills.clear();
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
get size(): number {
|
|
71
|
+
return this.skills.size;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import type { Skill } from './types.js';
|
|
2
|
+
import type { SkillRegistry } from './SkillRegistry.js';
|
|
3
|
+
|
|
4
|
+
interface TriggerMatch {
|
|
5
|
+
skill: Skill;
|
|
6
|
+
score: number;
|
|
7
|
+
matchedTriggers: string[];
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export class SkillTrigger {
|
|
11
|
+
private registry: SkillRegistry;
|
|
12
|
+
|
|
13
|
+
constructor(registry: SkillRegistry) {
|
|
14
|
+
this.registry = registry;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
match(input: string): Skill[] {
|
|
18
|
+
const lowerInput = input.toLowerCase();
|
|
19
|
+
const matches: TriggerMatch[] = [];
|
|
20
|
+
|
|
21
|
+
for (const skill of this.registry.getAll()) {
|
|
22
|
+
const matchedTriggers: string[] = [];
|
|
23
|
+
let score = 0;
|
|
24
|
+
|
|
25
|
+
for (const trigger of skill.meta.triggers) {
|
|
26
|
+
const lowerTrigger = trigger.toLowerCase();
|
|
27
|
+
if (lowerInput.includes(lowerTrigger)) {
|
|
28
|
+
matchedTriggers.push(trigger);
|
|
29
|
+
score += lowerTrigger.length;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const nameMatch = skill.meta.name.toLowerCase();
|
|
34
|
+
if (lowerInput.includes(nameMatch)) {
|
|
35
|
+
matchedTriggers.push(skill.meta.name);
|
|
36
|
+
score += nameMatch.length * 2;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const descWords = skill.meta.description.toLowerCase().split(/\s+/);
|
|
40
|
+
for (const word of descWords) {
|
|
41
|
+
if (word.length > 3 && lowerInput.includes(word)) {
|
|
42
|
+
score += 1;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
if (matchedTriggers.length > 0 || score > 0) {
|
|
47
|
+
matches.push({ skill, score, matchedTriggers });
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
matches.sort((a, b) => b.score - a.score);
|
|
52
|
+
|
|
53
|
+
return matches.map((m) => m.skill);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
getRecommendedSkills(input: string, maxResults: number = 3): Array<{
|
|
57
|
+
skill: Skill;
|
|
58
|
+
reason: string;
|
|
59
|
+
}> {
|
|
60
|
+
const matched = this.match(input).slice(0, maxResults);
|
|
61
|
+
|
|
62
|
+
return matched.map((skill) => {
|
|
63
|
+
const reasons: string[] = [];
|
|
64
|
+
|
|
65
|
+
const lowerInput = input.toLowerCase();
|
|
66
|
+
for (const trigger of skill.meta.triggers) {
|
|
67
|
+
if (lowerInput.includes(trigger.toLowerCase())) {
|
|
68
|
+
reasons.push(`matched trigger: "${trigger}"`);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
if (lowerInput.includes(skill.meta.name.toLowerCase())) {
|
|
73
|
+
reasons.push(`matched name: "${skill.meta.name}"`);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
return {
|
|
77
|
+
skill,
|
|
78
|
+
reason: reasons.length > 0 ? reasons.join(', ') : 'contextually relevant',
|
|
79
|
+
};
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
export interface SkillMeta {
|
|
2
|
+
name: string;
|
|
3
|
+
description: string;
|
|
4
|
+
version: string;
|
|
5
|
+
author: string;
|
|
6
|
+
triggers: string[];
|
|
7
|
+
tools: string[];
|
|
8
|
+
dependencies: string[];
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export interface Skill {
|
|
12
|
+
meta: SkillMeta;
|
|
13
|
+
content: string;
|
|
14
|
+
code?: string;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export interface SkillExecutionResult {
|
|
18
|
+
success: boolean;
|
|
19
|
+
output: string;
|
|
20
|
+
error?: string;
|
|
21
|
+
duration: number;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export interface SkillLoadResult {
|
|
25
|
+
skill: Skill;
|
|
26
|
+
source: string;
|
|
27
|
+
fromCodeFile: boolean;
|
|
28
|
+
}
|