@vdntio/clai 0.1.0-alpha.1

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 (89) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +98 -0
  3. package/dist/ai/index.d.ts +24 -0
  4. package/dist/ai/index.js +78 -0
  5. package/dist/ai/mock.d.ts +17 -0
  6. package/dist/ai/mock.js +49 -0
  7. package/dist/ai/parser.d.ts +14 -0
  8. package/dist/ai/parser.js +109 -0
  9. package/dist/ai/prompt.d.ts +12 -0
  10. package/dist/ai/prompt.js +76 -0
  11. package/dist/ai/providers/index.d.ts +1 -0
  12. package/dist/ai/providers/index.js +2 -0
  13. package/dist/ai/providers/openrouter.d.ts +31 -0
  14. package/dist/ai/providers/openrouter.js +142 -0
  15. package/dist/ai/types.d.ts +46 -0
  16. package/dist/ai/types.js +15 -0
  17. package/dist/cli/index.d.ts +19 -0
  18. package/dist/cli/index.js +71 -0
  19. package/dist/config/index.d.ts +12 -0
  20. package/dist/config/index.js +363 -0
  21. package/dist/config/types.d.ts +76 -0
  22. package/dist/config/types.js +40 -0
  23. package/dist/context/directory.d.ts +18 -0
  24. package/dist/context/directory.js +71 -0
  25. package/dist/context/history.d.ts +16 -0
  26. package/dist/context/history.js +89 -0
  27. package/dist/context/index.d.ts +24 -0
  28. package/dist/context/index.js +61 -0
  29. package/dist/context/redaction.d.ts +14 -0
  30. package/dist/context/redaction.js +57 -0
  31. package/dist/context/stdin.d.ts +13 -0
  32. package/dist/context/stdin.js +86 -0
  33. package/dist/context/system.d.ts +11 -0
  34. package/dist/context/system.js +56 -0
  35. package/dist/context/types.d.ts +31 -0
  36. package/dist/context/types.js +10 -0
  37. package/dist/error/index.d.ts +30 -0
  38. package/dist/error/index.js +50 -0
  39. package/dist/logging/file-logger.d.ts +12 -0
  40. package/dist/logging/file-logger.js +66 -0
  41. package/dist/logging/index.d.ts +15 -0
  42. package/dist/logging/index.js +33 -0
  43. package/dist/logging/logger.d.ts +15 -0
  44. package/dist/logging/logger.js +60 -0
  45. package/dist/main.d.ts +2 -0
  46. package/dist/main.js +192 -0
  47. package/dist/output/execute.d.ts +30 -0
  48. package/dist/output/execute.js +144 -0
  49. package/dist/output/index.d.ts +4 -0
  50. package/dist/output/index.js +7 -0
  51. package/dist/output/types.d.ts +48 -0
  52. package/dist/output/types.js +34 -0
  53. package/dist/output/validate.d.ts +23 -0
  54. package/dist/output/validate.js +42 -0
  55. package/dist/safety/index.d.ts +34 -0
  56. package/dist/safety/index.js +59 -0
  57. package/dist/safety/patterns.d.ts +23 -0
  58. package/dist/safety/patterns.js +96 -0
  59. package/dist/safety/types.d.ts +20 -0
  60. package/dist/safety/types.js +18 -0
  61. package/dist/signals/index.d.ts +4 -0
  62. package/dist/signals/index.js +35 -0
  63. package/dist/ui/App.d.ts +4 -0
  64. package/dist/ui/App.js +57 -0
  65. package/dist/ui/components/ActionPrompt.d.ts +8 -0
  66. package/dist/ui/components/ActionPrompt.js +9 -0
  67. package/dist/ui/components/CommandDisplay.d.ts +9 -0
  68. package/dist/ui/components/CommandDisplay.js +13 -0
  69. package/dist/ui/components/DangerousWarning.d.ts +6 -0
  70. package/dist/ui/components/DangerousWarning.js +6 -0
  71. package/dist/ui/components/Spinner.d.ts +15 -0
  72. package/dist/ui/components/Spinner.js +17 -0
  73. package/dist/ui/hooks/useAnimation.d.ts +27 -0
  74. package/dist/ui/hooks/useAnimation.js +85 -0
  75. package/dist/ui/hooks/useTerminalSize.d.ts +12 -0
  76. package/dist/ui/hooks/useTerminalSize.js +56 -0
  77. package/dist/ui/hooks/useTimeout.d.ts +9 -0
  78. package/dist/ui/hooks/useTimeout.js +31 -0
  79. package/dist/ui/index.d.ts +25 -0
  80. package/dist/ui/index.js +80 -0
  81. package/dist/ui/output.d.ts +20 -0
  82. package/dist/ui/output.js +56 -0
  83. package/dist/ui/spinner.d.ts +13 -0
  84. package/dist/ui/spinner.js +58 -0
  85. package/dist/ui/types.d.ts +105 -0
  86. package/dist/ui/types.js +60 -0
  87. package/dist/ui/utils/formatCommand.d.ts +50 -0
  88. package/dist/ui/utils/formatCommand.js +113 -0
  89. package/package.json +68 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 vdntio
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,98 @@
1
+ # clAI
2
+
3
+ > ⚠️ **Alpha Release**: This is an early alpha version. Expect bugs and breaking changes.
4
+
5
+ AI-powered CLI that converts natural language instructions into executable shell commands using OpenRouter.
6
+
7
+ ## Example
8
+
9
+ ```bash
10
+ clai "find all TypeScript files modified in the last 7 days"
11
+ # Outputs: find . -name "*.ts" -mtime -7
12
+
13
+ clai "list top 10 largest files in current directory"
14
+ # Outputs: du -h * | sort -rh | head -10
15
+ ```
16
+
17
+ ## Features
18
+
19
+ - 🤖 Natural language to shell commands using AI
20
+ - 🎨 Beautiful terminal UI with Ink
21
+ - 🔒 Safety checks for dangerous commands (rm -rf, etc.)
22
+ - 📝 Context-aware (current directory, shell type, git status)
23
+ - ⚡ Fast startup with Bun runtime
24
+ - 🌍 Cross-platform (Linux, macOS, Windows)
25
+
26
+ ## Installation
27
+
28
+ ### npm (Recommended)
29
+
30
+ ```bash
31
+ npm install -g clai@alpha
32
+ ```
33
+
34
+ ### Standalone Binary
35
+
36
+ Download from [Releases](https://github.com/vdntio/clAI/releases)
37
+
38
+ See [Installation Guide](docs/installation.md) for detailed instructions.
39
+
40
+ ## Quick Start
41
+
42
+ 1. Get an OpenRouter API key from [openrouter.ai](https://openrouter.ai)
43
+
44
+ 2. Set your API key:
45
+ ```bash
46
+ export OPENROUTER_API_KEY="your-key-here"
47
+ ```
48
+
49
+ 3. Run a command:
50
+ ```bash
51
+ clai "your natural language instruction"
52
+ ```
53
+
54
+ ## Configuration
55
+
56
+ clAI uses TOML config files. Priority order:
57
+ 1. `./.clai.toml` (project-level)
58
+ 2. `~/.config/clai/config.toml` (user-level)
59
+ 3. `/etc/clai/config.toml` (system-level)
60
+
61
+ Example config:
62
+ ```toml
63
+ openrouter_api_key = "sk-..."
64
+
65
+ [providers.openrouter]
66
+ model = "qwen/qwen3-coder" # Default model (can override with anthropic/claude-3.5-sonnet, etc.)
67
+
68
+ [safety]
69
+ confirm_dangerous = true
70
+ ```
71
+
72
+ ## Development
73
+
74
+ ```bash
75
+ # Install dependencies
76
+ bun install
77
+
78
+ # Run in development mode
79
+ bun run dev
80
+
81
+ # Run tests
82
+ bun test
83
+
84
+ # Build
85
+ bun run build
86
+ ```
87
+
88
+ ## OpenRouter Costs
89
+
90
+ clAI uses OpenRouter's API which charges per token. Default model is `qwen/qwen3-coder` (free tier available). You can override to use other models like `anthropic/claude-3.5-sonnet` (~$3 per million input tokens). Get credits at [openrouter.ai](https://openrouter.ai).
91
+
92
+ ## License
93
+
94
+ MIT - See [LICENSE](LICENSE) for details.
95
+
96
+ ## Issues & Feedback
97
+
98
+ Report bugs or request features at [GitHub Issues](https://github.com/vdntio/clAI/issues).
@@ -0,0 +1,24 @@
1
+ import { ContextData } from '../context/types.js';
2
+ import type { Config } from '../config/types.js';
3
+ import { ChatMessage } from './types.js';
4
+ export { AIError, } from './types.js';
5
+ export type { ChatMessage, ChatRequest, ChatResponse, AIProvider, } from './types.js';
6
+ export { buildPrompt } from './prompt.js';
7
+ export { parseResponse } from './parser.js';
8
+ export { OpenRouterProvider } from './providers/index.js';
9
+ export { MockProvider } from './mock.js';
10
+ /**
11
+ * Generate shell commands from natural language instruction
12
+ *
13
+ * @param context - Gathered context (system, directory, history, stdin)
14
+ * @param instruction - User's natural language instruction
15
+ * @param config - Runtime configuration
16
+ * @returns Array of command strings (1 for single mode, N for multi mode)
17
+ * @throws AIError on API failure or parse error (exit code 4)
18
+ */
19
+ export declare function generateCommands(context: ContextData, instruction: string, config: Config): Promise<string[]>;
20
+ /**
21
+ * Format messages for debug output
22
+ * Returns formatted string showing all messages
23
+ */
24
+ export declare function formatPromptForDebug(messages: ChatMessage[]): string;
@@ -0,0 +1,78 @@
1
+ // AI module main entry point
2
+ // Provides generateCommands() for converting natural language to shell commands
3
+ import { getProviderApiKey, getProviderModel } from '../config/index.js';
4
+ import { AIError, } from './types.js';
5
+ import { buildPrompt } from './prompt.js';
6
+ import { parseResponse } from './parser.js';
7
+ import { OpenRouterProvider } from './providers/index.js';
8
+ import { MockProvider } from './mock.js';
9
+ // Re-export types for consumers
10
+ export { AIError, } from './types.js';
11
+ export { buildPrompt } from './prompt.js';
12
+ export { parseResponse } from './parser.js';
13
+ export { OpenRouterProvider } from './providers/index.js';
14
+ export { MockProvider } from './mock.js';
15
+ /**
16
+ * Generate shell commands from natural language instruction
17
+ *
18
+ * @param context - Gathered context (system, directory, history, stdin)
19
+ * @param instruction - User's natural language instruction
20
+ * @param config - Runtime configuration
21
+ * @returns Array of command strings (1 for single mode, N for multi mode)
22
+ * @throws AIError on API failure or parse error (exit code 4)
23
+ */
24
+ export async function generateCommands(context, instruction, config) {
25
+ const providerName = config.providerName || config.provider.default;
26
+ const numOptions = config.ui.numOptions;
27
+ // Get appropriate provider
28
+ const provider = getProvider(providerName, config);
29
+ // Build prompt messages
30
+ const messages = buildPrompt(context, instruction, numOptions);
31
+ // Get model (from CLI, config, or default)
32
+ const model = getProviderModel(providerName, config);
33
+ // Make API request
34
+ const request = {
35
+ model,
36
+ messages,
37
+ temperature: 0.1, // Low temperature for more deterministic commands
38
+ };
39
+ const response = await provider.complete(request);
40
+ // Parse response into command(s)
41
+ return parseResponse(response.content, numOptions > 1);
42
+ }
43
+ /**
44
+ * Get provider instance based on configuration
45
+ * Returns mock provider if MOCK_AI=1 is set
46
+ */
47
+ function getProvider(name, config) {
48
+ // Check for mock mode
49
+ if (process.env.MOCK_AI === '1') {
50
+ return new MockProvider();
51
+ }
52
+ // Currently only OpenRouter is supported
53
+ // Future: add more providers here (Anthropic, Ollama, etc.)
54
+ if (name === 'openrouter') {
55
+ const apiKey = getProviderApiKey('openrouter', config);
56
+ if (!apiKey) {
57
+ throw new AIError('OpenRouter API key not configured. ' +
58
+ 'Set OPENROUTER_API_KEY environment variable or configure api_key in .clai.toml');
59
+ }
60
+ return new OpenRouterProvider(apiKey);
61
+ }
62
+ throw new AIError(`Unknown provider: ${name}. ` + 'Currently only "openrouter" is supported.');
63
+ }
64
+ /**
65
+ * Format messages for debug output
66
+ * Returns formatted string showing all messages
67
+ */
68
+ export function formatPromptForDebug(messages) {
69
+ return messages
70
+ .map((msg) => {
71
+ const role = msg.role.charAt(0).toUpperCase() + msg.role.slice(1);
72
+ const content = msg.content.length > 500
73
+ ? msg.content.substring(0, 500) + '...'
74
+ : msg.content;
75
+ return `[${role}]\n${content}`;
76
+ })
77
+ .join('\n\n');
78
+ }
@@ -0,0 +1,17 @@
1
+ import { AIProvider, ChatRequest, ChatResponse } from './types.js';
2
+ /**
3
+ * Mock provider for testing
4
+ * Returns predictable responses without API calls
5
+ */
6
+ export declare class MockProvider implements AIProvider {
7
+ name: string;
8
+ /**
9
+ * Always available (no API key needed)
10
+ */
11
+ isAvailable(): boolean;
12
+ /**
13
+ * Return mock response based on request
14
+ * Detects multi-command mode from system message
15
+ */
16
+ complete(request: ChatRequest): Promise<ChatResponse>;
17
+ }
@@ -0,0 +1,49 @@
1
+ // Mock AI Provider for testing
2
+ // Returns simple echo commands without making real API calls
3
+ // Activated by setting MOCK_AI=1 environment variable
4
+ /**
5
+ * Mock provider for testing
6
+ * Returns predictable responses without API calls
7
+ */
8
+ export class MockProvider {
9
+ name = 'mock';
10
+ /**
11
+ * Always available (no API key needed)
12
+ */
13
+ isAvailable() {
14
+ return true;
15
+ }
16
+ /**
17
+ * Return mock response based on request
18
+ * Detects multi-command mode from system message
19
+ */
20
+ async complete(request) {
21
+ const systemMsg = request.messages[0]?.content || '';
22
+ const isMultiCommand = systemMsg.includes('JSON');
23
+ if (isMultiCommand) {
24
+ // Extract number of commands from system message
25
+ const match = systemMsg.match(/exactly (\d+) different/);
26
+ const numCommands = match?.[1] ? parseInt(match[1], 10) : 3;
27
+ const commands = Array.from({ length: numCommands }, (_, i) => `echo "mock command ${i + 1}"`);
28
+ return {
29
+ content: JSON.stringify({ commands }),
30
+ model: 'mock',
31
+ usage: {
32
+ promptTokens: 100,
33
+ completionTokens: 50,
34
+ totalTokens: 150,
35
+ },
36
+ };
37
+ }
38
+ // Single command
39
+ return {
40
+ content: 'echo "mock command"',
41
+ model: 'mock',
42
+ usage: {
43
+ promptTokens: 50,
44
+ completionTokens: 10,
45
+ totalTokens: 60,
46
+ },
47
+ };
48
+ }
49
+ }
@@ -0,0 +1,14 @@
1
+ /**
2
+ * Parse AI response content into command(s)
3
+ *
4
+ * Handles:
5
+ * - Markdown code fence stripping (```bash, ```sh, ```shell, ```json, ```)
6
+ * - JSON parsing for multi-command mode: {"commands": [...]} or [...]
7
+ * - Fallback to single trimmed command
8
+ *
9
+ * @param content - Raw response content from AI
10
+ * @param expectMultiple - Whether to expect multiple commands (numOptions > 1)
11
+ * @returns Array of command strings
12
+ * @throws AIError if response is empty or cannot be parsed
13
+ */
14
+ export declare function parseResponse(content: string, expectMultiple: boolean): string[];
@@ -0,0 +1,109 @@
1
+ // Response parser for AI output
2
+ import { AIError } from './types.js';
3
+ /**
4
+ * Parse AI response content into command(s)
5
+ *
6
+ * Handles:
7
+ * - Markdown code fence stripping (```bash, ```sh, ```shell, ```json, ```)
8
+ * - JSON parsing for multi-command mode: {"commands": [...]} or [...]
9
+ * - Fallback to single trimmed command
10
+ *
11
+ * @param content - Raw response content from AI
12
+ * @param expectMultiple - Whether to expect multiple commands (numOptions > 1)
13
+ * @returns Array of command strings
14
+ * @throws AIError if response is empty or cannot be parsed
15
+ */
16
+ export function parseResponse(content, expectMultiple) {
17
+ // 1. Trim whitespace
18
+ let cleaned = content.trim();
19
+ if (!cleaned) {
20
+ throw new AIError('AI returned empty response');
21
+ }
22
+ // 2. Strip markdown code fences
23
+ cleaned = stripCodeFences(cleaned);
24
+ // Check if empty after stripping fences
25
+ if (!cleaned) {
26
+ throw new AIError('AI returned empty response after parsing');
27
+ }
28
+ // 3. Try JSON parse for multi-command mode
29
+ if (expectMultiple) {
30
+ const commands = tryParseMultipleCommands(cleaned);
31
+ if (commands.length > 0) {
32
+ return commands;
33
+ }
34
+ }
35
+ // 4. Fallback: treat as single command
36
+ cleaned = cleaned.trim();
37
+ if (!cleaned) {
38
+ throw new AIError('AI returned empty response after parsing');
39
+ }
40
+ return [cleaned];
41
+ }
42
+ /**
43
+ * Strip markdown code fences from content
44
+ * Handles multiple fence formats
45
+ */
46
+ function stripCodeFences(content) {
47
+ // Pattern 1: ```language\ncontent\n```
48
+ // Pattern 2: ```\ncontent\n```
49
+ // Must match the entire string from start to end
50
+ const lines = content.split('\n');
51
+ // Check if first line is an opening fence
52
+ if (lines[0]?.startsWith('```')) {
53
+ // Check if last line is a closing fence
54
+ if (lines[lines.length - 1] === '```') {
55
+ // Remove first and last lines
56
+ return lines.slice(1, -1).join('\n').trim();
57
+ }
58
+ // Single line case: ```content```
59
+ const singleLineMatch = content.match(/^```[\w]*\s*(.+?)\s*```$/);
60
+ if (singleLineMatch?.[1]) {
61
+ return singleLineMatch[1].trim();
62
+ }
63
+ }
64
+ return content;
65
+ }
66
+ /**
67
+ * Try to parse multiple commands from JSON
68
+ * Returns empty array if parsing fails
69
+ */
70
+ function tryParseMultipleCommands(content) {
71
+ // Try to extract JSON object if content has extra text
72
+ let jsonContent = content;
73
+ // Look for JSON object pattern if content has extra text
74
+ const jsonMatch = content.match(/\{[\s\S]*"commands"[\s\S]*\}/);
75
+ if (jsonMatch) {
76
+ jsonContent = jsonMatch[0];
77
+ }
78
+ try {
79
+ const parsed = JSON.parse(jsonContent);
80
+ // Handle {"commands": [...]}
81
+ if (parsed.commands && Array.isArray(parsed.commands)) {
82
+ const cmds = parsed.commands.filter((c) => typeof c === 'string' && c.trim());
83
+ return cmds.map((c) => c.trim());
84
+ }
85
+ // Handle raw array [...]
86
+ if (Array.isArray(parsed)) {
87
+ const cmds = parsed.filter((c) => typeof c === 'string' && c.trim());
88
+ return cmds.map((c) => c.trim());
89
+ }
90
+ }
91
+ catch {
92
+ // JSON parse failed, try to extract from text
93
+ // Look for array pattern: ["cmd1", "cmd2"]
94
+ const arrayMatch = content.match(/\[[\s\S]*\]/);
95
+ if (arrayMatch) {
96
+ try {
97
+ const parsed = JSON.parse(arrayMatch[0]);
98
+ if (Array.isArray(parsed)) {
99
+ const cmds = parsed.filter((c) => typeof c === 'string' && c.trim());
100
+ return cmds.map((c) => c.trim());
101
+ }
102
+ }
103
+ catch {
104
+ // JSON parse failed, continue to other strategies
105
+ }
106
+ }
107
+ }
108
+ return [];
109
+ }
@@ -0,0 +1,12 @@
1
+ import { ContextData } from '../context/types.js';
2
+ import { ChatMessage } from './types.js';
3
+ /**
4
+ * Build chat messages for AI request
5
+ * Constructs system and user messages based on context and instruction
6
+ *
7
+ * @param context - Gathered system/directory/history/stdin context
8
+ * @param instruction - User's natural language instruction
9
+ * @param numOptions - Number of command options to generate (1 for single, >1 for multi)
10
+ * @returns Array of chat messages for the AI
11
+ */
12
+ export declare function buildPrompt(context: ContextData, instruction: string, numOptions: number): ChatMessage[];
@@ -0,0 +1,76 @@
1
+ // Prompt builder for AI requests
2
+ /**
3
+ * Build chat messages for AI request
4
+ * Constructs system and user messages based on context and instruction
5
+ *
6
+ * @param context - Gathered system/directory/history/stdin context
7
+ * @param instruction - User's natural language instruction
8
+ * @param numOptions - Number of command options to generate (1 for single, >1 for multi)
9
+ * @returns Array of chat messages for the AI
10
+ */
11
+ export function buildPrompt(context, instruction, numOptions) {
12
+ const isMultiCommand = numOptions > 1;
13
+ // System message differs for single vs multi-command
14
+ const systemMessage = isMultiCommand
15
+ ? buildMultiCommandSystemPrompt(numOptions)
16
+ : buildSingleCommandSystemPrompt();
17
+ // User message includes context and instruction
18
+ const userMessage = buildUserPrompt(context, instruction, numOptions);
19
+ return [
20
+ { role: 'system', content: systemMessage },
21
+ { role: 'user', content: userMessage },
22
+ ];
23
+ }
24
+ /**
25
+ * Build system prompt for single command generation
26
+ */
27
+ function buildSingleCommandSystemPrompt() {
28
+ return `You are a helpful assistant that converts natural language instructions into executable shell commands. Respond with ONLY the command, no explanations or markdown.`;
29
+ }
30
+ /**
31
+ * Build system prompt for multi-command generation
32
+ */
33
+ function buildMultiCommandSystemPrompt(numOptions) {
34
+ return `You are a helpful assistant that converts natural language instructions into executable shell commands. Generate exactly ${numOptions} different command options. Respond ONLY with a JSON object in this format: {"commands": ["cmd1", "cmd2", ...]}. No markdown, no explanations.`;
35
+ }
36
+ /**
37
+ * Build user prompt with context data
38
+ */
39
+ function buildUserPrompt(context, instruction, numOptions) {
40
+ const parts = [];
41
+ // System context
42
+ parts.push(`System Context:
43
+ OS: ${context.system.osName} ${context.system.osVersion}
44
+ Architecture: ${context.system.architecture}
45
+ Shell: ${context.system.shell}
46
+ User: ${context.system.user}
47
+ Memory: ${context.system.totalMemoryMb} MB`);
48
+ // Directory context
49
+ const filesList = context.files.length > 0
50
+ ? context.files.slice(0, 20).join(', ')
51
+ : '(empty directory)';
52
+ parts.push(`\nDirectory Context:
53
+ Current directory: ${context.cwd}
54
+ Files: ${filesList}`);
55
+ // History context (if available)
56
+ if (context.history.length > 0) {
57
+ const historyList = context.history
58
+ .map((h, i) => `${i + 1}. ${h}`)
59
+ .join('\n');
60
+ parts.push(`\nRecent Shell History:\n${historyList}`);
61
+ }
62
+ // Stdin context (if available)
63
+ if (context.stdin) {
64
+ parts.push(`\nStdin input:\n${context.stdin}`);
65
+ }
66
+ // User instruction
67
+ parts.push(`\nUser Instruction: ${instruction}`);
68
+ // Response instruction differs for single vs multi
69
+ if (numOptions > 1) {
70
+ parts.push(`\nRespond with exactly ${numOptions} different command options as JSON: {"commands": ["cmd1", "cmd2", ...]}. Order from simplest to most advanced. No markdown or explanations.`);
71
+ }
72
+ else {
73
+ parts.push(`\nRespond ONLY with the executable command. Do not include markdown code fences, explanations, or any other text. Just the command itself.`);
74
+ }
75
+ return parts.join('');
76
+ }
@@ -0,0 +1 @@
1
+ export { OpenRouterProvider } from './openrouter.js';
@@ -0,0 +1,2 @@
1
+ // Provider exports
2
+ export { OpenRouterProvider } from './openrouter.js';
@@ -0,0 +1,31 @@
1
+ import { AIProvider, ChatRequest, ChatResponse } from '../types.js';
2
+ /**
3
+ * OpenRouter provider implementation
4
+ * Handles API calls with authentication, timeout, and retry logic
5
+ */
6
+ export declare class OpenRouterProvider implements AIProvider {
7
+ name: string;
8
+ private apiKey;
9
+ constructor(apiKey: string);
10
+ /**
11
+ * Check if provider is available (has API key)
12
+ */
13
+ isAvailable(): boolean;
14
+ /**
15
+ * Send completion request to OpenRouter
16
+ * Retries on 429 rate limit with exponential backoff
17
+ */
18
+ complete(request: ChatRequest): Promise<ChatResponse>;
19
+ /**
20
+ * Make the actual HTTP request
21
+ */
22
+ private makeRequest;
23
+ /**
24
+ * Map HTTP status to appropriate AIError
25
+ */
26
+ private mapError;
27
+ /**
28
+ * Parse successful response JSON
29
+ */
30
+ private parseResponse;
31
+ }