@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,103 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* UI Types and Interfaces
|
|
3
|
+
*
|
|
4
|
+
* Shared types used across all UI modules.
|
|
5
|
+
*/
|
|
6
|
+
export interface UserMessage {
|
|
7
|
+
type: 'user';
|
|
8
|
+
content: string;
|
|
9
|
+
timestamp: Date;
|
|
10
|
+
}
|
|
11
|
+
export interface AssistantMessage {
|
|
12
|
+
type: 'assistant';
|
|
13
|
+
content: string;
|
|
14
|
+
timestamp: Date;
|
|
15
|
+
}
|
|
16
|
+
export interface ToolMessage {
|
|
17
|
+
type: 'tool';
|
|
18
|
+
name: string;
|
|
19
|
+
args?: string;
|
|
20
|
+
result?: string;
|
|
21
|
+
timestamp: Date;
|
|
22
|
+
}
|
|
23
|
+
export type ConversationMessage = UserMessage | AssistantMessage | ToolMessage;
|
|
24
|
+
export interface TodoItem {
|
|
25
|
+
content: string;
|
|
26
|
+
status: 'pending' | 'in_progress' | 'completed';
|
|
27
|
+
activeForm?: string;
|
|
28
|
+
}
|
|
29
|
+
export interface SpinnerState {
|
|
30
|
+
text: string;
|
|
31
|
+
tool?: string;
|
|
32
|
+
tokens: number;
|
|
33
|
+
startTime: number;
|
|
34
|
+
}
|
|
35
|
+
export interface CommandOption {
|
|
36
|
+
command: string;
|
|
37
|
+
description: string;
|
|
38
|
+
}
|
|
39
|
+
export interface AutocompleteState {
|
|
40
|
+
options: CommandOption[];
|
|
41
|
+
selectedIndex: number;
|
|
42
|
+
inputPrefix: string;
|
|
43
|
+
}
|
|
44
|
+
export type InputResult = {
|
|
45
|
+
action: 'submit';
|
|
46
|
+
value: string;
|
|
47
|
+
} | {
|
|
48
|
+
action: 'command';
|
|
49
|
+
command: string;
|
|
50
|
+
args: string;
|
|
51
|
+
} | {
|
|
52
|
+
action: 'cancel';
|
|
53
|
+
} | {
|
|
54
|
+
action: 'continue';
|
|
55
|
+
};
|
|
56
|
+
export interface PhysicalLayout {
|
|
57
|
+
lines: string[];
|
|
58
|
+
cursorRow: number;
|
|
59
|
+
cursorCol: number;
|
|
60
|
+
totalRows: number;
|
|
61
|
+
}
|
|
62
|
+
export interface ContextStats {
|
|
63
|
+
tokens: number;
|
|
64
|
+
maxTokens: number;
|
|
65
|
+
messages: number;
|
|
66
|
+
turns: number;
|
|
67
|
+
utilization: number;
|
|
68
|
+
}
|
|
69
|
+
export type PermissionResult = 'allow' | 'deny' | 'allow-always';
|
|
70
|
+
/**
|
|
71
|
+
* Agent execution modes (cycle with Shift+Tab)
|
|
72
|
+
*
|
|
73
|
+
* - normal: Ask permission for dangerous operations (default)
|
|
74
|
+
* - auto-accept: Execute all operations without asking
|
|
75
|
+
* - plan: Generate plan only, don't execute (placeholder for now)
|
|
76
|
+
*/
|
|
77
|
+
export type AgentMode = 'normal' | 'auto-accept' | 'plan';
|
|
78
|
+
/**
|
|
79
|
+
* Mode display info
|
|
80
|
+
*/
|
|
81
|
+
export interface ModeInfo {
|
|
82
|
+
mode: AgentMode;
|
|
83
|
+
label: string;
|
|
84
|
+
description: string;
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Get display info for a mode
|
|
88
|
+
*/
|
|
89
|
+
export declare const MODE_INFO: Record<AgentMode, ModeInfo>;
|
|
90
|
+
/**
|
|
91
|
+
* Get next mode in cycle
|
|
92
|
+
*/
|
|
93
|
+
export declare function getNextMode(current: AgentMode): AgentMode;
|
|
94
|
+
export interface REPLState {
|
|
95
|
+
conversationHistory: ConversationMessage[];
|
|
96
|
+
spinner: SpinnerState | null;
|
|
97
|
+
todos: TodoItem[];
|
|
98
|
+
pendingMessages: string[];
|
|
99
|
+
isAgentRunning: boolean;
|
|
100
|
+
currentModel: string;
|
|
101
|
+
inputHistory: string[];
|
|
102
|
+
allowedTools: Set<string>;
|
|
103
|
+
}
|
package/dist/ui/types.js
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* UI Types and Interfaces
|
|
3
|
+
*
|
|
4
|
+
* Shared types used across all UI modules.
|
|
5
|
+
*/
|
|
6
|
+
/**
|
|
7
|
+
* Get display info for a mode
|
|
8
|
+
*/
|
|
9
|
+
export const MODE_INFO = {
|
|
10
|
+
'normal': {
|
|
11
|
+
mode: 'normal',
|
|
12
|
+
label: 'Normal',
|
|
13
|
+
description: 'Ask permission for dangerous operations',
|
|
14
|
+
},
|
|
15
|
+
'auto-accept': {
|
|
16
|
+
mode: 'auto-accept',
|
|
17
|
+
label: 'Auto-accept',
|
|
18
|
+
description: 'Execute all operations without asking',
|
|
19
|
+
},
|
|
20
|
+
'plan': {
|
|
21
|
+
mode: 'plan',
|
|
22
|
+
label: 'Plan',
|
|
23
|
+
description: 'Generate plan only (coming soon)',
|
|
24
|
+
},
|
|
25
|
+
};
|
|
26
|
+
/**
|
|
27
|
+
* Get next mode in cycle
|
|
28
|
+
*/
|
|
29
|
+
export function getNextMode(current) {
|
|
30
|
+
const modes = ['normal', 'auto-accept', 'plan'];
|
|
31
|
+
const currentIndex = modes.indexOf(current);
|
|
32
|
+
return modes[(currentIndex + 1) % modes.length];
|
|
33
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Credentials Management
|
|
3
|
+
*
|
|
4
|
+
* Securely stores API keys using AES-256-GCM encryption.
|
|
5
|
+
* Encryption key is derived from:
|
|
6
|
+
* - Machine-specific identifiers (hostname, username)
|
|
7
|
+
* - A random "mk" value stored in settings.json
|
|
8
|
+
*/
|
|
9
|
+
export type ProviderKey = 'anthropic' | 'openai' | 'google' | 'ollama';
|
|
10
|
+
export interface StoredCredentials {
|
|
11
|
+
anthropic?: string;
|
|
12
|
+
openai?: string;
|
|
13
|
+
google?: string;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Get API key for a provider
|
|
17
|
+
* Priority: Environment variable > Stored credential
|
|
18
|
+
*/
|
|
19
|
+
export declare function getApiKey(provider: ProviderKey): string | null;
|
|
20
|
+
/**
|
|
21
|
+
* Set API key for a provider (stores encrypted)
|
|
22
|
+
*/
|
|
23
|
+
export declare function setApiKey(provider: ProviderKey, key: string): void;
|
|
24
|
+
/**
|
|
25
|
+
* Delete API key for a provider
|
|
26
|
+
*/
|
|
27
|
+
export declare function deleteApiKey(provider: ProviderKey): void;
|
|
28
|
+
/**
|
|
29
|
+
* Check if API key exists for a provider
|
|
30
|
+
*/
|
|
31
|
+
export declare function hasApiKey(provider: ProviderKey): boolean;
|
|
32
|
+
/**
|
|
33
|
+
* Get masked version of API key for display
|
|
34
|
+
* Shows first 7 and last 4 characters
|
|
35
|
+
*/
|
|
36
|
+
export declare function getMaskedKey(provider: ProviderKey): string | null;
|
|
37
|
+
/**
|
|
38
|
+
* Check if key is from environment variable
|
|
39
|
+
*/
|
|
40
|
+
export declare function isKeyFromEnv(provider: ProviderKey): boolean;
|
|
41
|
+
/**
|
|
42
|
+
* Get all provider statuses
|
|
43
|
+
*/
|
|
44
|
+
export declare function getAllProviderStatuses(): Array<{
|
|
45
|
+
provider: ProviderKey;
|
|
46
|
+
name: string;
|
|
47
|
+
hasKey: boolean;
|
|
48
|
+
masked: string | null;
|
|
49
|
+
fromEnv: boolean;
|
|
50
|
+
keyUrl: string;
|
|
51
|
+
}>;
|
|
52
|
+
/**
|
|
53
|
+
* Map provider type from settings to credential provider key
|
|
54
|
+
*/
|
|
55
|
+
export declare function settingsProviderToCredentialKey(provider: 'auto' | 'claude' | 'openai' | 'gemini' | 'ollama'): ProviderKey;
|
|
@@ -0,0 +1,268 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Credentials Management
|
|
3
|
+
*
|
|
4
|
+
* Securely stores API keys using AES-256-GCM encryption.
|
|
5
|
+
* Encryption key is derived from:
|
|
6
|
+
* - Machine-specific identifiers (hostname, username)
|
|
7
|
+
* - A random "mk" value stored in settings.json
|
|
8
|
+
*/
|
|
9
|
+
import * as crypto from 'crypto';
|
|
10
|
+
import * as fs from 'fs';
|
|
11
|
+
import * as path from 'path';
|
|
12
|
+
import * as os from 'os';
|
|
13
|
+
// =============================================================================
|
|
14
|
+
// Constants
|
|
15
|
+
// =============================================================================
|
|
16
|
+
const CONFIG_DIR = path.join(os.homedir(), '.compilr-dev');
|
|
17
|
+
const SETTINGS_FILE = path.join(CONFIG_DIR, 'settings.json');
|
|
18
|
+
const CREDENTIALS_FILE = path.join(CONFIG_DIR, 'credentials.enc');
|
|
19
|
+
const ALGORITHM = 'aes-256-gcm';
|
|
20
|
+
const SALT = 'compilr-dev-v1';
|
|
21
|
+
// =============================================================================
|
|
22
|
+
// Master Key Management
|
|
23
|
+
// =============================================================================
|
|
24
|
+
/**
|
|
25
|
+
* Get or create the master key ("mk") from settings.json
|
|
26
|
+
*/
|
|
27
|
+
function getMasterKey() {
|
|
28
|
+
try {
|
|
29
|
+
// Ensure config directory exists
|
|
30
|
+
fs.mkdirSync(CONFIG_DIR, { recursive: true });
|
|
31
|
+
// Load settings
|
|
32
|
+
let settings = {};
|
|
33
|
+
if (fs.existsSync(SETTINGS_FILE)) {
|
|
34
|
+
const parsed = JSON.parse(fs.readFileSync(SETTINGS_FILE, 'utf-8'));
|
|
35
|
+
if (parsed && typeof parsed === 'object') {
|
|
36
|
+
settings = parsed;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
// Check if mk exists
|
|
40
|
+
if (settings.mk && typeof settings.mk === 'string' && settings.mk.length === 64) {
|
|
41
|
+
return settings.mk;
|
|
42
|
+
}
|
|
43
|
+
// Generate new mk (32 bytes = 64 hex chars)
|
|
44
|
+
const mk = crypto.randomBytes(32).toString('hex');
|
|
45
|
+
settings.mk = mk;
|
|
46
|
+
// Save back to settings
|
|
47
|
+
fs.writeFileSync(SETTINGS_FILE, JSON.stringify(settings, null, 2), 'utf-8');
|
|
48
|
+
return mk;
|
|
49
|
+
}
|
|
50
|
+
catch (error) {
|
|
51
|
+
throw new Error(`Failed to manage master key: ${error instanceof Error ? error.message : String(error)}`);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Derive encryption key from master key + machine identifiers
|
|
56
|
+
*/
|
|
57
|
+
function deriveKey() {
|
|
58
|
+
const mk = getMasterKey();
|
|
59
|
+
const machineId = `${os.hostname()}:${os.userInfo().username}`;
|
|
60
|
+
// Use scrypt for key derivation (secure, slow by design)
|
|
61
|
+
return crypto.scryptSync(`${mk}:${machineId}`, SALT, 32);
|
|
62
|
+
}
|
|
63
|
+
// =============================================================================
|
|
64
|
+
// Encryption/Decryption
|
|
65
|
+
// =============================================================================
|
|
66
|
+
/**
|
|
67
|
+
* Encrypt data using AES-256-GCM
|
|
68
|
+
*/
|
|
69
|
+
function encrypt(data) {
|
|
70
|
+
const key = deriveKey();
|
|
71
|
+
const iv = crypto.randomBytes(16);
|
|
72
|
+
const cipher = crypto.createCipheriv(ALGORITHM, key, iv);
|
|
73
|
+
let encrypted = cipher.update(data, 'utf8', 'hex');
|
|
74
|
+
encrypted += cipher.final('hex');
|
|
75
|
+
const authTag = cipher.getAuthTag();
|
|
76
|
+
// Format: iv:authTag:encrypted
|
|
77
|
+
return `${iv.toString('hex')}:${authTag.toString('hex')}:${encrypted}`;
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Decrypt data using AES-256-GCM
|
|
81
|
+
*/
|
|
82
|
+
function decrypt(encryptedData) {
|
|
83
|
+
const key = deriveKey();
|
|
84
|
+
const parts = encryptedData.split(':');
|
|
85
|
+
if (parts.length !== 3) {
|
|
86
|
+
throw new Error('Invalid encrypted data format');
|
|
87
|
+
}
|
|
88
|
+
const iv = Buffer.from(parts[0], 'hex');
|
|
89
|
+
const authTag = Buffer.from(parts[1], 'hex');
|
|
90
|
+
const encrypted = parts[2];
|
|
91
|
+
const decipher = crypto.createDecipheriv(ALGORITHM, key, iv);
|
|
92
|
+
decipher.setAuthTag(authTag);
|
|
93
|
+
let decrypted = decipher.update(encrypted, 'hex', 'utf8');
|
|
94
|
+
decrypted += decipher.final('utf8');
|
|
95
|
+
return decrypted;
|
|
96
|
+
}
|
|
97
|
+
// =============================================================================
|
|
98
|
+
// Credentials Storage
|
|
99
|
+
// =============================================================================
|
|
100
|
+
/**
|
|
101
|
+
* Load all stored credentials
|
|
102
|
+
*/
|
|
103
|
+
function loadCredentials() {
|
|
104
|
+
try {
|
|
105
|
+
if (!fs.existsSync(CREDENTIALS_FILE)) {
|
|
106
|
+
return {};
|
|
107
|
+
}
|
|
108
|
+
const encryptedData = fs.readFileSync(CREDENTIALS_FILE, 'utf-8');
|
|
109
|
+
const decrypted = decrypt(encryptedData);
|
|
110
|
+
return JSON.parse(decrypted);
|
|
111
|
+
}
|
|
112
|
+
catch {
|
|
113
|
+
// If decryption fails (e.g., key changed), return empty
|
|
114
|
+
return {};
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* Save all credentials
|
|
119
|
+
*/
|
|
120
|
+
function saveCredentials(credentials) {
|
|
121
|
+
fs.mkdirSync(CONFIG_DIR, { recursive: true });
|
|
122
|
+
const encrypted = encrypt(JSON.stringify(credentials));
|
|
123
|
+
fs.writeFileSync(CREDENTIALS_FILE, encrypted, 'utf-8');
|
|
124
|
+
}
|
|
125
|
+
// =============================================================================
|
|
126
|
+
// Public API
|
|
127
|
+
// =============================================================================
|
|
128
|
+
/**
|
|
129
|
+
* Get API key for a provider
|
|
130
|
+
* Priority: Environment variable > Stored credential
|
|
131
|
+
*/
|
|
132
|
+
export function getApiKey(provider) {
|
|
133
|
+
// Check environment variable first
|
|
134
|
+
const envVarMap = {
|
|
135
|
+
anthropic: 'ANTHROPIC_API_KEY',
|
|
136
|
+
openai: 'OPENAI_API_KEY',
|
|
137
|
+
google: 'GOOGLE_API_KEY',
|
|
138
|
+
ollama: '', // No key needed
|
|
139
|
+
};
|
|
140
|
+
const envVar = envVarMap[provider];
|
|
141
|
+
if (envVar && process.env[envVar]) {
|
|
142
|
+
return process.env[envVar] ?? null;
|
|
143
|
+
}
|
|
144
|
+
// Check stored credentials
|
|
145
|
+
if (provider === 'ollama') {
|
|
146
|
+
return 'local'; // Ollama doesn't need a key
|
|
147
|
+
}
|
|
148
|
+
const credentials = loadCredentials();
|
|
149
|
+
return credentials[provider] ?? null;
|
|
150
|
+
}
|
|
151
|
+
/**
|
|
152
|
+
* Set API key for a provider (stores encrypted)
|
|
153
|
+
*/
|
|
154
|
+
export function setApiKey(provider, key) {
|
|
155
|
+
if (provider === 'ollama') {
|
|
156
|
+
return; // Ollama doesn't need a key
|
|
157
|
+
}
|
|
158
|
+
const credentials = loadCredentials();
|
|
159
|
+
credentials[provider] = key;
|
|
160
|
+
saveCredentials(credentials);
|
|
161
|
+
}
|
|
162
|
+
/**
|
|
163
|
+
* Delete API key for a provider
|
|
164
|
+
*/
|
|
165
|
+
export function deleteApiKey(provider) {
|
|
166
|
+
const credentials = loadCredentials();
|
|
167
|
+
// Remove the key by setting to undefined and then filtering
|
|
168
|
+
if (provider === 'anthropic') {
|
|
169
|
+
delete credentials.anthropic;
|
|
170
|
+
}
|
|
171
|
+
else if (provider === 'openai') {
|
|
172
|
+
delete credentials.openai;
|
|
173
|
+
}
|
|
174
|
+
else if (provider === 'google') {
|
|
175
|
+
delete credentials.google;
|
|
176
|
+
}
|
|
177
|
+
saveCredentials(credentials);
|
|
178
|
+
}
|
|
179
|
+
/**
|
|
180
|
+
* Check if API key exists for a provider
|
|
181
|
+
*/
|
|
182
|
+
export function hasApiKey(provider) {
|
|
183
|
+
return getApiKey(provider) !== null;
|
|
184
|
+
}
|
|
185
|
+
/**
|
|
186
|
+
* Get masked version of API key for display
|
|
187
|
+
* Shows first 7 and last 4 characters
|
|
188
|
+
*/
|
|
189
|
+
export function getMaskedKey(provider) {
|
|
190
|
+
const key = getApiKey(provider);
|
|
191
|
+
if (!key || key === 'local') {
|
|
192
|
+
return key;
|
|
193
|
+
}
|
|
194
|
+
if (key.length <= 15) {
|
|
195
|
+
return '***';
|
|
196
|
+
}
|
|
197
|
+
return `${key.substring(0, 7)}...${key.substring(key.length - 4)}`;
|
|
198
|
+
}
|
|
199
|
+
/**
|
|
200
|
+
* Check if key is from environment variable
|
|
201
|
+
*/
|
|
202
|
+
export function isKeyFromEnv(provider) {
|
|
203
|
+
const envVarMap = {
|
|
204
|
+
anthropic: 'ANTHROPIC_API_KEY',
|
|
205
|
+
openai: 'OPENAI_API_KEY',
|
|
206
|
+
google: 'GOOGLE_API_KEY',
|
|
207
|
+
ollama: '',
|
|
208
|
+
};
|
|
209
|
+
const envVar = envVarMap[provider];
|
|
210
|
+
return !!(envVar && process.env[envVar]);
|
|
211
|
+
}
|
|
212
|
+
/**
|
|
213
|
+
* Get all provider statuses
|
|
214
|
+
*/
|
|
215
|
+
export function getAllProviderStatuses() {
|
|
216
|
+
return [
|
|
217
|
+
{
|
|
218
|
+
provider: 'anthropic',
|
|
219
|
+
name: 'Anthropic (Claude)',
|
|
220
|
+
hasKey: hasApiKey('anthropic'),
|
|
221
|
+
masked: getMaskedKey('anthropic'),
|
|
222
|
+
fromEnv: isKeyFromEnv('anthropic'),
|
|
223
|
+
keyUrl: 'https://console.anthropic.com/settings/keys',
|
|
224
|
+
},
|
|
225
|
+
{
|
|
226
|
+
provider: 'openai',
|
|
227
|
+
name: 'OpenAI',
|
|
228
|
+
hasKey: hasApiKey('openai'),
|
|
229
|
+
masked: getMaskedKey('openai'),
|
|
230
|
+
fromEnv: isKeyFromEnv('openai'),
|
|
231
|
+
keyUrl: 'https://platform.openai.com/api-keys',
|
|
232
|
+
},
|
|
233
|
+
{
|
|
234
|
+
provider: 'google',
|
|
235
|
+
name: 'Google AI (Gemini)',
|
|
236
|
+
hasKey: hasApiKey('google'),
|
|
237
|
+
masked: getMaskedKey('google'),
|
|
238
|
+
fromEnv: isKeyFromEnv('google'),
|
|
239
|
+
keyUrl: 'https://aistudio.google.com/apikey',
|
|
240
|
+
},
|
|
241
|
+
{
|
|
242
|
+
provider: 'ollama',
|
|
243
|
+
name: 'Ollama (Local)',
|
|
244
|
+
hasKey: true, // Always available
|
|
245
|
+
masked: 'local',
|
|
246
|
+
fromEnv: false,
|
|
247
|
+
keyUrl: 'https://ollama.ai',
|
|
248
|
+
},
|
|
249
|
+
];
|
|
250
|
+
}
|
|
251
|
+
/**
|
|
252
|
+
* Map provider type from settings to credential provider key
|
|
253
|
+
*/
|
|
254
|
+
export function settingsProviderToCredentialKey(provider) {
|
|
255
|
+
switch (provider) {
|
|
256
|
+
case 'claude':
|
|
257
|
+
return 'anthropic';
|
|
258
|
+
case 'gemini':
|
|
259
|
+
return 'google';
|
|
260
|
+
case 'openai':
|
|
261
|
+
return 'openai';
|
|
262
|
+
case 'ollama':
|
|
263
|
+
return 'ollama';
|
|
264
|
+
case 'auto':
|
|
265
|
+
default:
|
|
266
|
+
return 'anthropic'; // Default to anthropic for auto
|
|
267
|
+
}
|
|
268
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Model Tier Detection
|
|
3
|
+
*
|
|
4
|
+
* Categorizes LLM models by capability tier (small/medium/large)
|
|
5
|
+
* and provides upgrade suggestions within the same provider.
|
|
6
|
+
*
|
|
7
|
+
* Used by commands like /design to warn users about model limitations
|
|
8
|
+
* and suggest alternatives.
|
|
9
|
+
*/
|
|
10
|
+
export type TierLevel = 'small' | 'medium' | 'large';
|
|
11
|
+
export interface ModelTier {
|
|
12
|
+
tier: TierLevel;
|
|
13
|
+
provider: string;
|
|
14
|
+
suggestedUpgrade?: string;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Get the tier information for a model.
|
|
18
|
+
* Handles versioned model names (e.g., claude-3-5-sonnet-20241022)
|
|
19
|
+
*/
|
|
20
|
+
export declare function getModelTier(modelId: string): ModelTier;
|
|
21
|
+
/**
|
|
22
|
+
* Get the minimum tier required for a command.
|
|
23
|
+
*/
|
|
24
|
+
export declare function getCommandMinTier(command: string): TierLevel;
|
|
25
|
+
/**
|
|
26
|
+
* Compare two tier levels.
|
|
27
|
+
* Returns: -1 if a < b, 0 if a == b, 1 if a > b
|
|
28
|
+
*/
|
|
29
|
+
export declare function compareTiers(a: TierLevel, b: TierLevel): number;
|
|
30
|
+
/**
|
|
31
|
+
* Check if a model meets the minimum tier requirement for a command.
|
|
32
|
+
*/
|
|
33
|
+
export declare function modelMeetsTier(modelId: string, command: string): boolean;
|
|
34
|
+
/**
|
|
35
|
+
* Get a friendly display name for a tier.
|
|
36
|
+
*/
|
|
37
|
+
export declare function getTierDisplayName(tier: TierLevel): string;
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Model Tier Detection
|
|
3
|
+
*
|
|
4
|
+
* Categorizes LLM models by capability tier (small/medium/large)
|
|
5
|
+
* and provides upgrade suggestions within the same provider.
|
|
6
|
+
*
|
|
7
|
+
* Used by commands like /design to warn users about model limitations
|
|
8
|
+
* and suggest alternatives.
|
|
9
|
+
*/
|
|
10
|
+
// =============================================================================
|
|
11
|
+
// Model Tier Mappings
|
|
12
|
+
// =============================================================================
|
|
13
|
+
const MODEL_TIERS = {
|
|
14
|
+
// Claude (Anthropic)
|
|
15
|
+
'claude-3-opus': { tier: 'large', provider: 'anthropic' },
|
|
16
|
+
'claude-opus-4': { tier: 'large', provider: 'anthropic' },
|
|
17
|
+
'claude-sonnet-4': { tier: 'large', provider: 'anthropic' },
|
|
18
|
+
'claude-3-5-sonnet': { tier: 'large', provider: 'anthropic' },
|
|
19
|
+
'claude-3-sonnet': { tier: 'large', provider: 'anthropic' },
|
|
20
|
+
'claude-3-5-haiku': { tier: 'medium', provider: 'anthropic', suggestedUpgrade: 'claude-sonnet-4-20250514' },
|
|
21
|
+
'claude-3-haiku': { tier: 'medium', provider: 'anthropic', suggestedUpgrade: 'claude-sonnet-4-20250514' },
|
|
22
|
+
// OpenAI
|
|
23
|
+
'gpt-4o': { tier: 'large', provider: 'openai' },
|
|
24
|
+
'gpt-4-turbo': { tier: 'large', provider: 'openai' },
|
|
25
|
+
'gpt-4': { tier: 'large', provider: 'openai' },
|
|
26
|
+
'gpt-4o-mini': { tier: 'medium', provider: 'openai', suggestedUpgrade: 'gpt-4o' },
|
|
27
|
+
'gpt-3.5-turbo': { tier: 'small', provider: 'openai', suggestedUpgrade: 'gpt-4o-mini' },
|
|
28
|
+
'o1': { tier: 'large', provider: 'openai' },
|
|
29
|
+
'o1-mini': { tier: 'medium', provider: 'openai', suggestedUpgrade: 'o1' },
|
|
30
|
+
'o1-preview': { tier: 'large', provider: 'openai' },
|
|
31
|
+
// Google (Gemini)
|
|
32
|
+
'gemini-1.5-pro': { tier: 'large', provider: 'google' },
|
|
33
|
+
'gemini-2.0-flash': { tier: 'large', provider: 'google' },
|
|
34
|
+
'gemini-2.5-pro': { tier: 'large', provider: 'google' },
|
|
35
|
+
'gemini-2.5-flash': { tier: 'medium', provider: 'google', suggestedUpgrade: 'gemini-2.5-pro' },
|
|
36
|
+
'gemini-1.5-flash': { tier: 'medium', provider: 'google', suggestedUpgrade: 'gemini-1.5-pro' },
|
|
37
|
+
'gemini-2.0-flash-lite': { tier: 'small', provider: 'google', suggestedUpgrade: 'gemini-2.0-flash' },
|
|
38
|
+
// Ollama / Local models (common ones)
|
|
39
|
+
'llama3.2': { tier: 'small', provider: 'ollama', suggestedUpgrade: 'llama3.1:70b' },
|
|
40
|
+
'llama3.1': { tier: 'medium', provider: 'ollama', suggestedUpgrade: 'llama3.1:70b' },
|
|
41
|
+
'llama3.1:70b': { tier: 'large', provider: 'ollama' },
|
|
42
|
+
'mistral': { tier: 'small', provider: 'ollama' },
|
|
43
|
+
'mixtral': { tier: 'medium', provider: 'ollama' },
|
|
44
|
+
'codellama': { tier: 'medium', provider: 'ollama' },
|
|
45
|
+
'deepseek-coder': { tier: 'medium', provider: 'ollama' },
|
|
46
|
+
'qwen2.5-coder': { tier: 'medium', provider: 'ollama' },
|
|
47
|
+
};
|
|
48
|
+
// =============================================================================
|
|
49
|
+
// Functions
|
|
50
|
+
// =============================================================================
|
|
51
|
+
/**
|
|
52
|
+
* Get the tier information for a model.
|
|
53
|
+
* Handles versioned model names (e.g., claude-3-5-sonnet-20241022)
|
|
54
|
+
*/
|
|
55
|
+
export function getModelTier(modelId) {
|
|
56
|
+
// Check exact match first
|
|
57
|
+
if (modelId in MODEL_TIERS) {
|
|
58
|
+
return MODEL_TIERS[modelId];
|
|
59
|
+
}
|
|
60
|
+
// Check partial match (for versioned models like claude-3-5-sonnet-20241022)
|
|
61
|
+
for (const [key, value] of Object.entries(MODEL_TIERS)) {
|
|
62
|
+
if (modelId.startsWith(key)) {
|
|
63
|
+
return value;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
// Ollama/local models - assume small unless known
|
|
67
|
+
if (modelId.includes('ollama') || modelId.includes('localhost')) {
|
|
68
|
+
return { tier: 'small', provider: 'ollama' };
|
|
69
|
+
}
|
|
70
|
+
// Unknown - assume medium (safer than assuming small)
|
|
71
|
+
return { tier: 'medium', provider: 'unknown' };
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Get the minimum tier required for a command.
|
|
75
|
+
*/
|
|
76
|
+
export function getCommandMinTier(command) {
|
|
77
|
+
const requirements = {
|
|
78
|
+
'/design': 'large',
|
|
79
|
+
'/refine': 'large',
|
|
80
|
+
'/arch': 'large',
|
|
81
|
+
'/build': 'medium',
|
|
82
|
+
'/prd': 'medium',
|
|
83
|
+
'/sketch': 'small', // Simplified version - works with any model
|
|
84
|
+
'/init': 'small',
|
|
85
|
+
'/backlog': 'small',
|
|
86
|
+
'/note': 'small',
|
|
87
|
+
};
|
|
88
|
+
return requirements[command] ?? 'small';
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Compare two tier levels.
|
|
92
|
+
* Returns: -1 if a < b, 0 if a == b, 1 if a > b
|
|
93
|
+
*/
|
|
94
|
+
export function compareTiers(a, b) {
|
|
95
|
+
const order = { small: 0, medium: 1, large: 2 };
|
|
96
|
+
return order[a] - order[b];
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* Check if a model meets the minimum tier requirement for a command.
|
|
100
|
+
*/
|
|
101
|
+
export function modelMeetsTier(modelId, command) {
|
|
102
|
+
const modelTier = getModelTier(modelId);
|
|
103
|
+
const minTier = getCommandMinTier(command);
|
|
104
|
+
return compareTiers(modelTier.tier, minTier) >= 0;
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* Get a friendly display name for a tier.
|
|
108
|
+
*/
|
|
109
|
+
export function getTierDisplayName(tier) {
|
|
110
|
+
switch (tier) {
|
|
111
|
+
case 'small':
|
|
112
|
+
return 'Small';
|
|
113
|
+
case 'medium':
|
|
114
|
+
return 'Medium';
|
|
115
|
+
case 'large':
|
|
116
|
+
return 'Large';
|
|
117
|
+
}
|
|
118
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Project Memory Loader
|
|
3
|
+
*
|
|
4
|
+
* Loads project-level instructions from COMPILR.md or CLAUDE.md files.
|
|
5
|
+
* Provides size guards and warnings for large files.
|
|
6
|
+
*/
|
|
7
|
+
export interface ProjectMemoryResult {
|
|
8
|
+
/** Whether a memory file was found */
|
|
9
|
+
found: boolean;
|
|
10
|
+
/** The loaded content (may be truncated) */
|
|
11
|
+
content: string;
|
|
12
|
+
/** Path to the file that was loaded */
|
|
13
|
+
filePath: string | null;
|
|
14
|
+
/** Original file size in bytes */
|
|
15
|
+
originalSize: number;
|
|
16
|
+
/** Whether the content was truncated */
|
|
17
|
+
truncated: boolean;
|
|
18
|
+
/** Estimated token count (chars / 4) */
|
|
19
|
+
estimatedTokens: number;
|
|
20
|
+
}
|
|
21
|
+
export interface ProjectMemoryOptions {
|
|
22
|
+
/** Maximum content size in bytes before truncation (default: 100KB) */
|
|
23
|
+
maxSize?: number;
|
|
24
|
+
/** Size threshold for warning (default: 30KB) */
|
|
25
|
+
warnSize?: number;
|
|
26
|
+
/** Custom search directory (default: process.cwd()) */
|
|
27
|
+
cwd?: string;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Load project memory from COMPILR.md or CLAUDE.md
|
|
31
|
+
*
|
|
32
|
+
* @param options - Configuration options
|
|
33
|
+
* @returns Result with content and metadata
|
|
34
|
+
*/
|
|
35
|
+
export declare function loadProjectMemory(options?: ProjectMemoryOptions): ProjectMemoryResult;
|
|
36
|
+
/**
|
|
37
|
+
* Check if a project memory file exists without loading it
|
|
38
|
+
*/
|
|
39
|
+
export declare function hasProjectMemory(cwd?: string): boolean;
|
|
40
|
+
/**
|
|
41
|
+
* Get the size category for display purposes
|
|
42
|
+
*/
|
|
43
|
+
export declare function getSizeCategory(bytes: number): 'small' | 'medium' | 'large';
|
|
44
|
+
/**
|
|
45
|
+
* Format bytes as human-readable string
|
|
46
|
+
*/
|
|
47
|
+
export declare function formatBytes(bytes: number): string;
|