neuro-cli 4.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.
- package/LICENSE +21 -0
- package/README.md +510 -0
- package/dist/advisor/advisor.d.ts +50 -0
- package/dist/advisor/advisor.js +178 -0
- package/dist/agents/base.d.ts +62 -0
- package/dist/agents/base.js +215 -0
- package/dist/agents/orchestrator.d.ts +46 -0
- package/dist/agents/orchestrator.js +192 -0
- package/dist/agents/team.d.ts +51 -0
- package/dist/agents/team.js +210 -0
- package/dist/api/models.d.ts +23 -0
- package/dist/api/models.js +514 -0
- package/dist/api/ollama.d.ts +153 -0
- package/dist/api/ollama.js +751 -0
- package/dist/api/openrouter.d.ts +55 -0
- package/dist/api/openrouter.js +223 -0
- package/dist/commands/commands.d.ts +43 -0
- package/dist/commands/commands.js +308 -0
- package/dist/config/config.d.ts +8 -0
- package/dist/config/config.js +311 -0
- package/dist/context/compaction.d.ts +54 -0
- package/dist/context/compaction.js +251 -0
- package/dist/context/custom-agents.d.ts +107 -0
- package/dist/context/custom-agents.js +397 -0
- package/dist/context/custom-tools.d.ts +120 -0
- package/dist/context/custom-tools.js +564 -0
- package/dist/context/git-checkpoint.d.ts +52 -0
- package/dist/context/git-checkpoint.js +240 -0
- package/dist/context/neuro-md.d.ts +48 -0
- package/dist/context/neuro-md.js +202 -0
- package/dist/context/neuroignore.d.ts +102 -0
- package/dist/context/neuroignore.js +441 -0
- package/dist/context/repo-map.d.ts +38 -0
- package/dist/context/repo-map.js +220 -0
- package/dist/context/skill-standard.d.ts +262 -0
- package/dist/context/skill-standard.js +1156 -0
- package/dist/context/skill-system.d.ts +75 -0
- package/dist/context/skill-system.js +578 -0
- package/dist/context/tree-sitter.d.ts +99 -0
- package/dist/context/tree-sitter.js +1956 -0
- package/dist/core/acp.d.ts +325 -0
- package/dist/core/acp.js +1498 -0
- package/dist/core/api-server.d.ts +143 -0
- package/dist/core/api-server.js +550 -0
- package/dist/core/approval.d.ts +81 -0
- package/dist/core/approval.js +432 -0
- package/dist/core/auto-compact.d.ts +127 -0
- package/dist/core/auto-compact.js +436 -0
- package/dist/core/auto-mode.d.ts +232 -0
- package/dist/core/auto-mode.js +831 -0
- package/dist/core/background-session.d.ts +166 -0
- package/dist/core/background-session.js +696 -0
- package/dist/core/cicd.d.ts +257 -0
- package/dist/core/cicd.js +1443 -0
- package/dist/core/cloud-sync.d.ts +156 -0
- package/dist/core/cloud-sync.js +582 -0
- package/dist/core/code-review.d.ts +132 -0
- package/dist/core/code-review.js +1191 -0
- package/dist/core/completion.d.ts +49 -0
- package/dist/core/completion.js +384 -0
- package/dist/core/context.d.ts +38 -0
- package/dist/core/context.js +144 -0
- package/dist/core/diff-preview.d.ts +35 -0
- package/dist/core/diff-preview.js +173 -0
- package/dist/core/doom-loop.d.ts +51 -0
- package/dist/core/doom-loop.js +179 -0
- package/dist/core/engine.d.ts +183 -0
- package/dist/core/engine.js +942 -0
- package/dist/core/extended-thinking.d.ts +103 -0
- package/dist/core/extended-thinking.js +269 -0
- package/dist/core/fallback.d.ts +54 -0
- package/dist/core/fallback.js +104 -0
- package/dist/core/git-worktree.d.ts +139 -0
- package/dist/core/git-worktree.js +614 -0
- package/dist/core/headless.d.ts +30 -0
- package/dist/core/headless.js +101 -0
- package/dist/core/i18n.d.ts +83 -0
- package/dist/core/i18n.js +739 -0
- package/dist/core/linting.d.ts +129 -0
- package/dist/core/linting.js +699 -0
- package/dist/core/model-router.d.ts +109 -0
- package/dist/core/model-router.js +581 -0
- package/dist/core/multi-model.d.ts +243 -0
- package/dist/core/multi-model.js +1099 -0
- package/dist/core/multi-session.d.ts +144 -0
- package/dist/core/multi-session.js +442 -0
- package/dist/core/multimodal.d.ts +125 -0
- package/dist/core/multimodal.js +286 -0
- package/dist/core/observability.d.ts +93 -0
- package/dist/core/observability.js +737 -0
- package/dist/core/os-sandbox.d.ts +122 -0
- package/dist/core/os-sandbox.js +1193 -0
- package/dist/core/outcome-grading.d.ts +228 -0
- package/dist/core/outcome-grading.js +1123 -0
- package/dist/core/output-styles.d.ts +57 -0
- package/dist/core/output-styles.js +382 -0
- package/dist/core/parallel-agents.d.ts +183 -0
- package/dist/core/parallel-agents.js +563 -0
- package/dist/core/plugin-bundle.d.ts +236 -0
- package/dist/core/plugin-bundle.js +887 -0
- package/dist/core/plugin-sdk.d.ts +139 -0
- package/dist/core/plugin-sdk.js +273 -0
- package/dist/core/prompt-cache.d.ts +163 -0
- package/dist/core/prompt-cache.js +599 -0
- package/dist/core/sandbox.d.ts +127 -0
- package/dist/core/sandbox.js +369 -0
- package/dist/core/scheduled-tasks.d.ts +151 -0
- package/dist/core/scheduled-tasks.js +502 -0
- package/dist/core/security-scanner.d.ts +160 -0
- package/dist/core/security-scanner.js +1494 -0
- package/dist/core/session.d.ts +83 -0
- package/dist/core/session.js +269 -0
- package/dist/core/shell-completion.d.ts +51 -0
- package/dist/core/shell-completion.js +674 -0
- package/dist/core/smart-monitor.d.ts +146 -0
- package/dist/core/smart-monitor.js +1199 -0
- package/dist/core/spec-driven.d.ts +233 -0
- package/dist/core/spec-driven.js +1485 -0
- package/dist/core/spending-warnings.d.ts +123 -0
- package/dist/core/spending-warnings.js +456 -0
- package/dist/core/sub-agent.d.ts +298 -0
- package/dist/core/sub-agent.js +1023 -0
- package/dist/core/telemetry.d.ts +157 -0
- package/dist/core/telemetry.js +412 -0
- package/dist/core/terminal-ux.d.ts +134 -0
- package/dist/core/terminal-ux.js +649 -0
- package/dist/core/testing.d.ts +146 -0
- package/dist/core/testing.js +1200 -0
- package/dist/core/types.d.ts +439 -0
- package/dist/core/types.js +6 -0
- package/dist/core/undo-redo.d.ts +112 -0
- package/dist/core/undo-redo.js +290 -0
- package/dist/core/updater.d.ts +159 -0
- package/dist/core/updater.js +608 -0
- package/dist/core/vim-mode.d.ts +151 -0
- package/dist/core/vim-mode.js +771 -0
- package/dist/core/voice.d.ts +137 -0
- package/dist/core/voice.js +538 -0
- package/dist/core/web-dashboard.d.ts +109 -0
- package/dist/core/web-dashboard.js +484 -0
- package/dist/hooks/hooks.d.ts +74 -0
- package/dist/hooks/hooks.js +160 -0
- package/dist/hooks/llm-evaluator.d.ts +165 -0
- package/dist/hooks/llm-evaluator.js +560 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +1186 -0
- package/dist/lsp/lsp-manager.d.ts +63 -0
- package/dist/lsp/lsp-manager.js +351 -0
- package/dist/mcp/client.d.ts +133 -0
- package/dist/mcp/client.js +684 -0
- package/dist/mcp/mcp-apps.d.ts +70 -0
- package/dist/mcp/mcp-apps.js +1007 -0
- package/dist/tools/bash.d.ts +5 -0
- package/dist/tools/bash.js +195 -0
- package/dist/tools/browser.d.ts +92 -0
- package/dist/tools/browser.js +1570 -0
- package/dist/tools/extended.d.ts +6 -0
- package/dist/tools/extended.js +191 -0
- package/dist/tools/file.d.ts +10 -0
- package/dist/tools/file.js +382 -0
- package/dist/tools/github.d.ts +389 -0
- package/dist/tools/github.js +759 -0
- package/dist/tools/index.d.ts +9 -0
- package/dist/tools/index.js +40 -0
- package/dist/tools/memory.d.ts +6 -0
- package/dist/tools/memory.js +197 -0
- package/dist/tools/registry.d.ts +29 -0
- package/dist/tools/registry.js +64 -0
- package/dist/tools/web.d.ts +6 -0
- package/dist/tools/web.js +150 -0
- package/dist/ui/renderer.d.ts +97 -0
- package/dist/ui/renderer.js +279 -0
- package/dist/ui/theme.d.ts +27 -0
- package/dist/ui/theme.js +106 -0
- package/package.json +83 -0
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
export interface CustomAgentDefinition {
|
|
2
|
+
name: string;
|
|
3
|
+
description: string;
|
|
4
|
+
systemPrompt: string;
|
|
5
|
+
model?: string;
|
|
6
|
+
temperature?: number;
|
|
7
|
+
maxTokens?: number;
|
|
8
|
+
tools?: string[];
|
|
9
|
+
maxIterations?: number;
|
|
10
|
+
tags?: string[];
|
|
11
|
+
isCustom: boolean;
|
|
12
|
+
source: string;
|
|
13
|
+
}
|
|
14
|
+
export declare class CustomAgentLoader {
|
|
15
|
+
private agents;
|
|
16
|
+
private projectAgentsDir;
|
|
17
|
+
private globalAgentsDir;
|
|
18
|
+
private watchers;
|
|
19
|
+
private onChange?;
|
|
20
|
+
constructor(projectRoot: string, onChange?: () => void);
|
|
21
|
+
/**
|
|
22
|
+
* Discover all custom agents from project and global directories.
|
|
23
|
+
* Global agents are loaded first so project-level agents with the same
|
|
24
|
+
* name take precedence (last-write-wins).
|
|
25
|
+
*/
|
|
26
|
+
discover(): CustomAgentDefinition[];
|
|
27
|
+
/**
|
|
28
|
+
* Get a single agent definition by name.
|
|
29
|
+
*/
|
|
30
|
+
get(name: string): CustomAgentDefinition | undefined;
|
|
31
|
+
/**
|
|
32
|
+
* Get all discovered custom agent definitions.
|
|
33
|
+
*/
|
|
34
|
+
getAll(): CustomAgentDefinition[];
|
|
35
|
+
/**
|
|
36
|
+
* Convert a CustomAgentDefinition into a standard AgentConfig object
|
|
37
|
+
* merged with default values.
|
|
38
|
+
*/
|
|
39
|
+
toAgentConfig(def: CustomAgentDefinition, defaultModel: string): {
|
|
40
|
+
name: string;
|
|
41
|
+
description: string;
|
|
42
|
+
systemPrompt: string;
|
|
43
|
+
model: string;
|
|
44
|
+
temperature: number;
|
|
45
|
+
maxTokens: number;
|
|
46
|
+
tools: string[];
|
|
47
|
+
maxIterations: number;
|
|
48
|
+
isCustom: boolean;
|
|
49
|
+
tags: string[];
|
|
50
|
+
};
|
|
51
|
+
/**
|
|
52
|
+
* Print a formatted list of discovered custom agents to stdout.
|
|
53
|
+
*/
|
|
54
|
+
printAgents(): void;
|
|
55
|
+
/**
|
|
56
|
+
* Enable file watching for hot-reload of agent definitions.
|
|
57
|
+
* Calls the onChange callback (or re-discovers) when files change.
|
|
58
|
+
*/
|
|
59
|
+
enableWatch(): void;
|
|
60
|
+
/**
|
|
61
|
+
* Disable all file watchers.
|
|
62
|
+
*/
|
|
63
|
+
disableWatch(): void;
|
|
64
|
+
/**
|
|
65
|
+
* Load agent definitions from a single directory.
|
|
66
|
+
*/
|
|
67
|
+
private loadFromDirectory;
|
|
68
|
+
/**
|
|
69
|
+
* Parse a markdown file with YAML frontmatter into a CustomAgentDefinition.
|
|
70
|
+
*
|
|
71
|
+
* Expected format:
|
|
72
|
+
* ---
|
|
73
|
+
* name: my-agent
|
|
74
|
+
* description: A helpful description
|
|
75
|
+
* model: claude-sonnet-4-20250514
|
|
76
|
+
* temperature: 0.5
|
|
77
|
+
* maxTokens: 2048
|
|
78
|
+
* tools:
|
|
79
|
+
* - read
|
|
80
|
+
* - write
|
|
81
|
+
* maxIterations: 5
|
|
82
|
+
* tags:
|
|
83
|
+
* - coding
|
|
84
|
+
* - review
|
|
85
|
+
* ---
|
|
86
|
+
* You are a specialized code review assistant...
|
|
87
|
+
*/
|
|
88
|
+
private parseAgentFile;
|
|
89
|
+
/**
|
|
90
|
+
* Minimal YAML parser that handles the flat key-value structures and
|
|
91
|
+
* simple arrays typical of agent frontmatter.
|
|
92
|
+
*
|
|
93
|
+
* Supports:
|
|
94
|
+
* key: value
|
|
95
|
+
* key: "quoted value"
|
|
96
|
+
* key:
|
|
97
|
+
* - item1
|
|
98
|
+
* - item2
|
|
99
|
+
* key:
|
|
100
|
+
* - "quoted item"
|
|
101
|
+
*
|
|
102
|
+
* Does NOT support:
|
|
103
|
+
* Nested objects, multiline strings (|, >), anchors, etc.
|
|
104
|
+
*/
|
|
105
|
+
private parseSimpleYaml;
|
|
106
|
+
}
|
|
107
|
+
//# sourceMappingURL=custom-agents.d.ts.map
|
|
@@ -0,0 +1,397 @@
|
|
|
1
|
+
// ============================================================
|
|
2
|
+
// NeuroCLI - Custom Agents Loader
|
|
3
|
+
// Loads agent definitions from .neuro/agents/ (project) and
|
|
4
|
+
// ~/.neuro/agents/ (global). Supports markdown files with YAML
|
|
5
|
+
// frontmatter, similar to Claude Code's agent system.
|
|
6
|
+
// ============================================================
|
|
7
|
+
import { readFileSync, readdirSync, existsSync, statSync, watchFile, unwatchFile } from 'fs';
|
|
8
|
+
import { join, basename, extname } from 'path';
|
|
9
|
+
import { homedir } from 'os';
|
|
10
|
+
// ---------------------------------------------------------------------------
|
|
11
|
+
// Custom Agent Loader
|
|
12
|
+
// ---------------------------------------------------------------------------
|
|
13
|
+
export class CustomAgentLoader {
|
|
14
|
+
agents;
|
|
15
|
+
projectAgentsDir;
|
|
16
|
+
globalAgentsDir;
|
|
17
|
+
watchers = [];
|
|
18
|
+
onChange;
|
|
19
|
+
constructor(projectRoot, onChange) {
|
|
20
|
+
this.agents = new Map();
|
|
21
|
+
this.projectAgentsDir = join(projectRoot, '.neuro', 'agents');
|
|
22
|
+
this.globalAgentsDir = join(homedir(), '.neuro', 'agents');
|
|
23
|
+
this.onChange = onChange;
|
|
24
|
+
}
|
|
25
|
+
// -------------------------------------------------------------------------
|
|
26
|
+
// Public API
|
|
27
|
+
// -------------------------------------------------------------------------
|
|
28
|
+
/**
|
|
29
|
+
* Discover all custom agents from project and global directories.
|
|
30
|
+
* Global agents are loaded first so project-level agents with the same
|
|
31
|
+
* name take precedence (last-write-wins).
|
|
32
|
+
*/
|
|
33
|
+
discover() {
|
|
34
|
+
this.agents.clear();
|
|
35
|
+
// Load global agents first (lower priority)
|
|
36
|
+
this.loadFromDirectory(this.globalAgentsDir);
|
|
37
|
+
// Load project agents second (higher priority, overrides global)
|
|
38
|
+
this.loadFromDirectory(this.projectAgentsDir);
|
|
39
|
+
return this.getAll();
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Get a single agent definition by name.
|
|
43
|
+
*/
|
|
44
|
+
get(name) {
|
|
45
|
+
return this.agents.get(name);
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Get all discovered custom agent definitions.
|
|
49
|
+
*/
|
|
50
|
+
getAll() {
|
|
51
|
+
return Array.from(this.agents.values());
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Convert a CustomAgentDefinition into a standard AgentConfig object
|
|
55
|
+
* merged with default values.
|
|
56
|
+
*/
|
|
57
|
+
toAgentConfig(def, defaultModel) {
|
|
58
|
+
return {
|
|
59
|
+
name: def.name,
|
|
60
|
+
description: def.description,
|
|
61
|
+
systemPrompt: def.systemPrompt,
|
|
62
|
+
model: def.model ?? defaultModel,
|
|
63
|
+
temperature: def.temperature ?? 0.7,
|
|
64
|
+
maxTokens: def.maxTokens ?? 4096,
|
|
65
|
+
tools: def.tools ?? [],
|
|
66
|
+
maxIterations: def.maxIterations ?? 10,
|
|
67
|
+
isCustom: true,
|
|
68
|
+
tags: def.tags ?? [],
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Print a formatted list of discovered custom agents to stdout.
|
|
73
|
+
*/
|
|
74
|
+
printAgents() {
|
|
75
|
+
const agents = this.getAll();
|
|
76
|
+
if (agents.length === 0) {
|
|
77
|
+
console.log('No custom agents found.');
|
|
78
|
+
console.log('');
|
|
79
|
+
console.log(' Place agent definition files in:');
|
|
80
|
+
console.log(` Project: ${this.projectAgentsDir}/`);
|
|
81
|
+
console.log(` Global: ${this.globalAgentsDir}/`);
|
|
82
|
+
console.log('');
|
|
83
|
+
console.log(' Agent files are markdown with YAML frontmatter:');
|
|
84
|
+
console.log(' ---');
|
|
85
|
+
console.log(' name: my-agent');
|
|
86
|
+
console.log(' description: Does something useful');
|
|
87
|
+
console.log(' ---');
|
|
88
|
+
console.log(' System prompt body goes here...');
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
console.log(`Custom agents (${agents.length}):`);
|
|
92
|
+
console.log('');
|
|
93
|
+
for (const agent of agents) {
|
|
94
|
+
const tags = agent.tags && agent.tags.length > 0
|
|
95
|
+
? ` [${agent.tags.join(', ')}]`
|
|
96
|
+
: '';
|
|
97
|
+
const model = agent.model ? ` (model: ${agent.model})` : '';
|
|
98
|
+
const source = agent.source.includes('.neuro/agents/')
|
|
99
|
+
? agent.source.split('.neuro/agents/')[1]
|
|
100
|
+
: agent.source;
|
|
101
|
+
console.log(` ${agent.name}${tags}${model}`);
|
|
102
|
+
console.log(` ${agent.description}`);
|
|
103
|
+
console.log(` source: ${source}`);
|
|
104
|
+
console.log('');
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
// -------------------------------------------------------------------------
|
|
108
|
+
// Hot-reload
|
|
109
|
+
// -------------------------------------------------------------------------
|
|
110
|
+
/**
|
|
111
|
+
* Enable file watching for hot-reload of agent definitions.
|
|
112
|
+
* Calls the onChange callback (or re-discovers) when files change.
|
|
113
|
+
*/
|
|
114
|
+
enableWatch() {
|
|
115
|
+
this.disableWatch();
|
|
116
|
+
const watchDir = (dir) => {
|
|
117
|
+
if (!existsSync(dir))
|
|
118
|
+
return;
|
|
119
|
+
try {
|
|
120
|
+
const entries = readdirSync(dir);
|
|
121
|
+
for (const entry of entries) {
|
|
122
|
+
const fullPath = join(dir, entry);
|
|
123
|
+
if (!statSync(fullPath).isFile())
|
|
124
|
+
continue;
|
|
125
|
+
const ext = extname(entry).toLowerCase();
|
|
126
|
+
if (ext !== '.md' && ext !== '.markdown')
|
|
127
|
+
continue;
|
|
128
|
+
watchFile(fullPath, { interval: 2000 }, () => {
|
|
129
|
+
// Re-discover on change
|
|
130
|
+
this.discover();
|
|
131
|
+
this.onChange?.();
|
|
132
|
+
});
|
|
133
|
+
this.watchers.push({
|
|
134
|
+
path: fullPath,
|
|
135
|
+
close: () => unwatchFile(fullPath),
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
catch {
|
|
140
|
+
// Directory may not be readable; skip silently
|
|
141
|
+
}
|
|
142
|
+
};
|
|
143
|
+
watchDir(this.globalAgentsDir);
|
|
144
|
+
watchDir(this.projectAgentsDir);
|
|
145
|
+
}
|
|
146
|
+
/**
|
|
147
|
+
* Disable all file watchers.
|
|
148
|
+
*/
|
|
149
|
+
disableWatch() {
|
|
150
|
+
for (const w of this.watchers) {
|
|
151
|
+
w.close();
|
|
152
|
+
}
|
|
153
|
+
this.watchers = [];
|
|
154
|
+
}
|
|
155
|
+
// -------------------------------------------------------------------------
|
|
156
|
+
// Private helpers
|
|
157
|
+
// -------------------------------------------------------------------------
|
|
158
|
+
/**
|
|
159
|
+
* Load agent definitions from a single directory.
|
|
160
|
+
*/
|
|
161
|
+
loadFromDirectory(dir) {
|
|
162
|
+
if (!existsSync(dir))
|
|
163
|
+
return;
|
|
164
|
+
let entries;
|
|
165
|
+
try {
|
|
166
|
+
entries = readdirSync(dir);
|
|
167
|
+
}
|
|
168
|
+
catch {
|
|
169
|
+
// Directory not readable; skip
|
|
170
|
+
return;
|
|
171
|
+
}
|
|
172
|
+
for (const entry of entries) {
|
|
173
|
+
const fullPath = join(dir, entry);
|
|
174
|
+
let stat;
|
|
175
|
+
try {
|
|
176
|
+
stat = statSync(fullPath);
|
|
177
|
+
}
|
|
178
|
+
catch {
|
|
179
|
+
continue;
|
|
180
|
+
}
|
|
181
|
+
if (!stat.isFile())
|
|
182
|
+
continue;
|
|
183
|
+
const ext = extname(entry).toLowerCase();
|
|
184
|
+
if (ext !== '.md' && ext !== '.markdown')
|
|
185
|
+
continue;
|
|
186
|
+
try {
|
|
187
|
+
const content = readFileSync(fullPath, 'utf-8');
|
|
188
|
+
const agent = this.parseAgentFile(content, fullPath);
|
|
189
|
+
if (agent) {
|
|
190
|
+
this.agents.set(agent.name, agent);
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
catch (err) {
|
|
194
|
+
console.warn(`[custom-agents] Failed to parse ${fullPath}: ${err instanceof Error ? err.message : String(err)}`);
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
/**
|
|
199
|
+
* Parse a markdown file with YAML frontmatter into a CustomAgentDefinition.
|
|
200
|
+
*
|
|
201
|
+
* Expected format:
|
|
202
|
+
* ---
|
|
203
|
+
* name: my-agent
|
|
204
|
+
* description: A helpful description
|
|
205
|
+
* model: claude-sonnet-4-20250514
|
|
206
|
+
* temperature: 0.5
|
|
207
|
+
* maxTokens: 2048
|
|
208
|
+
* tools:
|
|
209
|
+
* - read
|
|
210
|
+
* - write
|
|
211
|
+
* maxIterations: 5
|
|
212
|
+
* tags:
|
|
213
|
+
* - coding
|
|
214
|
+
* - review
|
|
215
|
+
* ---
|
|
216
|
+
* You are a specialized code review assistant...
|
|
217
|
+
*/
|
|
218
|
+
parseAgentFile(content, filePath) {
|
|
219
|
+
// Match frontmatter delimited by --- at the start of the file
|
|
220
|
+
const frontmatterRegex = /^---\r?\n([\s\S]*?)\r?\n---/;
|
|
221
|
+
const match = content.match(frontmatterRegex);
|
|
222
|
+
if (!match) {
|
|
223
|
+
console.warn(`[custom-agents] No YAML frontmatter found in ${filePath}`);
|
|
224
|
+
return null;
|
|
225
|
+
}
|
|
226
|
+
const yamlStr = match[1];
|
|
227
|
+
const body = content.slice(match[0].length).trim();
|
|
228
|
+
const raw = this.parseSimpleYaml(yamlStr);
|
|
229
|
+
// Derive name: explicit > filename fallback
|
|
230
|
+
const derivedName = typeof raw.name === 'string' && raw.name.trim()
|
|
231
|
+
? raw.name.trim()
|
|
232
|
+
: basename(filePath, extname(filePath));
|
|
233
|
+
// Derive description
|
|
234
|
+
const description = typeof raw.description === 'string' && raw.description.trim()
|
|
235
|
+
? raw.description.trim()
|
|
236
|
+
: '';
|
|
237
|
+
// Validate required fields
|
|
238
|
+
if (!body) {
|
|
239
|
+
console.warn(`[custom-agents] Missing system prompt body in ${filePath}`);
|
|
240
|
+
return null;
|
|
241
|
+
}
|
|
242
|
+
if (!description) {
|
|
243
|
+
console.warn(`[custom-agents] Missing "description" field in frontmatter of ${filePath}`);
|
|
244
|
+
return null;
|
|
245
|
+
}
|
|
246
|
+
// Parse optional fields with type coercion
|
|
247
|
+
const model = typeof raw.model === 'string' ? raw.model : undefined;
|
|
248
|
+
const temperature = typeof raw.temperature === 'number'
|
|
249
|
+
? raw.temperature
|
|
250
|
+
: typeof raw.temperature === 'string'
|
|
251
|
+
? parseFloat(raw.temperature)
|
|
252
|
+
: undefined;
|
|
253
|
+
const validTemperature = temperature !== undefined && !isNaN(temperature)
|
|
254
|
+
? temperature
|
|
255
|
+
: undefined;
|
|
256
|
+
const maxTokens = typeof raw.maxTokens === 'number'
|
|
257
|
+
? raw.maxTokens
|
|
258
|
+
: typeof raw.maxTokens === 'string'
|
|
259
|
+
? parseInt(raw.maxTokens, 10)
|
|
260
|
+
: undefined;
|
|
261
|
+
const validMaxTokens = maxTokens !== undefined && !isNaN(maxTokens) && maxTokens > 0
|
|
262
|
+
? maxTokens
|
|
263
|
+
: undefined;
|
|
264
|
+
const maxIterations = typeof raw.maxIterations === 'number'
|
|
265
|
+
? raw.maxIterations
|
|
266
|
+
: typeof raw.maxIterations === 'string'
|
|
267
|
+
? parseInt(raw.maxIterations, 10)
|
|
268
|
+
: undefined;
|
|
269
|
+
const validMaxIterations = maxIterations !== undefined && !isNaN(maxIterations) && maxIterations > 0
|
|
270
|
+
? maxIterations
|
|
271
|
+
: undefined;
|
|
272
|
+
const tools = Array.isArray(raw.tools)
|
|
273
|
+
? raw.tools.filter((t) => typeof t === 'string')
|
|
274
|
+
: undefined;
|
|
275
|
+
const tags = Array.isArray(raw.tags)
|
|
276
|
+
? raw.tags.filter((t) => typeof t === 'string')
|
|
277
|
+
: undefined;
|
|
278
|
+
return {
|
|
279
|
+
name: derivedName,
|
|
280
|
+
description,
|
|
281
|
+
systemPrompt: body,
|
|
282
|
+
model,
|
|
283
|
+
temperature: validTemperature,
|
|
284
|
+
maxTokens: validMaxTokens,
|
|
285
|
+
tools,
|
|
286
|
+
maxIterations: validMaxIterations,
|
|
287
|
+
tags,
|
|
288
|
+
isCustom: true,
|
|
289
|
+
source: filePath,
|
|
290
|
+
};
|
|
291
|
+
}
|
|
292
|
+
/**
|
|
293
|
+
* Minimal YAML parser that handles the flat key-value structures and
|
|
294
|
+
* simple arrays typical of agent frontmatter.
|
|
295
|
+
*
|
|
296
|
+
* Supports:
|
|
297
|
+
* key: value
|
|
298
|
+
* key: "quoted value"
|
|
299
|
+
* key:
|
|
300
|
+
* - item1
|
|
301
|
+
* - item2
|
|
302
|
+
* key:
|
|
303
|
+
* - "quoted item"
|
|
304
|
+
*
|
|
305
|
+
* Does NOT support:
|
|
306
|
+
* Nested objects, multiline strings (|, >), anchors, etc.
|
|
307
|
+
*/
|
|
308
|
+
parseSimpleYaml(yaml) {
|
|
309
|
+
const result = {};
|
|
310
|
+
const lines = yaml.split(/\r?\n/);
|
|
311
|
+
let currentKey = null;
|
|
312
|
+
let currentArray = null;
|
|
313
|
+
const unquote = (s) => {
|
|
314
|
+
const trimmed = s.trim();
|
|
315
|
+
if ((trimmed.startsWith('"') && trimmed.endsWith('"')) ||
|
|
316
|
+
(trimmed.startsWith("'") && trimmed.endsWith("'"))) {
|
|
317
|
+
return trimmed.slice(1, -1);
|
|
318
|
+
}
|
|
319
|
+
return trimmed;
|
|
320
|
+
};
|
|
321
|
+
const coerceValue = (s) => {
|
|
322
|
+
const trimmed = s.trim();
|
|
323
|
+
if (trimmed === '')
|
|
324
|
+
return null;
|
|
325
|
+
// Quoted strings stay as strings
|
|
326
|
+
if ((trimmed.startsWith('"') && trimmed.endsWith('"')) ||
|
|
327
|
+
(trimmed.startsWith("'") && trimmed.endsWith("'"))) {
|
|
328
|
+
return unquote(trimmed);
|
|
329
|
+
}
|
|
330
|
+
// Booleans
|
|
331
|
+
if (trimmed.toLowerCase() === 'true')
|
|
332
|
+
return true;
|
|
333
|
+
if (trimmed.toLowerCase() === 'false')
|
|
334
|
+
return false;
|
|
335
|
+
// Null
|
|
336
|
+
if (trimmed.toLowerCase() === 'null' || trimmed === '~')
|
|
337
|
+
return null;
|
|
338
|
+
// Numbers
|
|
339
|
+
const asNum = Number(trimmed);
|
|
340
|
+
if (trimmed !== '' && !isNaN(asNum) && isFinite(asNum)) {
|
|
341
|
+
return asNum;
|
|
342
|
+
}
|
|
343
|
+
return trimmed;
|
|
344
|
+
};
|
|
345
|
+
for (const line of lines) {
|
|
346
|
+
// Skip empty lines and comments
|
|
347
|
+
if (/^\s*$/.test(line) || /^\s*#/.test(line)) {
|
|
348
|
+
continue;
|
|
349
|
+
}
|
|
350
|
+
// Array item: " - value"
|
|
351
|
+
const arrayMatch = line.match(/^(\s+)-\s+(.*)$/);
|
|
352
|
+
if (arrayMatch && currentKey !== null && currentArray !== null) {
|
|
353
|
+
const itemValue = coerceValue(arrayMatch[2]);
|
|
354
|
+
if (itemValue !== null) {
|
|
355
|
+
currentArray.push(itemValue);
|
|
356
|
+
}
|
|
357
|
+
continue;
|
|
358
|
+
}
|
|
359
|
+
// Key-value pair: "key: value"
|
|
360
|
+
const kvMatch = line.match(/^([a-zA-Z_][a-zA-Z0-9_-]*)\s*:\s*(.*)$/);
|
|
361
|
+
if (kvMatch) {
|
|
362
|
+
// Flush previous array if any
|
|
363
|
+
if (currentKey !== null && currentArray !== null) {
|
|
364
|
+
result[currentKey] = currentArray;
|
|
365
|
+
}
|
|
366
|
+
currentKey = kvMatch[1];
|
|
367
|
+
const rawValue = kvMatch[2].trim();
|
|
368
|
+
// Key with no value on this line -> start array
|
|
369
|
+
if (rawValue === '') {
|
|
370
|
+
currentArray = [];
|
|
371
|
+
}
|
|
372
|
+
else {
|
|
373
|
+
const value = coerceValue(rawValue);
|
|
374
|
+
result[currentKey] = value;
|
|
375
|
+
currentArray = null;
|
|
376
|
+
}
|
|
377
|
+
continue;
|
|
378
|
+
}
|
|
379
|
+
// Fallback: treat indented lines under an array key as array items
|
|
380
|
+
if (currentKey !== null && currentArray !== null) {
|
|
381
|
+
const stripped = line.trim();
|
|
382
|
+
if (stripped.startsWith('- ')) {
|
|
383
|
+
const itemValue = coerceValue(stripped.slice(2));
|
|
384
|
+
if (itemValue !== null) {
|
|
385
|
+
currentArray.push(itemValue);
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
// Flush last array
|
|
391
|
+
if (currentKey !== null && currentArray !== null) {
|
|
392
|
+
result[currentKey] = currentArray;
|
|
393
|
+
}
|
|
394
|
+
return result;
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
//# sourceMappingURL=custom-agents.js.map
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
export interface CustomToolDefinition {
|
|
2
|
+
name: string;
|
|
3
|
+
description: string;
|
|
4
|
+
parameters: {
|
|
5
|
+
type: 'object';
|
|
6
|
+
properties: Record<string, {
|
|
7
|
+
type: string;
|
|
8
|
+
description: string;
|
|
9
|
+
enum?: string[];
|
|
10
|
+
}>;
|
|
11
|
+
required: string[];
|
|
12
|
+
};
|
|
13
|
+
risk?: 'low' | 'medium' | 'high';
|
|
14
|
+
execute?: string;
|
|
15
|
+
command?: string;
|
|
16
|
+
source: string;
|
|
17
|
+
}
|
|
18
|
+
export declare class CustomToolLoader {
|
|
19
|
+
private tools;
|
|
20
|
+
private projectToolsDir;
|
|
21
|
+
private globalToolsDir;
|
|
22
|
+
constructor(projectRoot: string);
|
|
23
|
+
/** Discover and load all custom tools (global then project, project wins). */
|
|
24
|
+
discover(): CustomToolDefinition[];
|
|
25
|
+
/** Retrieve a single tool definition by name. */
|
|
26
|
+
get(name: string): CustomToolDefinition | undefined;
|
|
27
|
+
/** Return all discovered tool definitions, sorted by name. */
|
|
28
|
+
getAll(): CustomToolDefinition[];
|
|
29
|
+
/**
|
|
30
|
+
* Create an executor function for a tool definition.
|
|
31
|
+
*
|
|
32
|
+
* If the tool defines `execute`, the source is run inside a sandboxed
|
|
33
|
+
* function body with only safe globals. The `args` object is passed as
|
|
34
|
+
* the first argument and the executor must return a string.
|
|
35
|
+
*
|
|
36
|
+
* If the tool defines `command`, the template string has {{arg}} tokens
|
|
37
|
+
* replaced with the corresponding argument values and is executed via
|
|
38
|
+
* a restricted shell invocation.
|
|
39
|
+
*/
|
|
40
|
+
createExecutor(def: CustomToolDefinition): (args: Record<string, unknown>) => Promise<string>;
|
|
41
|
+
/** Print a human-readable list of all discovered custom tools. */
|
|
42
|
+
printTools(): void;
|
|
43
|
+
/** Scan a directory for .json, .js, .mjs, .ts tool definition files. */
|
|
44
|
+
private loadFromDirectory;
|
|
45
|
+
/**
|
|
46
|
+
* Load a tool definition from a JSON file.
|
|
47
|
+
*
|
|
48
|
+
* Expected format:
|
|
49
|
+
* {
|
|
50
|
+
* "name": "my-tool",
|
|
51
|
+
* "description": "Does something useful",
|
|
52
|
+
* "parameters": {
|
|
53
|
+
* "type": "object",
|
|
54
|
+
* "properties": { ... },
|
|
55
|
+
* "required": [ ... ]
|
|
56
|
+
* },
|
|
57
|
+
* "risk": "low",
|
|
58
|
+
* "command": "echo {{input}}"
|
|
59
|
+
* }
|
|
60
|
+
*/
|
|
61
|
+
private loadJsonTool;
|
|
62
|
+
/**
|
|
63
|
+
* Load a tool definition from a JS/TS file.
|
|
64
|
+
*
|
|
65
|
+
* The file should export a `default` or named `toolDefinition` object
|
|
66
|
+
* conforming to CustomToolDefinition (without `source`).
|
|
67
|
+
*
|
|
68
|
+
* Example (ESM):
|
|
69
|
+
* export const toolDefinition = { name: ..., ... };
|
|
70
|
+
* export default toolDefinition;
|
|
71
|
+
*
|
|
72
|
+
* Because we cannot natively import arbitrary ESM at runtime without
|
|
73
|
+
* a dynamic import that may fail on TS files, we take a pragmatic
|
|
74
|
+
* approach: read the file source and attempt to extract the exported
|
|
75
|
+
* definition via a sandboxed evaluation for .js/.mjs, or parse the
|
|
76
|
+
* object literal for .ts files.
|
|
77
|
+
*/
|
|
78
|
+
private loadJsTool;
|
|
79
|
+
/**
|
|
80
|
+
* Attempt to extract a tool definition object from source code text.
|
|
81
|
+
* Looks for common export patterns and parses the object literal.
|
|
82
|
+
*/
|
|
83
|
+
private extractDefinitionFromSource;
|
|
84
|
+
/**
|
|
85
|
+
* Strip TypeScript-specific syntax from an object literal string
|
|
86
|
+
* so it can be parsed as plain JavaScript.
|
|
87
|
+
*/
|
|
88
|
+
private sanitizeTsObjectLiteral;
|
|
89
|
+
/**
|
|
90
|
+
* Evaluate a JS source file in a sandboxed context and extract
|
|
91
|
+
* the exported tool definition.
|
|
92
|
+
*/
|
|
93
|
+
private evaluateJsSource;
|
|
94
|
+
/**
|
|
95
|
+
* Validate that a partial tool definition has all required fields
|
|
96
|
+
* and that their shapes are correct.
|
|
97
|
+
*/
|
|
98
|
+
private validateDefinition;
|
|
99
|
+
/**
|
|
100
|
+
* Create an executor that runs the tool's JavaScript source in a sandbox.
|
|
101
|
+
*
|
|
102
|
+
* The sandbox restricts access to only safe, deterministic globals.
|
|
103
|
+
* Network access, filesystem, and process APIs are not available.
|
|
104
|
+
*/
|
|
105
|
+
private createJsExecutor;
|
|
106
|
+
/**
|
|
107
|
+
* Create an executor that runs a shell command template.
|
|
108
|
+
*
|
|
109
|
+
* Template tokens like {{input}} are replaced with the corresponding
|
|
110
|
+
* argument values. Shell metacharacters in argument values are escaped
|
|
111
|
+
* to reduce injection risk.
|
|
112
|
+
*/
|
|
113
|
+
private createCommandExecutor;
|
|
114
|
+
/**
|
|
115
|
+
* Escape a string for safe inclusion in a shell command argument.
|
|
116
|
+
* Uses single-quoting with internal single-quotes escaped.
|
|
117
|
+
*/
|
|
118
|
+
private escapeShellArg;
|
|
119
|
+
}
|
|
120
|
+
//# sourceMappingURL=custom-tools.d.ts.map
|