@indiccoder/mentis-cli 1.1.2 → 1.1.4

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.
@@ -44,6 +44,7 @@ const ConfigManager_1 = require("../config/ConfigManager");
44
44
  const OpenAIClient_1 = require("../llm/OpenAIClient");
45
45
  const ContextManager_1 = require("../context/ContextManager");
46
46
  const UIManager_1 = require("../ui/UIManager");
47
+ const InputBox_1 = require("../ui/InputBox");
47
48
  const FileTools_1 = require("../tools/FileTools");
48
49
  const SearchTools_1 = require("../tools/SearchTools");
49
50
  const PersistentShellTool_1 = require("../tools/PersistentShellTool");
@@ -224,46 +225,33 @@ class ReplManager {
224
225
  }
225
226
  catch (e) { }
226
227
  }
228
+ // Initialize InputBox with history
229
+ const inputBox = new InputBox_1.InputBox(commandHistory);
227
230
  while (true) {
228
- // Minimalist Separator
229
- console.log(chalk_1.default.gray('────────────────────────────────────────────────────────────────────────────────'));
230
- // Hint (Claude style puts it below, we put it above for standard terminal compatibility)
231
- console.log(chalk_1.default.dim(' ? for shortcuts'));
232
- const promptText = `> `; // Clean prompt
233
- // Use readline for basic input to support history
234
- const answer = await new Promise((resolve) => {
235
- const rl = readline.createInterface({
236
- input: process.stdin,
237
- output: process.stdout,
238
- history: commandHistory,
239
- historySize: 1000,
240
- prompt: promptText
241
- });
242
- rl.prompt();
243
- rl.on('line', (line) => {
244
- rl.close();
245
- resolve(line);
246
- });
231
+ // Calculate context usage for display
232
+ const usage = this.contextVisualizer.calculateUsage(this.history);
233
+ // Display enhanced input frame
234
+ inputBox.displayFrame({
235
+ messageCount: this.history.length,
236
+ contextPercent: usage.percentage
237
+ });
238
+ // Get styled input
239
+ const answer = await inputBox.prompt({
240
+ showHint: this.history.length === 0,
241
+ hint: 'Type your message or /help for commands'
247
242
  });
248
- // Update history manually or grab from rl?
249
- // rl.history gets updated when user hits enter.
250
- // But we closed rl. We should manually save the input to our tracking array and file.
251
243
  const input = answer.trim();
252
244
  if (input) {
253
- // Update in-memory history (for next readline instance)
254
- // Readline history has newest at 0.
255
- // Avoid duplicates if needed, but standard shell keeps them.
256
- if (commandHistory[0] !== input) {
257
- commandHistory.unshift(input);
258
- }
259
- // Append to file (as standard log, so append at end)
245
+ // Update history via InputBox
246
+ inputBox.addToHistory(input);
247
+ // Append to file
260
248
  try {
261
249
  fs.appendFileSync(HISTORY_FILE, input + '\n');
262
250
  }
263
251
  catch (e) { }
264
252
  }
265
- if (!answer.trim())
266
- continue; // Skip empty but allow it to close readline loop
253
+ if (!input)
254
+ continue;
267
255
  if (input.startsWith('/')) {
268
256
  await this.handleCommand(input);
269
257
  continue;
@@ -1,6 +1,21 @@
1
1
  "use strict";
2
2
  /**
3
- * Skill data structure for Agent Skills system
4
- * Based on Claude Code's Agent Skills format
3
+ * Agent Skills System
4
+ *
5
+ * Based on Claude Code's Agent Skills format. Skills are reusable AI agent
6
+ * configurations stored as SKILL.md files in dedicated directories.
7
+ *
8
+ * @packageDocumentation
9
+ *
10
+ * @example
11
+ * ```markdown
12
+ * ---
13
+ * name: code-reviewer
14
+ * description: Use when the user asks for a code review. Examines code for bugs, style issues, and improvements.
15
+ * allowed-tools: [Read, Grep, Glob]
16
+ * ---
17
+ *
18
+ * Review the code for...
19
+ * ```
5
20
  */
6
21
  Object.defineProperty(exports, "__esModule", { value: true });
@@ -60,10 +60,20 @@ class SkillsManager {
60
60
  } = options;
61
61
  const discovered = [];
62
62
  if (includePersonal) {
63
- discovered.push(...await this.discoverSkillsInDirectory(this.personalSkillsDir, 'personal'));
63
+ try {
64
+ discovered.push(...await this.discoverSkillsInDirectory(this.personalSkillsDir, 'personal'));
65
+ }
66
+ catch (error) {
67
+ console.warn(`Warning: Failed to load personal skills from ${this.personalSkillsDir}: ${error.message}`);
68
+ }
64
69
  }
65
70
  if (includeProject) {
66
- discovered.push(...await this.discoverSkillsInDirectory(this.projectSkillsDir, 'project'));
71
+ try {
72
+ discovered.push(...await this.discoverSkillsInDirectory(this.projectSkillsDir, 'project'));
73
+ }
74
+ catch (error) {
75
+ console.warn(`Warning: Failed to load project skills from ${this.projectSkillsDir}: ${error.message}`);
76
+ }
67
77
  }
68
78
  // Store skills in map for quick lookup
69
79
  for (const skill of discovered) {
@@ -109,6 +119,7 @@ class SkillsManager {
109
119
  const content = fs.readFileSync(skillPath, 'utf-8');
110
120
  const frontmatter = this.extractFrontmatter(content);
111
121
  if (!frontmatter) {
122
+ console.warn(`Warning: Invalid or missing frontmatter in ${skillPath} (skipping)`);
112
123
  return null;
113
124
  }
114
125
  // Convert SkillFrontmatter to SkillMetadata for validation
@@ -128,10 +139,24 @@ class SkillsManager {
128
139
  isValid: validation.isValid,
129
140
  errors: validation.errors.length > 0 ? validation.errors : undefined
130
141
  };
142
+ // Log validation warnings
143
+ if (validation.warnings.length > 0) {
144
+ for (const warning of validation.warnings) {
145
+ console.warn(`Warning (${skill.name}): ${warning}`);
146
+ }
147
+ }
131
148
  return skill;
132
149
  }
133
150
  catch (error) {
134
- console.error(`Error loading skill metadata from ${skillPath}: ${error.message}`);
151
+ if (error.code === 'ENOENT') {
152
+ console.warn(`Warning: Skill file not found: ${skillPath}`);
153
+ }
154
+ else if (error.code === 'EACCES') {
155
+ console.warn(`Warning: Permission denied reading skill: ${skillPath}`);
156
+ }
157
+ else {
158
+ console.error(`Error loading skill metadata from ${skillPath}: ${error.message}`);
159
+ }
135
160
  return null;
136
161
  }
137
162
  }
@@ -0,0 +1,62 @@
1
+ "use strict";
2
+ /**
3
+ * Tests for SkillsManager
4
+ */
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ const SkillsManager_1 = require("../SkillsManager");
7
+ describe('SkillsManager', () => {
8
+ let manager;
9
+ beforeEach(() => {
10
+ manager = new SkillsManager_1.SkillsManager();
11
+ });
12
+ describe('getAllSkills', () => {
13
+ it('should return empty array initially', () => {
14
+ const skills = manager.getAllSkills();
15
+ expect(skills).toEqual([]);
16
+ });
17
+ });
18
+ describe('getSkill', () => {
19
+ it('should return undefined for non-existent skill', () => {
20
+ const skill = manager.getSkill('non-existent');
21
+ expect(skill).toBeUndefined();
22
+ });
23
+ });
24
+ describe('getSkillsContext', () => {
25
+ it('should return empty string when no skills', () => {
26
+ const context = manager.getSkillsContext();
27
+ expect(context).toBe('');
28
+ });
29
+ });
30
+ describe('addSkill', () => {
31
+ it('should add skill to manager', () => {
32
+ const skill = {
33
+ name: 'test-skill',
34
+ description: 'Test skill',
35
+ path: '/skills/test.md',
36
+ type: 'personal',
37
+ directory: '/skills',
38
+ isValid: true,
39
+ content: 'Test content'
40
+ };
41
+ manager['skills'].set('test-skill', skill);
42
+ const retrieved = manager.getSkill('test-skill');
43
+ expect(retrieved).toBeDefined();
44
+ expect(retrieved?.name).toBe('test-skill');
45
+ });
46
+ it('should return skills context with skills', () => {
47
+ const skill = {
48
+ name: 'reviewer',
49
+ description: 'Code reviewer skill',
50
+ path: '/skills/reviewer.md',
51
+ type: 'personal',
52
+ directory: '/skills',
53
+ isValid: true,
54
+ content: 'Review code'
55
+ };
56
+ manager['skills'].set('reviewer', skill);
57
+ const context = manager.getSkillsContext();
58
+ expect(context).toContain('Available Skills');
59
+ expect(context).toContain('reviewer');
60
+ });
61
+ });
62
+ });
@@ -0,0 +1,127 @@
1
+ "use strict";
2
+ /**
3
+ * InputBox - Simple clean input with top line only
4
+ * Bottom line appears after submission (cross-platform compatible)
5
+ */
6
+ var __importDefault = (this && this.__importDefault) || function (mod) {
7
+ return (mod && mod.__esModule) ? mod : { "default": mod };
8
+ };
9
+ Object.defineProperty(exports, "__esModule", { value: true });
10
+ exports.InputBox = void 0;
11
+ const readline_1 = __importDefault(require("readline"));
12
+ const chalk_1 = __importDefault(require("chalk"));
13
+ class InputBox {
14
+ constructor(history = []) {
15
+ this.history = [];
16
+ this.historySize = 1000;
17
+ this.history = history;
18
+ }
19
+ /**
20
+ * Get terminal width
21
+ */
22
+ getTerminalWidth() {
23
+ return process.stdout.columns || 80;
24
+ }
25
+ /**
26
+ * Create horizontal line
27
+ */
28
+ createLine() {
29
+ const width = this.getTerminalWidth();
30
+ return chalk_1.default.gray('─'.repeat(width));
31
+ }
32
+ /**
33
+ * Get user input with horizontal lines around it
34
+ */
35
+ async prompt(options = {}) {
36
+ const { showHint = false, hint } = options;
37
+ // Display top horizontal line
38
+ console.log(this.createLine());
39
+ // Display hint if provided
40
+ if (showHint && hint) {
41
+ console.log(chalk_1.default.dim(` ${hint}`));
42
+ }
43
+ return new Promise((resolve) => {
44
+ // Create readline with simple prompt
45
+ const rl = readline_1.default.createInterface({
46
+ input: process.stdin,
47
+ output: process.stdout,
48
+ prompt: chalk_1.default.cyan('> '),
49
+ history: this.history,
50
+ historySize: this.historySize,
51
+ completer: this.completer.bind(this)
52
+ });
53
+ rl.prompt();
54
+ rl.on('line', (line) => {
55
+ // Display bottom horizontal line after input
56
+ console.log(this.createLine());
57
+ rl.close();
58
+ resolve(line);
59
+ });
60
+ // Handle Ctrl+C
61
+ rl.on('SIGINT', () => {
62
+ console.log(this.createLine());
63
+ rl.close();
64
+ resolve('/exit');
65
+ });
66
+ });
67
+ }
68
+ /**
69
+ * Simple tab completer for commands
70
+ */
71
+ completer(line) {
72
+ const commands = [
73
+ '/help', '/clear', '/exit', '/update', '/config',
74
+ '/init', '/resume', '/skills', '/commands', '/checkpoint',
75
+ '/model', '/use', '/mcp', '/search', '/run', '/commit'
76
+ ];
77
+ const hits = commands.filter(c => c.startsWith(line));
78
+ return [hits.length ? hits : commands, line];
79
+ }
80
+ /**
81
+ * Add input to history
82
+ */
83
+ addToHistory(input) {
84
+ if (!input || input === this.history[0])
85
+ return;
86
+ this.history.unshift(input);
87
+ if (this.history.length > this.historySize) {
88
+ this.history = this.history.slice(0, this.historySize);
89
+ }
90
+ }
91
+ /**
92
+ * Get current history
93
+ */
94
+ getHistory() {
95
+ return this.history;
96
+ }
97
+ /**
98
+ * Display separator and context info before input
99
+ */
100
+ displayFrame(contextInfo) {
101
+ console.log('');
102
+ // Context bar with message count and percentage
103
+ if (contextInfo) {
104
+ const { messageCount, contextPercent } = contextInfo;
105
+ const color = contextPercent < 60 ? chalk_1.default.green : contextPercent < 80 ? chalk_1.default.yellow : chalk_1.default.red;
106
+ const bar = this.createProgressBar(contextPercent);
107
+ console.log(chalk_1.default.dim(` ${bar} ${messageCount} msgs ${color(contextPercent + '%')}`));
108
+ }
109
+ }
110
+ /**
111
+ * Display separator (alias for displayFrame)
112
+ */
113
+ displaySeparator(contextInfo) {
114
+ this.displayFrame(contextInfo);
115
+ }
116
+ /**
117
+ * Create a visual progress bar
118
+ */
119
+ createProgressBar(percentage) {
120
+ const width = 15;
121
+ const filled = Math.round(percentage / 100 * width);
122
+ const empty = width - filled;
123
+ const color = percentage < 60 ? chalk_1.default.green : percentage < 80 ? chalk_1.default.yellow : chalk_1.default.red;
124
+ return color('█'.repeat(filled)) + chalk_1.default.dim('░'.repeat(empty));
125
+ }
126
+ }
127
+ exports.InputBox = InputBox;
@@ -19,12 +19,12 @@ class UIManager {
19
19
  whitespaceBreak: true,
20
20
  });
21
21
  console.log(gradient_string_1.default.pastel.multiline(logoText));
22
- console.log(chalk_1.default.gray(' v1.1.2 - AI Coding Agent'));
22
+ console.log(chalk_1.default.gray(' v1.1.3 - AI Coding Agent'));
23
23
  console.log('');
24
24
  }
25
25
  static renderDashboard(config) {
26
26
  const { model, cwd } = config;
27
- const version = 'v1.1.2';
27
+ const version = 'v1.1.3';
28
28
  // Layout: Left (Status/Welcome) | Right (Tips/Activity)
29
29
  // Total width ~80 chars.
30
30
  // Left ~45, Right ~30.
@@ -0,0 +1,20 @@
1
+ "use strict";
2
+ // Manual mock for chalk to avoid ESM issues in Jest
3
+ module.exports = {
4
+ dim: (str) => str,
5
+ green: (str) => str,
6
+ yellow: (str) => str,
7
+ red: (str) => str,
8
+ gray: (str) => str,
9
+ bold: (str) => str,
10
+ cyan: (str) => str,
11
+ default: {
12
+ dim: (str) => str,
13
+ green: (str) => str,
14
+ yellow: (str) => str,
15
+ red: (str) => str,
16
+ gray: (str) => str,
17
+ bold: (str) => str,
18
+ cyan: (str) => str
19
+ }
20
+ };
@@ -0,0 +1,95 @@
1
+ "use strict";
2
+ /**
3
+ * Tests for ContextVisualizer
4
+ */
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ const ContextVisualizer_1 = require("../ContextVisualizer");
7
+ describe('ContextVisualizer', () => {
8
+ let visualizer;
9
+ beforeEach(() => {
10
+ visualizer = new ContextVisualizer_1.ContextVisualizer();
11
+ });
12
+ describe('calculateUsage', () => {
13
+ it('should handle empty history', () => {
14
+ const history = [];
15
+ const usage = visualizer.calculateUsage(history);
16
+ // Includes 2000 char overhead = 500 tokens
17
+ expect(usage.tokens).toBe(500);
18
+ expect(usage.maxTokens).toBe(128000);
19
+ expect(usage).toHaveProperty('percentage');
20
+ expect(usage).toHaveProperty('tokens');
21
+ expect(usage).toHaveProperty('maxTokens');
22
+ });
23
+ it('should calculate tokens for messages', () => {
24
+ const history = [
25
+ { role: 'system', content: 'You are a helpful assistant.' },
26
+ { role: 'user', content: 'Hello' },
27
+ { role: 'assistant', content: 'Hi there!' }
28
+ ];
29
+ const usage = visualizer.calculateUsage(history);
30
+ expect(usage.tokens).toBeGreaterThan(500);
31
+ expect(usage.tokens).toBeLessThan(1000);
32
+ expect(usage.maxTokens).toBe(128000);
33
+ });
34
+ it('should handle large messages', () => {
35
+ const largeContent = 'x'.repeat(10000);
36
+ const history = [
37
+ { role: 'user', content: largeContent }
38
+ ];
39
+ const usage = visualizer.calculateUsage(history);
40
+ expect(usage.tokens).toBeGreaterThan(1500);
41
+ });
42
+ });
43
+ describe('formatBar', () => {
44
+ it('should format bar at low usage', () => {
45
+ const usage = { tokens: 1000, percentage: 5, maxTokens: 128000 };
46
+ const bar = visualizer.formatBar(usage);
47
+ // Check that bar contains expected data (without chalk dependency)
48
+ expect(bar).toContain('5');
49
+ expect(bar).toContain('1k');
50
+ expect(bar).toContain('128');
51
+ });
52
+ it('should format bar at medium usage', () => {
53
+ const usage = { tokens: 50000, percentage: 40, maxTokens: 128000 };
54
+ const bar = visualizer.formatBar(usage);
55
+ expect(bar).toContain('40');
56
+ expect(bar).toContain('50k');
57
+ });
58
+ it('should format bar at high usage', () => {
59
+ const usage = { tokens: 100000, percentage: 80, maxTokens: 128000 };
60
+ const bar = visualizer.formatBar(usage);
61
+ expect(bar).toContain('80');
62
+ expect(bar).toContain('100k');
63
+ });
64
+ });
65
+ describe('shouldCompact', () => {
66
+ it('should return false for low percentage', () => {
67
+ const history = [
68
+ { role: 'user', content: 'small message' }
69
+ ];
70
+ const shouldCompact = visualizer.shouldCompact(history);
71
+ expect(shouldCompact).toBe(false);
72
+ });
73
+ it('should return true at 80% threshold', () => {
74
+ // Create enough content to exceed 80%
75
+ // 80% of 128000 tokens = 102400 tokens = ~409600 chars
76
+ // Subtract 2000 overhead = ~407400 chars needed
77
+ const largeContent = 'x'.repeat(410000);
78
+ const history = [
79
+ { role: 'system', content: largeContent },
80
+ { role: 'user', content: largeContent }
81
+ ];
82
+ const shouldCompact = visualizer.shouldCompact(history);
83
+ expect(shouldCompact).toBe(true);
84
+ });
85
+ });
86
+ describe('setMaxTokens', () => {
87
+ it('should update max tokens', () => {
88
+ visualizer.setMaxTokens(32000);
89
+ const history = [];
90
+ const usage = visualizer.calculateUsage(history);
91
+ expect(usage.maxTokens).toBe(32000);
92
+ expect(usage.percentage).toBeGreaterThan(1); // Should be higher percentage with smaller max
93
+ });
94
+ });
95
+ });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@indiccoder/mentis-cli",
3
- "version": "1.1.2",
3
+ "version": "1.1.4",
4
4
  "publishConfig": {
5
5
  "access": "public"
6
6
  },
@@ -12,7 +12,9 @@
12
12
  "scripts": {
13
13
  "build": "tsc",
14
14
  "start": "node dist/index.js",
15
- "test": "echo \"Error: no test specified\" && exit 1"
15
+ "test": "jest",
16
+ "test:watch": "jest --watch",
17
+ "test:coverage": "jest --coverage"
16
18
  },
17
19
  "keywords": [
18
20
  "cli",
@@ -33,6 +35,7 @@
33
35
  "axios": "^1.13.2",
34
36
  "boxen": "^8.0.1",
35
37
  "chalk": "^5.6.2",
38
+ "cli-cursor": "^5.0.0",
36
39
  "cli-highlight": "^2.1.11",
37
40
  "commander": "^14.0.2",
38
41
  "dotenv": "^17.2.3",
@@ -54,11 +57,31 @@
54
57
  "yaml": "^2.7.0"
55
58
  },
56
59
  "devDependencies": {
60
+ "@types/cli-cursor": "^2.1.0",
57
61
  "@types/figlet": "^1.7.0",
58
62
  "@types/fs-extra": "^11.0.4",
59
63
  "@types/gradient-string": "^1.1.6",
60
64
  "@types/inquirer": "^9.0.9",
65
+ "@types/jest": "^30.0.0",
61
66
  "@types/node": "^25.0.2",
67
+ "jest": "^30.2.0",
68
+ "ts-jest": "^29.4.6",
62
69
  "typescript": "^5.9.3"
70
+ },
71
+ "jest": {
72
+ "preset": "ts-jest",
73
+ "testEnvironment": "node",
74
+ "roots": ["<rootDir>/src"],
75
+ "testMatch": ["**/__tests__/**/*.ts", "**/?(*.)+(spec|test).ts"],
76
+ "collectCoverageFrom": ["src/**/*.ts", "!src/**/*.d.ts"],
77
+ "transformIgnorePatterns": ["node_modules/(?!(chalk)/)"],
78
+ "coverageThreshold": {
79
+ "global": {
80
+ "branches": 20,
81
+ "functions": 30,
82
+ "lines": 30,
83
+ "statements": 30
84
+ }
85
+ }
63
86
  }
64
87
  }
@@ -1,40 +1,91 @@
1
1
  /**
2
2
  * Custom Slash Commands System
3
- * Users can define their own slash commands as markdown files
3
+ *
4
+ * Users can define their own slash commands as markdown files with YAML frontmatter.
5
+ * Commands support parameter substitution, bash execution, and file references.
6
+ *
7
+ * @packageDocumentation
8
+ *
9
+ * @example
10
+ * ```markdown
11
+ * ---
12
+ * description: Run tests and show coverage
13
+ * argument-hint: [test-pattern]
14
+ * ---
15
+ *
16
+ * Run tests with !`npm test $1`
17
+ * ```
4
18
  */
5
19
 
20
+ /**
21
+ * YAML frontmatter options for custom commands
22
+ */
6
23
  export interface CommandFrontmatter {
24
+ /** Human-readable description of what the command does */
7
25
  description?: string;
26
+ /** Restrict which tools the AI can use when executing this command */
8
27
  'allowed-tools'?: string[];
28
+ /** Hint shown to user about expected arguments (e.g., "[pattern]") */
9
29
  'argument-hint'?: string;
30
+ /** Specific model to use for this command */
10
31
  model?: string;
32
+ /** Disable AI model invocation (execute bash/reads only) */
11
33
  'disable-model-invocation'?: boolean;
12
34
  }
13
35
 
36
+ /**
37
+ * A custom slash command
38
+ */
14
39
  export interface Command {
15
- name: string; // Command name (from filename)
16
- type: 'personal' | 'project'; // Personal or project command
17
- path: string; // Path to command file
18
- directory: string; // Directory containing the command
40
+ /** Command name (derived from filename, without .md extension) */
41
+ name: string;
42
+ /** Whether this is a personal or project-level command */
43
+ type: 'personal' | 'project';
44
+ /** Absolute path to the command's .md file */
45
+ path: string;
46
+ /** Directory containing the command file */
47
+ directory: string;
48
+ /** Parsed YAML frontmatter from the command file */
19
49
  frontmatter: CommandFrontmatter;
20
- content: string; // Command content (markdown)
21
- description: string; // Command description
22
- hasParameters: boolean; // Whether command uses parameters
50
+ /** Raw markdown content of the command */
51
+ content: string;
52
+ /** Human-readable description for display */
53
+ description: string;
54
+ /** Whether the command uses $1, $2, or $ARGUMENTS placeholders */
55
+ hasParameters: boolean;
23
56
  }
24
57
 
58
+ /**
59
+ * Execution context for running a custom command
60
+ */
25
61
  export interface CommandExecutionContext {
62
+ /** The command being executed */
26
63
  command: Command;
27
- args: string[]; // Command arguments
64
+ /** Arguments passed to the command */
65
+ args: string[];
66
+ /** Function to substitute $1, $2, $ARGUMENTS placeholders */
28
67
  substitutePlaceholders: (content: string, args: string[]) => string;
68
+ /** Function to execute bash commands */
29
69
  executeBash: (bashCommand: string) => Promise<string>;
70
+ /** Function to read file contents */
30
71
  readFile: (filePath: string) => Promise<string>;
31
72
  }
32
73
 
33
74
  /**
34
- * Parsed command with substitutions applied
75
+ * Parsed command with all substitutions applied
76
+ *
77
+ * @remarks
78
+ * After parsing, the command content will have:
79
+ * - $1, $2, etc. replaced with positional arguments
80
+ * - $ARGUMENTS replaced with all arguments joined by spaces
81
+ * - !`cmd` patterns extracted to bashCommands and replaced with [BASH_OUTPUT]
82
+ * - @file patterns extracted to fileReferences
35
83
  */
36
84
  export interface ParsedCommand {
37
- content: string; // Content with substitutions applied
38
- bashCommands: string[]; // Bash commands to execute
39
- fileReferences: string[]; // Files to read
85
+ /** Content with all substitutions applied */
86
+ content: string;
87
+ /** Bash commands extracted from !`cmd` patterns */
88
+ bashCommands: string[];
89
+ /** File paths extracted from @file patterns */
90
+ fileReferences: string[];
40
91
  }