@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.
Files changed (152) hide show
  1. package/README.md +110 -0
  2. package/dist/agent.d.ts +62 -0
  3. package/dist/agent.js +317 -0
  4. package/dist/agents/registry.d.ts +66 -0
  5. package/dist/agents/registry.js +238 -0
  6. package/dist/agents/types.d.ts +40 -0
  7. package/dist/agents/types.js +94 -0
  8. package/dist/commands/custom-registry.d.ts +69 -0
  9. package/dist/commands/custom-registry.js +246 -0
  10. package/dist/commands/index.d.ts +7 -0
  11. package/dist/commands/index.js +7 -0
  12. package/dist/commands/types.d.ts +31 -0
  13. package/dist/commands/types.js +26 -0
  14. package/dist/commands.d.ts +63 -0
  15. package/dist/commands.js +324 -0
  16. package/dist/db/index.d.ts +42 -0
  17. package/dist/db/index.js +146 -0
  18. package/dist/db/repositories/document-repository.d.ts +63 -0
  19. package/dist/db/repositories/document-repository.js +184 -0
  20. package/dist/db/repositories/index.d.ts +9 -0
  21. package/dist/db/repositories/index.js +6 -0
  22. package/dist/db/repositories/project-repository.d.ts +132 -0
  23. package/dist/db/repositories/project-repository.js +337 -0
  24. package/dist/db/repositories/work-item-repository.d.ts +115 -0
  25. package/dist/db/repositories/work-item-repository.js +389 -0
  26. package/dist/db/schema.d.ts +83 -0
  27. package/dist/db/schema.js +143 -0
  28. package/dist/debug.d.ts +8 -0
  29. package/dist/debug.js +48 -0
  30. package/dist/index.d.ts +2 -0
  31. package/dist/index.js +348 -0
  32. package/dist/index.old.d.ts +7 -0
  33. package/dist/index.old.js +1014 -0
  34. package/dist/repl.d.ts +121 -0
  35. package/dist/repl.js +1878 -0
  36. package/dist/settings/index.d.ts +80 -0
  37. package/dist/settings/index.js +195 -0
  38. package/dist/shared-handlers.d.ts +63 -0
  39. package/dist/shared-handlers.js +57 -0
  40. package/dist/slash-autocomplete.d.ts +41 -0
  41. package/dist/slash-autocomplete.js +638 -0
  42. package/dist/state.d.ts +75 -0
  43. package/dist/state.js +130 -0
  44. package/dist/tabbed-menu.d.ts +11 -0
  45. package/dist/tabbed-menu.js +328 -0
  46. package/dist/templates/backlog-md.d.ts +7 -0
  47. package/dist/templates/backlog-md.js +94 -0
  48. package/dist/templates/claude-md.d.ts +7 -0
  49. package/dist/templates/claude-md.js +189 -0
  50. package/dist/templates/coding-standards.d.ts +7 -0
  51. package/dist/templates/coding-standards.js +299 -0
  52. package/dist/templates/compilr-md.d.ts +7 -0
  53. package/dist/templates/compilr-md.js +189 -0
  54. package/dist/templates/config-json.d.ts +38 -0
  55. package/dist/templates/config-json.js +39 -0
  56. package/dist/templates/gitignore.d.ts +7 -0
  57. package/dist/templates/gitignore.js +85 -0
  58. package/dist/templates/index.d.ts +19 -0
  59. package/dist/templates/index.js +302 -0
  60. package/dist/templates/package-json.d.ts +7 -0
  61. package/dist/templates/package-json.js +111 -0
  62. package/dist/templates/readme-md.d.ts +7 -0
  63. package/dist/templates/readme-md.js +161 -0
  64. package/dist/templates/tsconfig.d.ts +7 -0
  65. package/dist/templates/tsconfig.js +61 -0
  66. package/dist/templates/types.d.ts +33 -0
  67. package/dist/templates/types.js +24 -0
  68. package/dist/test-autocomplete.d.ts +7 -0
  69. package/dist/test-autocomplete.js +85 -0
  70. package/dist/test-tabbed-menu.d.ts +7 -0
  71. package/dist/test-tabbed-menu.js +25 -0
  72. package/dist/themes/colors.d.ts +49 -0
  73. package/dist/themes/colors.js +135 -0
  74. package/dist/themes/index.d.ts +23 -0
  75. package/dist/themes/index.js +24 -0
  76. package/dist/themes/registry.d.ts +60 -0
  77. package/dist/themes/registry.js +195 -0
  78. package/dist/themes/types.d.ts +82 -0
  79. package/dist/themes/types.js +7 -0
  80. package/dist/tool-selector.d.ts +71 -0
  81. package/dist/tool-selector.js +184 -0
  82. package/dist/tools/ask-user-simple.d.ts +19 -0
  83. package/dist/tools/ask-user-simple.js +86 -0
  84. package/dist/tools/ask-user.d.ts +32 -0
  85. package/dist/tools/ask-user.js +113 -0
  86. package/dist/tools/backlog.d.ts +53 -0
  87. package/dist/tools/backlog.js +709 -0
  88. package/dist/tools.d.ts +15 -0
  89. package/dist/tools.js +121 -0
  90. package/dist/ui/agents-overlay.d.ts +12 -0
  91. package/dist/ui/agents-overlay.js +501 -0
  92. package/dist/ui/arch-type-overlay.d.ts +20 -0
  93. package/dist/ui/arch-type-overlay.js +229 -0
  94. package/dist/ui/ask-user-overlay.d.ts +26 -0
  95. package/dist/ui/ask-user-overlay.js +647 -0
  96. package/dist/ui/ask-user-simple-overlay.d.ts +25 -0
  97. package/dist/ui/ask-user-simple-overlay.js +242 -0
  98. package/dist/ui/backlog-overlay.d.ts +17 -0
  99. package/dist/ui/backlog-overlay.js +786 -0
  100. package/dist/ui/commands-overlay.d.ts +11 -0
  101. package/dist/ui/commands-overlay.js +410 -0
  102. package/dist/ui/config-overlay.d.ts +34 -0
  103. package/dist/ui/config-overlay.js +977 -0
  104. package/dist/ui/conversation.d.ts +82 -0
  105. package/dist/ui/conversation.js +508 -0
  106. package/dist/ui/diff.d.ts +38 -0
  107. package/dist/ui/diff.js +182 -0
  108. package/dist/ui/ephemeral.d.ts +111 -0
  109. package/dist/ui/ephemeral.js +413 -0
  110. package/dist/ui/file-autocomplete.d.ts +45 -0
  111. package/dist/ui/file-autocomplete.js +237 -0
  112. package/dist/ui/footer.d.ts +153 -0
  113. package/dist/ui/footer.js +422 -0
  114. package/dist/ui/index.d.ts +12 -0
  115. package/dist/ui/index.js +15 -0
  116. package/dist/ui/init-overlay.d.ts +24 -0
  117. package/dist/ui/init-overlay.js +525 -0
  118. package/dist/ui/input-prompt-v2.d.ts +179 -0
  119. package/dist/ui/input-prompt-v2.js +991 -0
  120. package/dist/ui/input-prompt.d.ts +97 -0
  121. package/dist/ui/input-prompt.js +800 -0
  122. package/dist/ui/iteration-limit-overlay.d.ts +21 -0
  123. package/dist/ui/iteration-limit-overlay.js +150 -0
  124. package/dist/ui/keys-overlay.d.ts +14 -0
  125. package/dist/ui/keys-overlay.js +181 -0
  126. package/dist/ui/model-warning-overlay.d.ts +30 -0
  127. package/dist/ui/model-warning-overlay.js +171 -0
  128. package/dist/ui/overlay-controller.d.ts +25 -0
  129. package/dist/ui/overlay-controller.js +35 -0
  130. package/dist/ui/overlays.d.ts +47 -0
  131. package/dist/ui/overlays.js +627 -0
  132. package/dist/ui/permission-overlay.d.ts +16 -0
  133. package/dist/ui/permission-overlay.js +494 -0
  134. package/dist/ui/terminal.d.ts +117 -0
  135. package/dist/ui/terminal.js +237 -0
  136. package/dist/ui/todo-zone.d.ts +112 -0
  137. package/dist/ui/todo-zone.js +353 -0
  138. package/dist/ui/tools-overlay.d.ts +26 -0
  139. package/dist/ui/tools-overlay.js +278 -0
  140. package/dist/ui/tutorial-overlay.d.ts +10 -0
  141. package/dist/ui/tutorial-overlay.js +936 -0
  142. package/dist/ui/types.d.ts +103 -0
  143. package/dist/ui/types.js +33 -0
  144. package/dist/utils/credentials.d.ts +55 -0
  145. package/dist/utils/credentials.js +268 -0
  146. package/dist/utils/model-tiers.d.ts +37 -0
  147. package/dist/utils/model-tiers.js +118 -0
  148. package/dist/utils/project-memory.d.ts +47 -0
  149. package/dist/utils/project-memory.js +117 -0
  150. package/dist/utils/project-status.d.ts +56 -0
  151. package/dist/utils/project-status.js +237 -0
  152. 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';