@compilr-dev/cli 0.4.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 +110 -0
- package/dist/agent.d.ts +62 -0
- package/dist/agent.js +317 -0
- package/dist/agents/registry.d.ts +66 -0
- package/dist/agents/registry.js +238 -0
- package/dist/agents/types.d.ts +40 -0
- package/dist/agents/types.js +94 -0
- package/dist/commands/custom-registry.d.ts +69 -0
- package/dist/commands/custom-registry.js +246 -0
- package/dist/commands/index.d.ts +7 -0
- package/dist/commands/index.js +7 -0
- package/dist/commands/types.d.ts +31 -0
- package/dist/commands/types.js +26 -0
- package/dist/commands.d.ts +63 -0
- package/dist/commands.js +324 -0
- package/dist/db/index.d.ts +42 -0
- package/dist/db/index.js +146 -0
- package/dist/db/repositories/document-repository.d.ts +63 -0
- package/dist/db/repositories/document-repository.js +184 -0
- package/dist/db/repositories/index.d.ts +9 -0
- package/dist/db/repositories/index.js +6 -0
- package/dist/db/repositories/project-repository.d.ts +132 -0
- package/dist/db/repositories/project-repository.js +337 -0
- package/dist/db/repositories/work-item-repository.d.ts +115 -0
- package/dist/db/repositories/work-item-repository.js +389 -0
- package/dist/db/schema.d.ts +83 -0
- package/dist/db/schema.js +143 -0
- package/dist/debug.d.ts +8 -0
- package/dist/debug.js +48 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +348 -0
- package/dist/index.old.d.ts +7 -0
- package/dist/index.old.js +1014 -0
- package/dist/repl.d.ts +121 -0
- package/dist/repl.js +1878 -0
- package/dist/settings/index.d.ts +80 -0
- package/dist/settings/index.js +195 -0
- package/dist/shared-handlers.d.ts +63 -0
- package/dist/shared-handlers.js +57 -0
- package/dist/slash-autocomplete.d.ts +41 -0
- package/dist/slash-autocomplete.js +638 -0
- package/dist/state.d.ts +75 -0
- package/dist/state.js +130 -0
- package/dist/tabbed-menu.d.ts +11 -0
- package/dist/tabbed-menu.js +328 -0
- package/dist/templates/backlog-md.d.ts +7 -0
- package/dist/templates/backlog-md.js +94 -0
- package/dist/templates/claude-md.d.ts +7 -0
- package/dist/templates/claude-md.js +189 -0
- package/dist/templates/coding-standards.d.ts +7 -0
- package/dist/templates/coding-standards.js +299 -0
- package/dist/templates/compilr-md.d.ts +7 -0
- package/dist/templates/compilr-md.js +189 -0
- package/dist/templates/config-json.d.ts +38 -0
- package/dist/templates/config-json.js +39 -0
- package/dist/templates/gitignore.d.ts +7 -0
- package/dist/templates/gitignore.js +85 -0
- package/dist/templates/index.d.ts +19 -0
- package/dist/templates/index.js +302 -0
- package/dist/templates/package-json.d.ts +7 -0
- package/dist/templates/package-json.js +111 -0
- package/dist/templates/readme-md.d.ts +7 -0
- package/dist/templates/readme-md.js +161 -0
- package/dist/templates/tsconfig.d.ts +7 -0
- package/dist/templates/tsconfig.js +61 -0
- package/dist/templates/types.d.ts +33 -0
- package/dist/templates/types.js +24 -0
- package/dist/test-autocomplete.d.ts +7 -0
- package/dist/test-autocomplete.js +85 -0
- package/dist/test-tabbed-menu.d.ts +7 -0
- package/dist/test-tabbed-menu.js +25 -0
- package/dist/themes/colors.d.ts +49 -0
- package/dist/themes/colors.js +135 -0
- package/dist/themes/index.d.ts +23 -0
- package/dist/themes/index.js +24 -0
- package/dist/themes/registry.d.ts +60 -0
- package/dist/themes/registry.js +195 -0
- package/dist/themes/types.d.ts +82 -0
- package/dist/themes/types.js +7 -0
- package/dist/tool-selector.d.ts +71 -0
- package/dist/tool-selector.js +184 -0
- package/dist/tools/ask-user-simple.d.ts +19 -0
- package/dist/tools/ask-user-simple.js +86 -0
- package/dist/tools/ask-user.d.ts +32 -0
- package/dist/tools/ask-user.js +113 -0
- package/dist/tools/backlog.d.ts +53 -0
- package/dist/tools/backlog.js +709 -0
- package/dist/tools.d.ts +15 -0
- package/dist/tools.js +121 -0
- package/dist/ui/agents-overlay.d.ts +12 -0
- package/dist/ui/agents-overlay.js +501 -0
- package/dist/ui/arch-type-overlay.d.ts +20 -0
- package/dist/ui/arch-type-overlay.js +229 -0
- package/dist/ui/ask-user-overlay.d.ts +26 -0
- package/dist/ui/ask-user-overlay.js +647 -0
- package/dist/ui/ask-user-simple-overlay.d.ts +25 -0
- package/dist/ui/ask-user-simple-overlay.js +242 -0
- package/dist/ui/backlog-overlay.d.ts +17 -0
- package/dist/ui/backlog-overlay.js +786 -0
- package/dist/ui/commands-overlay.d.ts +11 -0
- package/dist/ui/commands-overlay.js +410 -0
- package/dist/ui/config-overlay.d.ts +34 -0
- package/dist/ui/config-overlay.js +977 -0
- package/dist/ui/conversation.d.ts +82 -0
- package/dist/ui/conversation.js +508 -0
- package/dist/ui/diff.d.ts +38 -0
- package/dist/ui/diff.js +182 -0
- package/dist/ui/ephemeral.d.ts +111 -0
- package/dist/ui/ephemeral.js +413 -0
- package/dist/ui/file-autocomplete.d.ts +45 -0
- package/dist/ui/file-autocomplete.js +237 -0
- package/dist/ui/footer.d.ts +153 -0
- package/dist/ui/footer.js +422 -0
- package/dist/ui/index.d.ts +12 -0
- package/dist/ui/index.js +15 -0
- package/dist/ui/init-overlay.d.ts +24 -0
- package/dist/ui/init-overlay.js +525 -0
- package/dist/ui/input-prompt-v2.d.ts +179 -0
- package/dist/ui/input-prompt-v2.js +991 -0
- package/dist/ui/input-prompt.d.ts +97 -0
- package/dist/ui/input-prompt.js +800 -0
- package/dist/ui/iteration-limit-overlay.d.ts +21 -0
- package/dist/ui/iteration-limit-overlay.js +150 -0
- package/dist/ui/keys-overlay.d.ts +14 -0
- package/dist/ui/keys-overlay.js +181 -0
- package/dist/ui/model-warning-overlay.d.ts +30 -0
- package/dist/ui/model-warning-overlay.js +171 -0
- package/dist/ui/overlay-controller.d.ts +25 -0
- package/dist/ui/overlay-controller.js +35 -0
- package/dist/ui/overlays.d.ts +47 -0
- package/dist/ui/overlays.js +627 -0
- package/dist/ui/permission-overlay.d.ts +16 -0
- package/dist/ui/permission-overlay.js +494 -0
- package/dist/ui/terminal.d.ts +117 -0
- package/dist/ui/terminal.js +237 -0
- package/dist/ui/todo-zone.d.ts +112 -0
- package/dist/ui/todo-zone.js +353 -0
- package/dist/ui/tools-overlay.d.ts +26 -0
- package/dist/ui/tools-overlay.js +278 -0
- package/dist/ui/tutorial-overlay.d.ts +10 -0
- package/dist/ui/tutorial-overlay.js +936 -0
- package/dist/ui/types.d.ts +103 -0
- package/dist/ui/types.js +33 -0
- package/dist/utils/credentials.d.ts +55 -0
- package/dist/utils/credentials.js +268 -0
- package/dist/utils/model-tiers.d.ts +37 -0
- package/dist/utils/model-tiers.js +118 -0
- package/dist/utils/project-memory.d.ts +47 -0
- package/dist/utils/project-memory.js +117 -0
- package/dist/utils/project-status.d.ts +56 -0
- package/dist/utils/project-status.js +237 -0
- package/package.json +66 -0
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Agent Registry
|
|
3
|
+
*
|
|
4
|
+
* Handles loading, saving, and managing agent definitions.
|
|
5
|
+
* Loads from both project (.compilr-dev/agents/) and user (~/.compilr-dev/agents/) directories.
|
|
6
|
+
*/
|
|
7
|
+
import * as fs from 'fs';
|
|
8
|
+
import * as path from 'path';
|
|
9
|
+
import { BUILTIN_AGENTS, PROJECT_AGENTS_DIR, USER_AGENTS_DIR, isValidAgentName, isBuiltinAgentName, } from './types.js';
|
|
10
|
+
/**
|
|
11
|
+
* Parse a markdown file with YAML frontmatter
|
|
12
|
+
*/
|
|
13
|
+
function parseAgentFile(content) {
|
|
14
|
+
const frontmatterMatch = content.match(/^---\n([\s\S]*?)\n---\n([\s\S]*)$/);
|
|
15
|
+
if (!frontmatterMatch) {
|
|
16
|
+
return null;
|
|
17
|
+
}
|
|
18
|
+
const yamlContent = frontmatterMatch[1];
|
|
19
|
+
const markdownContent = frontmatterMatch[2].trim();
|
|
20
|
+
// Simple YAML parsing (key: value)
|
|
21
|
+
const frontmatter = {};
|
|
22
|
+
for (const line of yamlContent.split('\n')) {
|
|
23
|
+
const match = line.match(/^(\w+):\s*(.*)$/);
|
|
24
|
+
if (match) {
|
|
25
|
+
frontmatter[match[1]] = match[2].trim();
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
return {
|
|
29
|
+
frontmatter: frontmatter,
|
|
30
|
+
content: markdownContent,
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Generate markdown content for an agent
|
|
35
|
+
*/
|
|
36
|
+
function generateAgentFile(agent) {
|
|
37
|
+
return `---
|
|
38
|
+
name: ${agent.name}
|
|
39
|
+
description: ${agent.description}
|
|
40
|
+
model: ${agent.model}
|
|
41
|
+
---
|
|
42
|
+
|
|
43
|
+
${agent.systemPrompt}
|
|
44
|
+
`;
|
|
45
|
+
}
|
|
46
|
+
// =============================================================================
|
|
47
|
+
// Agent Loading
|
|
48
|
+
// =============================================================================
|
|
49
|
+
/**
|
|
50
|
+
* Load agents from a directory
|
|
51
|
+
*/
|
|
52
|
+
function loadAgentsFromDir(dir, location) {
|
|
53
|
+
const agents = [];
|
|
54
|
+
if (!fs.existsSync(dir)) {
|
|
55
|
+
return agents;
|
|
56
|
+
}
|
|
57
|
+
const files = fs.readdirSync(dir).filter((f) => f.endsWith('.md'));
|
|
58
|
+
for (const file of files) {
|
|
59
|
+
const filePath = path.join(dir, file);
|
|
60
|
+
try {
|
|
61
|
+
const content = fs.readFileSync(filePath, 'utf-8');
|
|
62
|
+
const parsed = parseAgentFile(content);
|
|
63
|
+
if (!parsed) {
|
|
64
|
+
console.warn(`Invalid agent file (no frontmatter): ${filePath}`);
|
|
65
|
+
continue;
|
|
66
|
+
}
|
|
67
|
+
const { frontmatter, content: systemPrompt } = parsed;
|
|
68
|
+
if (!frontmatter.name || !frontmatter.description) {
|
|
69
|
+
console.warn(`Invalid agent file (missing fields): ${filePath}`);
|
|
70
|
+
continue;
|
|
71
|
+
}
|
|
72
|
+
if (!isValidAgentName(frontmatter.name)) {
|
|
73
|
+
console.warn(`Invalid agent name: ${frontmatter.name} in ${filePath}`);
|
|
74
|
+
continue;
|
|
75
|
+
}
|
|
76
|
+
const model = (['sonnet', 'opus', 'haiku', 'inherit'].includes(frontmatter.model || '')
|
|
77
|
+
? frontmatter.model
|
|
78
|
+
: 'inherit');
|
|
79
|
+
agents.push({
|
|
80
|
+
name: frontmatter.name,
|
|
81
|
+
description: frontmatter.description,
|
|
82
|
+
model,
|
|
83
|
+
systemPrompt,
|
|
84
|
+
location,
|
|
85
|
+
filePath,
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
catch (error) {
|
|
89
|
+
console.warn(`Failed to load agent file: ${filePath}`, error);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
return agents;
|
|
93
|
+
}
|
|
94
|
+
// =============================================================================
|
|
95
|
+
// Registry Class
|
|
96
|
+
// =============================================================================
|
|
97
|
+
export class AgentRegistry {
|
|
98
|
+
projectDir;
|
|
99
|
+
userDir;
|
|
100
|
+
customAgents = [];
|
|
101
|
+
constructor(projectRoot = process.cwd()) {
|
|
102
|
+
this.projectDir = path.join(projectRoot, PROJECT_AGENTS_DIR);
|
|
103
|
+
this.userDir = USER_AGENTS_DIR;
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* Load all custom agents from project and user directories
|
|
107
|
+
*/
|
|
108
|
+
load() {
|
|
109
|
+
const projectAgents = loadAgentsFromDir(this.projectDir, 'project');
|
|
110
|
+
const userAgents = loadAgentsFromDir(this.userDir, 'personal');
|
|
111
|
+
// Project agents take precedence over user agents with same name
|
|
112
|
+
const seen = new Set();
|
|
113
|
+
this.customAgents = [];
|
|
114
|
+
for (const agent of projectAgents) {
|
|
115
|
+
if (!seen.has(agent.name)) {
|
|
116
|
+
this.customAgents.push(agent);
|
|
117
|
+
seen.add(agent.name);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
for (const agent of userAgents) {
|
|
121
|
+
if (!seen.has(agent.name)) {
|
|
122
|
+
this.customAgents.push(agent);
|
|
123
|
+
seen.add(agent.name);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* Get all built-in agents
|
|
129
|
+
*/
|
|
130
|
+
getBuiltinAgents() {
|
|
131
|
+
return [...BUILTIN_AGENTS];
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* Get all custom agents
|
|
135
|
+
*/
|
|
136
|
+
getCustomAgents() {
|
|
137
|
+
return [...this.customAgents];
|
|
138
|
+
}
|
|
139
|
+
/**
|
|
140
|
+
* Get all agents (built-in + custom)
|
|
141
|
+
*/
|
|
142
|
+
getAllAgents() {
|
|
143
|
+
return [...BUILTIN_AGENTS, ...this.customAgents];
|
|
144
|
+
}
|
|
145
|
+
/**
|
|
146
|
+
* Get an agent by name
|
|
147
|
+
*/
|
|
148
|
+
getAgent(name) {
|
|
149
|
+
// Check custom first (they can override built-in in the future)
|
|
150
|
+
const custom = this.customAgents.find((a) => a.name === name);
|
|
151
|
+
if (custom)
|
|
152
|
+
return custom;
|
|
153
|
+
return BUILTIN_AGENTS.find((a) => a.name === name);
|
|
154
|
+
}
|
|
155
|
+
/**
|
|
156
|
+
* Check if an agent name exists
|
|
157
|
+
*/
|
|
158
|
+
hasAgent(name) {
|
|
159
|
+
return this.getAgent(name) !== undefined;
|
|
160
|
+
}
|
|
161
|
+
/**
|
|
162
|
+
* Save a new custom agent
|
|
163
|
+
*/
|
|
164
|
+
saveAgent(agent, location) {
|
|
165
|
+
// Validate name
|
|
166
|
+
if (!isValidAgentName(agent.name)) {
|
|
167
|
+
throw new Error(`Invalid agent name: ${agent.name}. Use lowercase letters, numbers, and hyphens.`);
|
|
168
|
+
}
|
|
169
|
+
if (isBuiltinAgentName(agent.name)) {
|
|
170
|
+
throw new Error(`Cannot use built-in agent name: ${agent.name}`);
|
|
171
|
+
}
|
|
172
|
+
// Determine directory
|
|
173
|
+
const dir = location === 'project' ? this.projectDir : this.userDir;
|
|
174
|
+
// Ensure directory exists
|
|
175
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
176
|
+
// Generate file content
|
|
177
|
+
const content = generateAgentFile(agent);
|
|
178
|
+
const filePath = path.join(dir, `${agent.name}.md`);
|
|
179
|
+
// Check if already exists
|
|
180
|
+
if (fs.existsSync(filePath)) {
|
|
181
|
+
throw new Error(`Agent already exists: ${filePath}`);
|
|
182
|
+
}
|
|
183
|
+
// Write file
|
|
184
|
+
fs.writeFileSync(filePath, content, 'utf-8');
|
|
185
|
+
// Reload to update internal state
|
|
186
|
+
this.load();
|
|
187
|
+
return filePath;
|
|
188
|
+
}
|
|
189
|
+
/**
|
|
190
|
+
* Delete a custom agent
|
|
191
|
+
*/
|
|
192
|
+
deleteAgent(name) {
|
|
193
|
+
const agent = this.customAgents.find((a) => a.name === name);
|
|
194
|
+
if (!agent || !agent.filePath) {
|
|
195
|
+
return false;
|
|
196
|
+
}
|
|
197
|
+
try {
|
|
198
|
+
fs.unlinkSync(agent.filePath);
|
|
199
|
+
this.load();
|
|
200
|
+
return true;
|
|
201
|
+
}
|
|
202
|
+
catch {
|
|
203
|
+
return false;
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
/**
|
|
207
|
+
* Get the project agents directory
|
|
208
|
+
*/
|
|
209
|
+
getProjectDir() {
|
|
210
|
+
return this.projectDir;
|
|
211
|
+
}
|
|
212
|
+
/**
|
|
213
|
+
* Get the user agents directory
|
|
214
|
+
*/
|
|
215
|
+
getUserDir() {
|
|
216
|
+
return this.userDir;
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
// =============================================================================
|
|
220
|
+
// Singleton Instance
|
|
221
|
+
// =============================================================================
|
|
222
|
+
let registryInstance = null;
|
|
223
|
+
/**
|
|
224
|
+
* Get the global agent registry instance
|
|
225
|
+
*/
|
|
226
|
+
export function getAgentRegistry() {
|
|
227
|
+
if (!registryInstance) {
|
|
228
|
+
registryInstance = new AgentRegistry();
|
|
229
|
+
registryInstance.load();
|
|
230
|
+
}
|
|
231
|
+
return registryInstance;
|
|
232
|
+
}
|
|
233
|
+
/**
|
|
234
|
+
* Reset the registry (for testing)
|
|
235
|
+
*/
|
|
236
|
+
export function resetAgentRegistry() {
|
|
237
|
+
registryInstance = null;
|
|
238
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Agent Type Definitions
|
|
3
|
+
*
|
|
4
|
+
* Types for built-in and custom agent configurations.
|
|
5
|
+
*/
|
|
6
|
+
export type AgentModel = 'sonnet' | 'opus' | 'haiku' | 'inherit';
|
|
7
|
+
export type AgentLocation = 'project' | 'personal' | 'builtin';
|
|
8
|
+
export interface AgentDefinition {
|
|
9
|
+
/** Unique identifier (lowercase, hyphens allowed) */
|
|
10
|
+
name: string;
|
|
11
|
+
/** Description of when to use this agent */
|
|
12
|
+
description: string;
|
|
13
|
+
/** Model to use */
|
|
14
|
+
model: AgentModel;
|
|
15
|
+
/** System prompt (for custom agents) */
|
|
16
|
+
systemPrompt?: string;
|
|
17
|
+
/** Where this agent is defined */
|
|
18
|
+
location: AgentLocation;
|
|
19
|
+
/** File path (for custom agents) */
|
|
20
|
+
filePath?: string;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Built-in agents from the @compilr-dev/agents library.
|
|
24
|
+
* These are available via the Task tool.
|
|
25
|
+
*/
|
|
26
|
+
export declare const BUILTIN_AGENTS: AgentDefinition[];
|
|
27
|
+
/** Project-level agents folder */
|
|
28
|
+
export declare const PROJECT_AGENTS_DIR = ".compilr-dev/agents";
|
|
29
|
+
/** User-level agents folder */
|
|
30
|
+
export declare const USER_AGENTS_DIR: string;
|
|
31
|
+
/** Valid agent name pattern: lowercase letters, numbers, hyphens */
|
|
32
|
+
export declare const AGENT_NAME_PATTERN: RegExp;
|
|
33
|
+
/**
|
|
34
|
+
* Validate an agent name
|
|
35
|
+
*/
|
|
36
|
+
export declare function isValidAgentName(name: string): boolean;
|
|
37
|
+
/**
|
|
38
|
+
* Check if a name conflicts with built-in agents
|
|
39
|
+
*/
|
|
40
|
+
export declare function isBuiltinAgentName(name: string): boolean;
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Agent Type Definitions
|
|
3
|
+
*
|
|
4
|
+
* Types for built-in and custom agent configurations.
|
|
5
|
+
*/
|
|
6
|
+
// =============================================================================
|
|
7
|
+
// Built-in Agents
|
|
8
|
+
// =============================================================================
|
|
9
|
+
/**
|
|
10
|
+
* Built-in agents from the @compilr-dev/agents library.
|
|
11
|
+
* These are available via the Task tool.
|
|
12
|
+
*/
|
|
13
|
+
export const BUILTIN_AGENTS = [
|
|
14
|
+
{
|
|
15
|
+
name: 'explore',
|
|
16
|
+
description: 'Fast codebase exploration and search',
|
|
17
|
+
model: 'haiku',
|
|
18
|
+
location: 'builtin',
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
name: 'code-review',
|
|
22
|
+
description: 'Review code for bugs and best practices',
|
|
23
|
+
model: 'sonnet',
|
|
24
|
+
location: 'builtin',
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
name: 'general',
|
|
28
|
+
description: 'General-purpose multi-step tasks',
|
|
29
|
+
model: 'sonnet',
|
|
30
|
+
location: 'builtin',
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
name: 'plan',
|
|
34
|
+
description: 'Create implementation plans',
|
|
35
|
+
model: 'inherit',
|
|
36
|
+
location: 'builtin',
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
name: 'test-runner',
|
|
40
|
+
description: 'Run and analyze test suites',
|
|
41
|
+
model: 'sonnet',
|
|
42
|
+
location: 'builtin',
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
name: 'doc-lookup',
|
|
46
|
+
description: 'Search documentation',
|
|
47
|
+
model: 'haiku',
|
|
48
|
+
location: 'builtin',
|
|
49
|
+
},
|
|
50
|
+
{
|
|
51
|
+
name: 'refactor',
|
|
52
|
+
description: 'Refactor code for clarity',
|
|
53
|
+
model: 'sonnet',
|
|
54
|
+
location: 'builtin',
|
|
55
|
+
},
|
|
56
|
+
{
|
|
57
|
+
name: 'security-audit',
|
|
58
|
+
description: 'Security vulnerability analysis',
|
|
59
|
+
model: 'sonnet',
|
|
60
|
+
location: 'builtin',
|
|
61
|
+
},
|
|
62
|
+
{
|
|
63
|
+
name: 'debug',
|
|
64
|
+
description: 'Debug and fix issues',
|
|
65
|
+
model: 'sonnet',
|
|
66
|
+
location: 'builtin',
|
|
67
|
+
},
|
|
68
|
+
];
|
|
69
|
+
// =============================================================================
|
|
70
|
+
// Folder Paths
|
|
71
|
+
// =============================================================================
|
|
72
|
+
import * as os from 'os';
|
|
73
|
+
import * as path from 'path';
|
|
74
|
+
/** Project-level agents folder */
|
|
75
|
+
export const PROJECT_AGENTS_DIR = '.compilr-dev/agents';
|
|
76
|
+
/** User-level agents folder */
|
|
77
|
+
export const USER_AGENTS_DIR = path.join(os.homedir(), '.compilr-dev', 'agents');
|
|
78
|
+
// =============================================================================
|
|
79
|
+
// Validation
|
|
80
|
+
// =============================================================================
|
|
81
|
+
/** Valid agent name pattern: lowercase letters, numbers, hyphens */
|
|
82
|
+
export const AGENT_NAME_PATTERN = /^[a-z][a-z0-9-]{1,49}$/;
|
|
83
|
+
/**
|
|
84
|
+
* Validate an agent name
|
|
85
|
+
*/
|
|
86
|
+
export function isValidAgentName(name) {
|
|
87
|
+
return AGENT_NAME_PATTERN.test(name);
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Check if a name conflicts with built-in agents
|
|
91
|
+
*/
|
|
92
|
+
export function isBuiltinAgentName(name) {
|
|
93
|
+
return BUILTIN_AGENTS.some((agent) => agent.name === name);
|
|
94
|
+
}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Custom Command Registry
|
|
3
|
+
*
|
|
4
|
+
* Handles loading, saving, and managing custom command definitions.
|
|
5
|
+
* Loads from both project (.compilr-dev/commands/) and user (~/.compilr-dev/commands/) directories.
|
|
6
|
+
*/
|
|
7
|
+
import { CustomCommand } from './types.js';
|
|
8
|
+
/**
|
|
9
|
+
* Expand a command prompt with arguments
|
|
10
|
+
*
|
|
11
|
+
* Supports:
|
|
12
|
+
* - $1, $2, $3... - Positional arguments
|
|
13
|
+
* - $ARGUMENTS - All arguments as a single string
|
|
14
|
+
*/
|
|
15
|
+
export declare function expandArguments(prompt: string, args: string[]): string;
|
|
16
|
+
export declare class CustomCommandRegistry {
|
|
17
|
+
private readonly projectDir;
|
|
18
|
+
private readonly userDir;
|
|
19
|
+
private commands;
|
|
20
|
+
constructor(projectRoot?: string);
|
|
21
|
+
/**
|
|
22
|
+
* Load all custom commands from project and user directories
|
|
23
|
+
*/
|
|
24
|
+
load(): void;
|
|
25
|
+
/**
|
|
26
|
+
* Get all custom commands
|
|
27
|
+
*/
|
|
28
|
+
getAll(): CustomCommand[];
|
|
29
|
+
/**
|
|
30
|
+
* Get a command by name
|
|
31
|
+
*/
|
|
32
|
+
get(name: string): CustomCommand | undefined;
|
|
33
|
+
/**
|
|
34
|
+
* Check if a command exists
|
|
35
|
+
*/
|
|
36
|
+
has(name: string): boolean;
|
|
37
|
+
/**
|
|
38
|
+
* Expand a command with arguments
|
|
39
|
+
*/
|
|
40
|
+
expand(name: string, args: string[]): string | undefined;
|
|
41
|
+
/**
|
|
42
|
+
* Save a new custom command
|
|
43
|
+
*/
|
|
44
|
+
save(command: {
|
|
45
|
+
name: string;
|
|
46
|
+
description: string;
|
|
47
|
+
prompt: string;
|
|
48
|
+
}, location: 'project' | 'personal'): string;
|
|
49
|
+
/**
|
|
50
|
+
* Delete a custom command
|
|
51
|
+
*/
|
|
52
|
+
delete(name: string): boolean;
|
|
53
|
+
/**
|
|
54
|
+
* Get the project commands directory
|
|
55
|
+
*/
|
|
56
|
+
getProjectDir(): string;
|
|
57
|
+
/**
|
|
58
|
+
* Get the user commands directory
|
|
59
|
+
*/
|
|
60
|
+
getUserDir(): string;
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Get the global custom command registry instance
|
|
64
|
+
*/
|
|
65
|
+
export declare function getCustomCommandRegistry(): CustomCommandRegistry;
|
|
66
|
+
/**
|
|
67
|
+
* Reset the registry (for testing)
|
|
68
|
+
*/
|
|
69
|
+
export declare function resetCustomCommandRegistry(): void;
|
|
@@ -0,0 +1,246 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Custom Command Registry
|
|
3
|
+
*
|
|
4
|
+
* Handles loading, saving, and managing custom command definitions.
|
|
5
|
+
* Loads from both project (.compilr-dev/commands/) and user (~/.compilr-dev/commands/) directories.
|
|
6
|
+
*/
|
|
7
|
+
import * as fs from 'fs';
|
|
8
|
+
import * as path from 'path';
|
|
9
|
+
import { PROJECT_COMMANDS_DIR, USER_COMMANDS_DIR, isValidCommandName, } from './types.js';
|
|
10
|
+
/**
|
|
11
|
+
* Parse a markdown file with optional YAML frontmatter
|
|
12
|
+
*/
|
|
13
|
+
function parseCommandFile(content) {
|
|
14
|
+
const frontmatterMatch = content.match(/^---\n([\s\S]*?)\n---\n([\s\S]*)$/);
|
|
15
|
+
if (!frontmatterMatch) {
|
|
16
|
+
// No frontmatter, treat entire content as prompt
|
|
17
|
+
return {
|
|
18
|
+
frontmatter: {},
|
|
19
|
+
content: content.trim(),
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
const yamlContent = frontmatterMatch[1];
|
|
23
|
+
const markdownContent = frontmatterMatch[2].trim();
|
|
24
|
+
// Simple YAML parsing (key: value)
|
|
25
|
+
const frontmatter = {};
|
|
26
|
+
for (const line of yamlContent.split('\n')) {
|
|
27
|
+
const match = line.match(/^(\w+):\s*["']?(.*)["']?$/);
|
|
28
|
+
if (match) {
|
|
29
|
+
// Remove surrounding quotes if present
|
|
30
|
+
frontmatter[match[1]] = match[2].replace(/^["']|["']$/g, '').trim();
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
return {
|
|
34
|
+
frontmatter: frontmatter,
|
|
35
|
+
content: markdownContent,
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Generate markdown content for a command
|
|
40
|
+
*/
|
|
41
|
+
function generateCommandFile(command) {
|
|
42
|
+
return `---
|
|
43
|
+
description: "${command.description}"
|
|
44
|
+
---
|
|
45
|
+
|
|
46
|
+
${command.prompt}
|
|
47
|
+
`;
|
|
48
|
+
}
|
|
49
|
+
// =============================================================================
|
|
50
|
+
// Argument Substitution
|
|
51
|
+
// =============================================================================
|
|
52
|
+
/**
|
|
53
|
+
* Expand a command prompt with arguments
|
|
54
|
+
*
|
|
55
|
+
* Supports:
|
|
56
|
+
* - $1, $2, $3... - Positional arguments
|
|
57
|
+
* - $ARGUMENTS - All arguments as a single string
|
|
58
|
+
*/
|
|
59
|
+
export function expandArguments(prompt, args) {
|
|
60
|
+
let result = prompt;
|
|
61
|
+
// Replace $ARGUMENTS with all args joined
|
|
62
|
+
result = result.replace(/\$ARGUMENTS/g, args.join(' '));
|
|
63
|
+
// Replace $1, $2, etc. with positional args
|
|
64
|
+
for (let i = 0; i < args.length; i++) {
|
|
65
|
+
result = result.replace(new RegExp(`\\$${String(i + 1)}`, 'g'), args[i]);
|
|
66
|
+
}
|
|
67
|
+
// Remove unreplaced positional args (e.g., $3 when only 2 args given)
|
|
68
|
+
result = result.replace(/\$\d+/g, '');
|
|
69
|
+
return result.trim();
|
|
70
|
+
}
|
|
71
|
+
// =============================================================================
|
|
72
|
+
// Command Loading
|
|
73
|
+
// =============================================================================
|
|
74
|
+
/**
|
|
75
|
+
* Load commands from a directory
|
|
76
|
+
*/
|
|
77
|
+
function loadCommandsFromDir(dir, location) {
|
|
78
|
+
const commands = [];
|
|
79
|
+
if (!fs.existsSync(dir)) {
|
|
80
|
+
return commands;
|
|
81
|
+
}
|
|
82
|
+
const files = fs.readdirSync(dir).filter((f) => f.endsWith('.md'));
|
|
83
|
+
for (const file of files) {
|
|
84
|
+
const filePath = path.join(dir, file);
|
|
85
|
+
try {
|
|
86
|
+
const content = fs.readFileSync(filePath, 'utf-8');
|
|
87
|
+
const parsed = parseCommandFile(content);
|
|
88
|
+
// Name from filename (without .md extension)
|
|
89
|
+
const name = path.basename(file, '.md');
|
|
90
|
+
if (!isValidCommandName(name)) {
|
|
91
|
+
console.warn(`Invalid command name: ${name} in ${filePath}`);
|
|
92
|
+
continue;
|
|
93
|
+
}
|
|
94
|
+
if (!parsed.content) {
|
|
95
|
+
console.warn(`Empty command file: ${filePath}`);
|
|
96
|
+
continue;
|
|
97
|
+
}
|
|
98
|
+
commands.push({
|
|
99
|
+
name,
|
|
100
|
+
description: parsed.frontmatter.description || 'No description',
|
|
101
|
+
prompt: parsed.content,
|
|
102
|
+
location,
|
|
103
|
+
filePath,
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
catch (error) {
|
|
107
|
+
console.warn(`Failed to load command file: ${filePath}`, error);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
return commands;
|
|
111
|
+
}
|
|
112
|
+
// =============================================================================
|
|
113
|
+
// Registry Class
|
|
114
|
+
// =============================================================================
|
|
115
|
+
export class CustomCommandRegistry {
|
|
116
|
+
projectDir;
|
|
117
|
+
userDir;
|
|
118
|
+
commands = [];
|
|
119
|
+
constructor(projectRoot = process.cwd()) {
|
|
120
|
+
this.projectDir = path.join(projectRoot, PROJECT_COMMANDS_DIR);
|
|
121
|
+
this.userDir = USER_COMMANDS_DIR;
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* Load all custom commands from project and user directories
|
|
125
|
+
*/
|
|
126
|
+
load() {
|
|
127
|
+
const projectCommands = loadCommandsFromDir(this.projectDir, 'project');
|
|
128
|
+
const userCommands = loadCommandsFromDir(this.userDir, 'personal');
|
|
129
|
+
// Project commands take precedence over user commands with same name
|
|
130
|
+
const seen = new Set();
|
|
131
|
+
this.commands = [];
|
|
132
|
+
for (const command of projectCommands) {
|
|
133
|
+
if (!seen.has(command.name)) {
|
|
134
|
+
this.commands.push(command);
|
|
135
|
+
seen.add(command.name);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
for (const command of userCommands) {
|
|
139
|
+
if (!seen.has(command.name)) {
|
|
140
|
+
this.commands.push(command);
|
|
141
|
+
seen.add(command.name);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
/**
|
|
146
|
+
* Get all custom commands
|
|
147
|
+
*/
|
|
148
|
+
getAll() {
|
|
149
|
+
return [...this.commands];
|
|
150
|
+
}
|
|
151
|
+
/**
|
|
152
|
+
* Get a command by name
|
|
153
|
+
*/
|
|
154
|
+
get(name) {
|
|
155
|
+
return this.commands.find((c) => c.name === name);
|
|
156
|
+
}
|
|
157
|
+
/**
|
|
158
|
+
* Check if a command exists
|
|
159
|
+
*/
|
|
160
|
+
has(name) {
|
|
161
|
+
return this.get(name) !== undefined;
|
|
162
|
+
}
|
|
163
|
+
/**
|
|
164
|
+
* Expand a command with arguments
|
|
165
|
+
*/
|
|
166
|
+
expand(name, args) {
|
|
167
|
+
const command = this.get(name);
|
|
168
|
+
if (!command)
|
|
169
|
+
return undefined;
|
|
170
|
+
return expandArguments(command.prompt, args);
|
|
171
|
+
}
|
|
172
|
+
/**
|
|
173
|
+
* Save a new custom command
|
|
174
|
+
*/
|
|
175
|
+
save(command, location) {
|
|
176
|
+
// Validate name
|
|
177
|
+
if (!isValidCommandName(command.name)) {
|
|
178
|
+
throw new Error(`Invalid command name: ${command.name}. Use lowercase letters, numbers, and hyphens (2-30 chars, must start with letter).`);
|
|
179
|
+
}
|
|
180
|
+
// Determine directory
|
|
181
|
+
const dir = location === 'project' ? this.projectDir : this.userDir;
|
|
182
|
+
// Ensure directory exists
|
|
183
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
184
|
+
// Generate file content
|
|
185
|
+
const content = generateCommandFile(command);
|
|
186
|
+
const filePath = path.join(dir, `${command.name}.md`);
|
|
187
|
+
// Check if already exists
|
|
188
|
+
if (fs.existsSync(filePath)) {
|
|
189
|
+
throw new Error(`Command already exists: ${filePath}`);
|
|
190
|
+
}
|
|
191
|
+
// Write file
|
|
192
|
+
fs.writeFileSync(filePath, content, 'utf-8');
|
|
193
|
+
// Reload to update internal state
|
|
194
|
+
this.load();
|
|
195
|
+
return filePath;
|
|
196
|
+
}
|
|
197
|
+
/**
|
|
198
|
+
* Delete a custom command
|
|
199
|
+
*/
|
|
200
|
+
delete(name) {
|
|
201
|
+
const command = this.commands.find((c) => c.name === name);
|
|
202
|
+
if (!command || !command.filePath) {
|
|
203
|
+
return false;
|
|
204
|
+
}
|
|
205
|
+
try {
|
|
206
|
+
fs.unlinkSync(command.filePath);
|
|
207
|
+
this.load();
|
|
208
|
+
return true;
|
|
209
|
+
}
|
|
210
|
+
catch {
|
|
211
|
+
return false;
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
/**
|
|
215
|
+
* Get the project commands directory
|
|
216
|
+
*/
|
|
217
|
+
getProjectDir() {
|
|
218
|
+
return this.projectDir;
|
|
219
|
+
}
|
|
220
|
+
/**
|
|
221
|
+
* Get the user commands directory
|
|
222
|
+
*/
|
|
223
|
+
getUserDir() {
|
|
224
|
+
return this.userDir;
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
// =============================================================================
|
|
228
|
+
// Singleton Instance
|
|
229
|
+
// =============================================================================
|
|
230
|
+
let registryInstance = null;
|
|
231
|
+
/**
|
|
232
|
+
* Get the global custom command registry instance
|
|
233
|
+
*/
|
|
234
|
+
export function getCustomCommandRegistry() {
|
|
235
|
+
if (!registryInstance) {
|
|
236
|
+
registryInstance = new CustomCommandRegistry();
|
|
237
|
+
registryInstance.load();
|
|
238
|
+
}
|
|
239
|
+
return registryInstance;
|
|
240
|
+
}
|
|
241
|
+
/**
|
|
242
|
+
* Reset the registry (for testing)
|
|
243
|
+
*/
|
|
244
|
+
export function resetCustomCommandRegistry() {
|
|
245
|
+
registryInstance = null;
|
|
246
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Custom Commands Module
|
|
3
|
+
*
|
|
4
|
+
* Exports custom command types and registry.
|
|
5
|
+
*/
|
|
6
|
+
export { CustomCommand, CommandLocation, PROJECT_COMMANDS_DIR, USER_COMMANDS_DIR, isValidCommandName, } from './types.js';
|
|
7
|
+
export { CustomCommandRegistry, getCustomCommandRegistry, resetCustomCommandRegistry, expandArguments, } from './custom-registry.js';
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Custom Commands Module
|
|
3
|
+
*
|
|
4
|
+
* Exports custom command types and registry.
|
|
5
|
+
*/
|
|
6
|
+
export { PROJECT_COMMANDS_DIR, USER_COMMANDS_DIR, isValidCommandName, } from './types.js';
|
|
7
|
+
export { CustomCommandRegistry, getCustomCommandRegistry, resetCustomCommandRegistry, expandArguments, } from './custom-registry.js';
|