add-skill-kit 3.2.3 → 3.2.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.
Files changed (71) hide show
  1. package/lib/agent-cli/lib/audit.js +154 -0
  2. package/lib/agent-cli/lib/audit.test.js +100 -0
  3. package/lib/agent-cli/lib/auto-learn.js +319 -0
  4. package/lib/agent-cli/lib/auto_preview.py +148 -0
  5. package/lib/agent-cli/lib/backup.js +138 -0
  6. package/lib/agent-cli/lib/backup.test.js +78 -0
  7. package/lib/agent-cli/lib/checklist.py +222 -0
  8. package/lib/agent-cli/lib/cognitive-lesson.js +476 -0
  9. package/lib/agent-cli/lib/completion.js +149 -0
  10. package/lib/agent-cli/lib/config.js +35 -0
  11. package/lib/agent-cli/lib/eslint-fix.js +238 -0
  12. package/lib/agent-cli/lib/evolution-signal.js +215 -0
  13. package/lib/agent-cli/lib/export.js +86 -0
  14. package/lib/agent-cli/lib/export.test.js +65 -0
  15. package/lib/agent-cli/lib/fix.js +337 -0
  16. package/lib/agent-cli/lib/fix.test.js +80 -0
  17. package/lib/agent-cli/lib/gemini-export.js +83 -0
  18. package/lib/agent-cli/lib/generate-registry.js +42 -0
  19. package/lib/agent-cli/lib/hooks/install-hooks.js +152 -0
  20. package/lib/agent-cli/lib/hooks/lint-learn.js +172 -0
  21. package/lib/agent-cli/lib/ignore.js +116 -0
  22. package/lib/agent-cli/lib/ignore.test.js +58 -0
  23. package/lib/agent-cli/lib/init.js +124 -0
  24. package/lib/agent-cli/lib/learn.js +255 -0
  25. package/lib/agent-cli/lib/learn.test.js +70 -0
  26. package/lib/agent-cli/lib/migrate-to-v4.js +322 -0
  27. package/lib/agent-cli/lib/proposals.js +199 -0
  28. package/lib/agent-cli/lib/proposals.test.js +56 -0
  29. package/lib/agent-cli/lib/recall.js +820 -0
  30. package/lib/agent-cli/lib/recall.test.js +107 -0
  31. package/lib/agent-cli/lib/selfevolution-bridge.js +167 -0
  32. package/lib/agent-cli/lib/session_manager.py +120 -0
  33. package/lib/agent-cli/lib/settings.js +203 -0
  34. package/lib/agent-cli/lib/skill-learn.js +296 -0
  35. package/lib/agent-cli/lib/stats.js +132 -0
  36. package/lib/agent-cli/lib/stats.test.js +94 -0
  37. package/lib/agent-cli/lib/types.js +33 -0
  38. package/lib/agent-cli/lib/ui/audit-ui.js +146 -0
  39. package/lib/agent-cli/lib/ui/backup-ui.js +107 -0
  40. package/lib/agent-cli/lib/ui/clack-helpers.js +317 -0
  41. package/lib/agent-cli/lib/ui/common.js +83 -0
  42. package/lib/agent-cli/lib/ui/completion-ui.js +126 -0
  43. package/lib/agent-cli/lib/ui/custom-select.js +69 -0
  44. package/lib/agent-cli/lib/ui/dashboard-ui.js +123 -0
  45. package/lib/agent-cli/lib/ui/evolution-signals-ui.js +107 -0
  46. package/lib/agent-cli/lib/ui/export-ui.js +94 -0
  47. package/lib/agent-cli/lib/ui/fix-all-ui.js +191 -0
  48. package/lib/agent-cli/lib/ui/help-ui.js +49 -0
  49. package/lib/agent-cli/lib/ui/index.js +169 -0
  50. package/lib/agent-cli/lib/ui/init-ui.js +56 -0
  51. package/lib/agent-cli/lib/ui/knowledge-ui.js +55 -0
  52. package/lib/agent-cli/lib/ui/learn-ui.js +706 -0
  53. package/lib/agent-cli/lib/ui/lessons-ui.js +148 -0
  54. package/lib/agent-cli/lib/ui/pretty.js +145 -0
  55. package/lib/agent-cli/lib/ui/proposals-ui.js +99 -0
  56. package/lib/agent-cli/lib/ui/recall-ui.js +342 -0
  57. package/lib/agent-cli/lib/ui/routing-demo.js +79 -0
  58. package/lib/agent-cli/lib/ui/routing-ui.js +325 -0
  59. package/lib/agent-cli/lib/ui/settings-ui.js +381 -0
  60. package/lib/agent-cli/lib/ui/stats-ui.js +123 -0
  61. package/lib/agent-cli/lib/ui/watch-ui.js +236 -0
  62. package/lib/agent-cli/lib/verify_all.py +327 -0
  63. package/lib/agent-cli/lib/watcher.js +181 -0
  64. package/lib/agent-cli/lib/watcher.test.js +85 -0
  65. package/lib/agent-cli/package.json +51 -0
  66. package/lib/agentskillskit-cli/README.md +21 -0
  67. package/lib/agentskillskit-cli/ag-smart.js +158 -0
  68. package/lib/agentskillskit-cli/package.json +51 -0
  69. package/package.json +10 -6
  70. /package/{node_modules/agentskillskit-cli → lib/agent-cli}/README.md +0 -0
  71. /package/{node_modules/agentskillskit-cli → lib/agent-cli}/bin/ag-smart.js +0 -0
@@ -0,0 +1,107 @@
1
+ /**
2
+ * @fileoverview Tests for recall.js functionality
3
+ */
4
+ import { describe, it, expect, beforeEach, afterEach } from "vitest";
5
+ import fs from "fs";
6
+ import path from "path";
7
+ import os from "os";
8
+
9
+ const TEST_DIR = path.join(os.tmpdir(), "agent-skill-kit-recall-test");
10
+ const KNOWLEDGE_DIR = path.join(TEST_DIR, ".agent", "knowledge");
11
+ const LESSONS_PATH = path.join(KNOWLEDGE_DIR, "lessons-learned.yaml");
12
+ const TEST_FILE = path.join(TEST_DIR, "test-code.js");
13
+
14
+ describe("Recall - Pattern Matching", () => {
15
+ beforeEach(() => {
16
+ fs.mkdirSync(KNOWLEDGE_DIR, { recursive: true });
17
+ });
18
+
19
+ afterEach(() => {
20
+ fs.rmSync(TEST_DIR, { recursive: true, force: true });
21
+ });
22
+
23
+ it("detects pattern matches in code", () => {
24
+ const codeContent = `
25
+ console.log("debug");
26
+ const x = 1;
27
+ `;
28
+ const pattern = /console\.log/g;
29
+ const matches = codeContent.match(pattern);
30
+
31
+ expect(matches).not.toBeNull();
32
+ expect(matches.length).toBe(1);
33
+ });
34
+
35
+ it("returns null when no pattern matches", () => {
36
+ const codeContent = `const x = 1;`;
37
+ const pattern = /console\.log/g;
38
+ const matches = codeContent.match(pattern);
39
+
40
+ expect(matches).toBeNull();
41
+ });
42
+
43
+ it("counts multiple occurrences", () => {
44
+ const codeContent = `
45
+ console.log("one");
46
+ console.log("two");
47
+ console.log("three");
48
+ `;
49
+ const pattern = /console\.log/g;
50
+ const matches = codeContent.match(pattern);
51
+
52
+ expect(matches).not.toBeNull();
53
+ expect(matches.length).toBe(3);
54
+ });
55
+
56
+ it("can scan files from filesystem", () => {
57
+ const testCode = `console.log("test");`;
58
+ fs.writeFileSync(TEST_FILE, testCode, "utf8");
59
+
60
+ const content = fs.readFileSync(TEST_FILE, "utf8");
61
+ expect(content).toContain("console.log");
62
+ });
63
+
64
+ it("finds line numbers for matches", () => {
65
+ const lines = [
66
+ "const a = 1;",
67
+ "console.log('found');",
68
+ "const b = 2;"
69
+ ];
70
+ const pattern = /console\.log/;
71
+
72
+ const matchingLines = lines
73
+ .map((line, idx) => ({ line, num: idx + 1 }))
74
+ .filter(({ line }) => pattern.test(line));
75
+
76
+ expect(matchingLines.length).toBe(1);
77
+ expect(matchingLines[0].num).toBe(2);
78
+ });
79
+ });
80
+
81
+ describe("Recall - Directory Scanning", () => {
82
+ beforeEach(() => {
83
+ fs.mkdirSync(KNOWLEDGE_DIR, { recursive: true });
84
+ });
85
+
86
+ afterEach(() => {
87
+ fs.rmSync(TEST_DIR, { recursive: true, force: true });
88
+ });
89
+
90
+ it("filters files by extension", () => {
91
+ const extensions = [".js", ".ts", ".tsx"];
92
+ const testFiles = ["app.js", "util.ts", "readme.md", "style.css"];
93
+
94
+ const filtered = testFiles.filter(f =>
95
+ extensions.some(ext => f.endsWith(ext))
96
+ );
97
+
98
+ expect(filtered).toEqual(["app.js", "util.ts"]);
99
+ });
100
+
101
+ it("skips node_modules directory", () => {
102
+ const skipDirs = ["node_modules", ".git", "dist"];
103
+ const testDir = "node_modules";
104
+
105
+ expect(skipDirs.includes(testDir)).toBe(true);
106
+ });
107
+ });
@@ -0,0 +1,167 @@
1
+ /**
2
+ * SelfEvolution Bridge Module
3
+ * Connects Settings API keys with SelfEvolution's AI-powered optimization
4
+ *
5
+ * @module selfevolution-bridge
6
+ */
7
+
8
+ import { getApiKey } from './settings.js';
9
+ import fs from 'fs';
10
+ import path from 'path';
11
+ import { fileURLToPath } from 'url';
12
+ import { spawn } from 'child_process';
13
+
14
+ const __filename = fileURLToPath(import.meta.url);
15
+ const __dirname = path.dirname(__filename);
16
+ const PROJECT_ROOT = path.resolve(__dirname, '../../..');
17
+ const SELFEVOLUTION_DIR = path.join(PROJECT_ROOT, '.agent/skills/SelfEvolution');
18
+
19
+ /**
20
+ * Export API keys from Settings to SelfEvolution .env file
21
+ * This allows Python scripts to auto-detect and use the keys
22
+ *
23
+ * @returns {{success: boolean, exported?: {gemini: boolean, claude: boolean}, path?: string, reason?: string}}
24
+ */
25
+ export function exportApiKeysToSelfEvolution() {
26
+ try {
27
+ const geminiKey = getApiKey('gemini');
28
+ const claudeKey = getApiKey('claude');
29
+
30
+ // Check if any keys are configured
31
+ if (!geminiKey && !claudeKey) {
32
+ return {
33
+ success: false,
34
+ reason: 'No API keys configured in Settings'
35
+ };
36
+ }
37
+
38
+ // Ensure SelfEvolution directory exists
39
+ if (!fs.existsSync(SELFEVOLUTION_DIR)) {
40
+ return {
41
+ success: false,
42
+ reason: 'SelfEvolution skill directory not found'
43
+ };
44
+ }
45
+
46
+ // Build .env content
47
+ const envContent = [];
48
+
49
+ if (geminiKey) {
50
+ envContent.push(`GEMINI_API_KEY=${geminiKey}`);
51
+ }
52
+
53
+ if (claudeKey) {
54
+ envContent.push(`CLAUDE_API_KEY=${claudeKey}`);
55
+ envContent.push(`ANTHROPIC_API_KEY=${claudeKey}`); // Alternative name
56
+ }
57
+
58
+ envContent.push('');
59
+ envContent.push('# Auto-generated from Settings');
60
+ envContent.push(`# Last updated: ${new Date().toISOString()}`);
61
+ envContent.push('# DO NOT EDIT - Managed by CLI Settings');
62
+
63
+ // Write .env file
64
+ const envPath = path.join(SELFEVOLUTION_DIR, '.env');
65
+ fs.writeFileSync(envPath, envContent.join('\n'), 'utf8');
66
+
67
+ return {
68
+ success: true,
69
+ exported: {
70
+ gemini: !!geminiKey,
71
+ claude: !!claudeKey
72
+ },
73
+ path: envPath
74
+ };
75
+ } catch (error) {
76
+ return {
77
+ success: false,
78
+ reason: `Export failed: ${error.message}`
79
+ };
80
+ }
81
+ }
82
+
83
+ /**
84
+ * Verify that SelfEvolution can access the exported API keys
85
+ * Runs Python api_key_resolver.py --test
86
+ *
87
+ * @returns {Promise<{success: boolean, output?: string, error?: string, keyDetected?: boolean}>}
88
+ */
89
+ export async function verifySelfEvolutionAccess() {
90
+ return new Promise((resolve) => {
91
+ const scriptPath = path.join(SELFEVOLUTION_DIR, 'scripts/api_key_resolver.py');
92
+
93
+ // Check if script exists
94
+ if (!fs.existsSync(scriptPath)) {
95
+ resolve({
96
+ success: false,
97
+ error: 'api_key_resolver.py not found'
98
+ });
99
+ return;
100
+ }
101
+
102
+ // Run Python script with --test flag
103
+ const python = spawn('python', [scriptPath, '--test'], {
104
+ cwd: PROJECT_ROOT,
105
+ env: { ...process.env }
106
+ });
107
+
108
+ let stdout = '';
109
+ let stderr = '';
110
+
111
+ python.stdout.on('data', (data) => {
112
+ stdout += data.toString();
113
+ });
114
+
115
+ python.stderr.on('data', (data) => {
116
+ stderr += data.toString();
117
+ });
118
+
119
+ python.on('close', (code) => {
120
+ const success = code === 0;
121
+ const keyDetected = stdout.includes('✓') || stdout.includes('detected');
122
+
123
+ resolve({
124
+ success,
125
+ output: stdout.trim(),
126
+ error: stderr.trim(),
127
+ keyDetected
128
+ });
129
+ });
130
+
131
+ python.on('error', (error) => {
132
+ resolve({
133
+ success: false,
134
+ error: `Failed to run Python: ${error.message}`
135
+ });
136
+ });
137
+ });
138
+ }
139
+
140
+ /**
141
+ * Remove SelfEvolution .env file (cleanup)
142
+ * Called when user removes all API keys from Settings
143
+ *
144
+ * @returns {{success: boolean, reason?: string}}
145
+ */
146
+ export function cleanupSelfEvolutionEnv() {
147
+ try {
148
+ const envPath = path.join(SELFEVOLUTION_DIR, '.env');
149
+
150
+ if (fs.existsSync(envPath)) {
151
+ fs.unlinkSync(envPath);
152
+ }
153
+
154
+ return { success: true };
155
+ } catch (error) {
156
+ return {
157
+ success: false,
158
+ reason: `Cleanup failed: ${error.message}`
159
+ };
160
+ }
161
+ }
162
+
163
+ export default {
164
+ exportApiKeysToSelfEvolution,
165
+ verifySelfEvolutionAccess,
166
+ cleanupSelfEvolutionEnv
167
+ };
@@ -0,0 +1,120 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ Session Manager - Agent Skill Kit
4
+ =================================
5
+ Analyzes project state, detects tech stack, tracks file statistics, and provides
6
+ a summary of the current session.
7
+
8
+ Usage:
9
+ python .agent/scripts/session_manager.py status [path]
10
+ python .agent/scripts/session_manager.py info [path]
11
+ """
12
+
13
+ import os
14
+ import json
15
+ import argparse
16
+ from pathlib import Path
17
+ from typing import Dict, Any, List
18
+
19
+ def get_project_root(path: str) -> Path:
20
+ return Path(path).resolve()
21
+
22
+ def analyze_package_json(root: Path) -> Dict[str, Any]:
23
+ pkg_file = root / "package.json"
24
+ if not pkg_file.exists():
25
+ return {"type": "unknown", "dependencies": {}}
26
+
27
+ try:
28
+ with open(pkg_file, 'r', encoding='utf-8') as f:
29
+ data = json.load(f)
30
+
31
+ deps = data.get("dependencies", {})
32
+ dev_deps = data.get("devDependencies", {})
33
+ all_deps = {**deps, **dev_deps}
34
+
35
+ stack = []
36
+ if "next" in all_deps: stack.append("Next.js")
37
+ elif "react" in all_deps: stack.append("React")
38
+ elif "vue" in all_deps: stack.append("Vue")
39
+ elif "svelte" in all_deps: stack.append("Svelte")
40
+ elif "express" in all_deps: stack.append("Express")
41
+ elif "nestjs" in all_deps or "@nestjs/core" in all_deps: stack.append("NestJS")
42
+
43
+ if "tailwindcss" in all_deps: stack.append("Tailwind CSS")
44
+ if "prisma" in all_deps: stack.append("Prisma")
45
+ if "typescript" in all_deps: stack.append("TypeScript")
46
+
47
+ return {
48
+ "name": data.get("name", "unnamed"),
49
+ "version": data.get("version", "0.0.0"),
50
+ "stack": stack,
51
+ "scripts": list(data.get("scripts", {}).keys())
52
+ }
53
+ except Exception as e:
54
+ return {"error": str(e)}
55
+
56
+ def count_files(root: Path) -> Dict[str, int]:
57
+ stats = {"created": 0, "modified": 0, "total": 0}
58
+ # Simple count for now, comprehensive tracking would require git diff or extensive history
59
+ exclude = {".git", "node_modules", ".next", "dist", "build", ".agent", ".gemini", "__pycache__"}
60
+
61
+ for root_dir, dirs, files in os.walk(root):
62
+ dirs[:] = [d for d in dirs if d not in exclude]
63
+ stats["total"] += len(files)
64
+
65
+ return stats
66
+
67
+ def detect_features(root: Path) -> List[str]:
68
+ # Heuristic: look at folder names in src/
69
+ features = []
70
+ src = root / "src"
71
+ if src.exists():
72
+ possible_dirs = ["components", "modules", "features", "app", "pages", "services"]
73
+ for d in possible_dirs:
74
+ p = src / d
75
+ if p.exists() and p.is_dir():
76
+ # List subdirectories as likely features
77
+ for child in p.iterdir():
78
+ if child.is_dir():
79
+ features.append(child.name)
80
+ return features[:10] # Limit to top 10
81
+
82
+ def print_status(root: Path):
83
+ info = analyze_package_json(root)
84
+ stats = count_files(root)
85
+ features = detect_features(root)
86
+
87
+ print("\n=== Project Status ===")
88
+ print(f"\n📁 Project: {info.get('name', root.name)}")
89
+ print(f"📂 Path: {root}")
90
+ print(f"🏷️ Type: {', '.join(info.get('stack', ['Generic']))}")
91
+ print(f"📊 Status: Active")
92
+
93
+ print("\n🔧 Tech Stack:")
94
+ for tech in info.get('stack', []):
95
+ print(f" • {tech}")
96
+
97
+ print(f"\n✅ Detected Modules/Features ({len(features)}):")
98
+ for feat in features:
99
+ print(f" • {feat}")
100
+ if not features:
101
+ print(" (No distinct feature modules detected)")
102
+
103
+ print(f"\n📄 Files: {stats['total']} total files tracked")
104
+ print("\n====================\n")
105
+
106
+ def main():
107
+ parser = argparse.ArgumentParser(description="Session Manager")
108
+ parser.add_argument("command", choices=["status", "info"], help="Command to run")
109
+ parser.add_argument("path", nargs="?", default=".", help="Project path")
110
+
111
+ args = parser.parse_args()
112
+ root = get_project_root(args.path)
113
+
114
+ if args.command == "status":
115
+ print_status(root)
116
+ elif args.command == "info":
117
+ print(json.dumps(analyze_package_json(root), indent=2))
118
+
119
+ if __name__ == "__main__":
120
+ main()
@@ -0,0 +1,203 @@
1
+ /**
2
+ * @fileoverview Settings management for Agent Skill Kit
3
+ * Handles Auto-Learning and Auto-Updating preferences
4
+ */
5
+
6
+ import fs from "fs";
7
+ import yaml from "js-yaml";
8
+ import path from "path";
9
+ import { KNOWLEDGE_DIR } from "./config.js";
10
+
11
+ /** Path to settings file */
12
+ export const SETTINGS_PATH = path.join(KNOWLEDGE_DIR, "settings.yaml");
13
+
14
+ /** Default settings */
15
+ const DEFAULT_SETTINGS = {
16
+ version: 1,
17
+ autoLearning: true, // ON by default - learn from mistakes
18
+ autoUpdating: false, // OFF by default - requires user trust
19
+ updateThreshold: 5, // Hits before proposing update
20
+ lastUpdateCheck: null,
21
+ apiKeys: {
22
+ gemini: null, // Gemini API key for agent coding
23
+ claude: null, // Claude API key (alternative)
24
+ }
25
+ };
26
+
27
+ /**
28
+ * Load settings from YAML file
29
+ * @returns {object} Settings object
30
+ */
31
+ export function loadSettings() {
32
+ try {
33
+ if (fs.existsSync(SETTINGS_PATH)) {
34
+ const content = fs.readFileSync(SETTINGS_PATH, "utf8");
35
+ return { ...DEFAULT_SETTINGS, ...yaml.load(content) };
36
+ }
37
+ } catch (e) {
38
+ console.error("Failed to load settings:", e.message);
39
+ }
40
+ return { ...DEFAULT_SETTINGS };
41
+ }
42
+
43
+ /**
44
+ * Save settings to YAML file
45
+ * @param {object} settings - Settings to save
46
+ */
47
+ export function saveSettings(settings) {
48
+ try {
49
+ fs.mkdirSync(KNOWLEDGE_DIR, { recursive: true });
50
+ fs.writeFileSync(SETTINGS_PATH, yaml.dump(settings), "utf8");
51
+ return true;
52
+ } catch (e) {
53
+ console.error("Failed to save settings:", e.message);
54
+ return false;
55
+ }
56
+ }
57
+
58
+ /**
59
+ * Toggle Auto-Learning setting
60
+ * @returns {boolean} New value
61
+ */
62
+ export function toggleAutoLearning() {
63
+ const settings = loadSettings();
64
+ settings.autoLearning = !settings.autoLearning;
65
+ saveSettings(settings);
66
+ return settings.autoLearning;
67
+ }
68
+
69
+ /**
70
+ * Toggle Auto-Updating setting
71
+ * @returns {boolean} New value
72
+ */
73
+ export function toggleAutoUpdating() {
74
+ const settings = loadSettings();
75
+ settings.autoUpdating = !settings.autoUpdating;
76
+ saveSettings(settings);
77
+ return settings.autoUpdating;
78
+ }
79
+
80
+ /**
81
+ * Check if Auto-Learning is enabled
82
+ * @returns {boolean}
83
+ */
84
+ export function isAutoLearningEnabled() {
85
+ return loadSettings().autoLearning;
86
+ }
87
+
88
+ /**
89
+ * Check if Auto-Updating is enabled
90
+ * @returns {boolean}
91
+ */
92
+ export function isAutoUpdatingEnabled() {
93
+ return loadSettings().autoUpdating;
94
+ }
95
+
96
+ /**
97
+ * Get update threshold
98
+ * @returns {number}
99
+ */
100
+ export function getUpdateThreshold() {
101
+ return loadSettings().updateThreshold;
102
+ }
103
+
104
+ /**
105
+ * Initialize settings file if not exists
106
+ */
107
+ export function initSettings() {
108
+ if (!fs.existsSync(SETTINGS_PATH)) {
109
+ saveSettings(DEFAULT_SETTINGS);
110
+ }
111
+ }
112
+
113
+ /**
114
+ * Set API key for a provider
115
+ * Auto-exports to SelfEvolution for AI-powered optimization
116
+ * @param {'gemini' | 'claude'} provider - API provider
117
+ * @param {string} apiKey - The API key
118
+ * @returns {boolean} Success status
119
+ */
120
+ export function setApiKey(provider, apiKey) {
121
+ try {
122
+ const settings = loadSettings();
123
+ if (!settings.apiKeys) {
124
+ settings.apiKeys = { gemini: null, claude: null };
125
+ }
126
+ settings.apiKeys[provider] = apiKey;
127
+ saveSettings(settings);
128
+
129
+ // Auto-export to SelfEvolution (async, non-blocking)
130
+ import('./selfevolution-bridge.js')
131
+ .then(({ exportApiKeysToSelfEvolution }) => {
132
+ exportApiKeysToSelfEvolution();
133
+ })
134
+ .catch(() => {
135
+ // Silent fail - SelfEvolution might not be present
136
+ });
137
+
138
+ return true;
139
+ } catch (e) {
140
+ console.error(`Failed to set ${provider} API key:`, e.message);
141
+ return false;
142
+ }
143
+ }
144
+
145
+ /**
146
+ * Get API key for a provider
147
+ * @param {'gemini' | 'claude'} provider - API provider
148
+ * @returns {string | null} The API key or null
149
+ */
150
+ export function getApiKey(provider) {
151
+ const settings = loadSettings();
152
+ return settings.apiKeys?.[provider] || null;
153
+ }
154
+
155
+ /**
156
+ * Remove API key for a provider
157
+ * Auto-syncs with SelfEvolution
158
+ * @param {'gemini' | 'claude'} provider - API provider
159
+ * @returns {boolean} Success status
160
+ */
161
+ export function removeApiKey(provider) {
162
+ try {
163
+ const settings = loadSettings();
164
+ if (settings.apiKeys) {
165
+ settings.apiKeys[provider] = null;
166
+ saveSettings(settings);
167
+
168
+ // Re-export or cleanup if no keys left
169
+ import('./selfevolution-bridge.js')
170
+ .then(({ exportApiKeysToSelfEvolution, cleanupSelfEvolutionEnv }) => {
171
+ const hasAnyKey = settings.apiKeys.gemini || settings.apiKeys.claude;
172
+ if (hasAnyKey) {
173
+ exportApiKeysToSelfEvolution();
174
+ } else {
175
+ cleanupSelfEvolutionEnv();
176
+ }
177
+ })
178
+ .catch(() => {
179
+ // Silent fail
180
+ });
181
+ }
182
+ return true;
183
+ } catch (e) {
184
+ console.error(`Failed to remove ${provider} API key:`, e.message);
185
+ return false;
186
+ }
187
+ }
188
+
189
+ export default {
190
+ loadSettings,
191
+ saveSettings,
192
+ toggleAutoLearning,
193
+ toggleAutoUpdating,
194
+ isAutoLearningEnabled,
195
+ isAutoUpdatingEnabled,
196
+ getUpdateThreshold,
197
+ initSettings,
198
+ setApiKey,
199
+ getApiKey,
200
+ removeApiKey,
201
+ SETTINGS_PATH
202
+ };
203
+