add-skill-kit 3.2.2 → 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.
- package/bin/lib/commands/install.js +67 -45
- package/lib/agent-cli/README.md +21 -0
- package/lib/agent-cli/bin/ag-smart.js +158 -0
- package/lib/agent-cli/lib/audit.js +154 -0
- package/lib/agent-cli/lib/audit.test.js +100 -0
- package/lib/agent-cli/lib/auto-learn.js +319 -0
- package/lib/agent-cli/lib/auto_preview.py +148 -0
- package/lib/agent-cli/lib/backup.js +138 -0
- package/lib/agent-cli/lib/backup.test.js +78 -0
- package/lib/agent-cli/lib/checklist.py +222 -0
- package/lib/agent-cli/lib/cognitive-lesson.js +476 -0
- package/lib/agent-cli/lib/completion.js +149 -0
- package/lib/agent-cli/lib/config.js +35 -0
- package/lib/agent-cli/lib/eslint-fix.js +238 -0
- package/lib/agent-cli/lib/evolution-signal.js +215 -0
- package/lib/agent-cli/lib/export.js +86 -0
- package/lib/agent-cli/lib/export.test.js +65 -0
- package/lib/agent-cli/lib/fix.js +337 -0
- package/lib/agent-cli/lib/fix.test.js +80 -0
- package/lib/agent-cli/lib/gemini-export.js +83 -0
- package/lib/agent-cli/lib/generate-registry.js +42 -0
- package/lib/agent-cli/lib/hooks/install-hooks.js +152 -0
- package/lib/agent-cli/lib/hooks/lint-learn.js +172 -0
- package/lib/agent-cli/lib/ignore.js +116 -0
- package/lib/agent-cli/lib/ignore.test.js +58 -0
- package/lib/agent-cli/lib/init.js +124 -0
- package/lib/agent-cli/lib/learn.js +255 -0
- package/lib/agent-cli/lib/learn.test.js +70 -0
- package/lib/agent-cli/lib/migrate-to-v4.js +322 -0
- package/lib/agent-cli/lib/proposals.js +199 -0
- package/lib/agent-cli/lib/proposals.test.js +56 -0
- package/lib/agent-cli/lib/recall.js +820 -0
- package/lib/agent-cli/lib/recall.test.js +107 -0
- package/lib/agent-cli/lib/selfevolution-bridge.js +167 -0
- package/lib/agent-cli/lib/session_manager.py +120 -0
- package/lib/agent-cli/lib/settings.js +203 -0
- package/lib/agent-cli/lib/skill-learn.js +296 -0
- package/lib/agent-cli/lib/stats.js +132 -0
- package/lib/agent-cli/lib/stats.test.js +94 -0
- package/lib/agent-cli/lib/types.js +33 -0
- package/lib/agent-cli/lib/ui/audit-ui.js +146 -0
- package/lib/agent-cli/lib/ui/backup-ui.js +107 -0
- package/lib/agent-cli/lib/ui/clack-helpers.js +317 -0
- package/lib/agent-cli/lib/ui/common.js +83 -0
- package/lib/agent-cli/lib/ui/completion-ui.js +126 -0
- package/lib/agent-cli/lib/ui/custom-select.js +69 -0
- package/lib/agent-cli/lib/ui/dashboard-ui.js +123 -0
- package/lib/agent-cli/lib/ui/evolution-signals-ui.js +107 -0
- package/lib/agent-cli/lib/ui/export-ui.js +94 -0
- package/lib/agent-cli/lib/ui/fix-all-ui.js +191 -0
- package/lib/agent-cli/lib/ui/help-ui.js +49 -0
- package/lib/agent-cli/lib/ui/index.js +169 -0
- package/lib/agent-cli/lib/ui/init-ui.js +56 -0
- package/lib/agent-cli/lib/ui/knowledge-ui.js +55 -0
- package/lib/agent-cli/lib/ui/learn-ui.js +706 -0
- package/lib/agent-cli/lib/ui/lessons-ui.js +148 -0
- package/lib/agent-cli/lib/ui/pretty.js +145 -0
- package/lib/agent-cli/lib/ui/proposals-ui.js +99 -0
- package/lib/agent-cli/lib/ui/recall-ui.js +342 -0
- package/lib/agent-cli/lib/ui/routing-demo.js +79 -0
- package/lib/agent-cli/lib/ui/routing-ui.js +325 -0
- package/lib/agent-cli/lib/ui/settings-ui.js +381 -0
- package/lib/agent-cli/lib/ui/stats-ui.js +123 -0
- package/lib/agent-cli/lib/ui/watch-ui.js +236 -0
- package/lib/agent-cli/lib/verify_all.py +327 -0
- package/lib/agent-cli/lib/watcher.js +181 -0
- package/lib/agent-cli/lib/watcher.test.js +85 -0
- package/lib/agent-cli/package.json +51 -0
- package/lib/agentskillskit-cli/README.md +21 -0
- package/lib/agentskillskit-cli/ag-smart.js +158 -0
- package/lib/agentskillskit-cli/package.json +51 -0
- package/package.json +11 -6
|
@@ -0,0 +1,325 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Intelligent Routing UI for Agent Skill Kit
|
|
3
|
+
* FAANG-level professional agent display with Clack UI
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import * as p from "@clack/prompts";
|
|
7
|
+
import pc from "picocolors";
|
|
8
|
+
|
|
9
|
+
// ============================================================================
|
|
10
|
+
// CLACK SYMBOLS
|
|
11
|
+
// ============================================================================
|
|
12
|
+
|
|
13
|
+
const S = {
|
|
14
|
+
BAR: "│",
|
|
15
|
+
BAR_END: "└",
|
|
16
|
+
CORNER_TOP_RIGHT: "╮",
|
|
17
|
+
CORNER_BOTTOM_RIGHT: "╯",
|
|
18
|
+
STEP_ACTIVE: "◆",
|
|
19
|
+
STEP_SUBMIT: "◇",
|
|
20
|
+
RADIO_ACTIVE: "●",
|
|
21
|
+
RADIO_INACTIVE: "○",
|
|
22
|
+
CONNECT_LEFT: "├",
|
|
23
|
+
H_LINE: "─"
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
// ============================================================================
|
|
27
|
+
// AGENT BRANDING
|
|
28
|
+
// ============================================================================
|
|
29
|
+
|
|
30
|
+
const AGENT_EMOJI = "🤖";
|
|
31
|
+
|
|
32
|
+
const COMPLEXITY_CONFIG = {
|
|
33
|
+
"SIMPLE": {
|
|
34
|
+
color: pc.green,
|
|
35
|
+
icon: S.RADIO_ACTIVE,
|
|
36
|
+
label: "Focused",
|
|
37
|
+
description: "Single specialist engaged"
|
|
38
|
+
},
|
|
39
|
+
"MODERATE": {
|
|
40
|
+
color: pc.yellow,
|
|
41
|
+
icon: `${S.RADIO_ACTIVE}${S.RADIO_ACTIVE}`,
|
|
42
|
+
label: "Collaborative",
|
|
43
|
+
description: "Multi-specialist coordination"
|
|
44
|
+
},
|
|
45
|
+
"COMPLEX": {
|
|
46
|
+
color: pc.red,
|
|
47
|
+
icon: `${S.RADIO_ACTIVE}${S.RADIO_ACTIVE}${S.RADIO_ACTIVE}`,
|
|
48
|
+
label: "Full Stack",
|
|
49
|
+
description: "Complete team mobilized"
|
|
50
|
+
}
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
// FAANG-level professional messages
|
|
54
|
+
const ROUTING_MESSAGES = {
|
|
55
|
+
simple: [
|
|
56
|
+
"Specialist locked in",
|
|
57
|
+
"Expert matched to your task",
|
|
58
|
+
"Best-fit agent selected",
|
|
59
|
+
"Precision routing complete"
|
|
60
|
+
],
|
|
61
|
+
moderate: [
|
|
62
|
+
"Cross-functional team assembled",
|
|
63
|
+
"Specialists synchronized",
|
|
64
|
+
"Multi-domain expertise activated",
|
|
65
|
+
"Collaborative intelligence engaged"
|
|
66
|
+
],
|
|
67
|
+
complex: [
|
|
68
|
+
"Full engineering squad deployed",
|
|
69
|
+
"Maximum capability unlocked",
|
|
70
|
+
"Enterprise-grade orchestration",
|
|
71
|
+
"Complete specialist coverage"
|
|
72
|
+
]
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
// ============================================================================
|
|
76
|
+
// CORE FUNCTIONS
|
|
77
|
+
// ============================================================================
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Get icon for an agent
|
|
81
|
+
* @param {string} agentName
|
|
82
|
+
* @param {boolean} isMain - Is this the main/first agent?
|
|
83
|
+
* @returns {string}
|
|
84
|
+
*/
|
|
85
|
+
function getAgentIcon(agentName, isMain = false) {
|
|
86
|
+
if (isMain || agentName === "orchestrator") {
|
|
87
|
+
return S.STEP_ACTIVE;
|
|
88
|
+
}
|
|
89
|
+
return S.STEP_SUBMIT;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Format agent names with professional styling
|
|
94
|
+
* @param {string[]} agents - List of agent names
|
|
95
|
+
* @returns {string}
|
|
96
|
+
*/
|
|
97
|
+
function formatAgents(agents) {
|
|
98
|
+
if (!agents || agents.length === 0) return pc.dim("analyzing...");
|
|
99
|
+
|
|
100
|
+
return agents
|
|
101
|
+
.map((agent, index) => {
|
|
102
|
+
const icon = getAgentIcon(agent, index === 0);
|
|
103
|
+
const name = `@${agent}`;
|
|
104
|
+
const colorFn = index === 0 ? pc.cyan : pc.blue;
|
|
105
|
+
return `${colorFn(icon)} ${pc.bold(colorFn(name))}`;
|
|
106
|
+
})
|
|
107
|
+
.join(pc.dim(" → "));
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Format domains with professional styling
|
|
112
|
+
* @param {string[]} domains
|
|
113
|
+
* @returns {string}
|
|
114
|
+
*/
|
|
115
|
+
function formatDomains(domains) {
|
|
116
|
+
if (!domains || domains.length === 0) return pc.dim("general");
|
|
117
|
+
return domains.map(d => pc.magenta(d)).join(pc.dim(" · "));
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Get random message from array
|
|
122
|
+
* @param {string[]} messages
|
|
123
|
+
* @returns {string}
|
|
124
|
+
*/
|
|
125
|
+
function getRandomMessage(messages) {
|
|
126
|
+
return messages[Math.floor(Math.random() * messages.length)];
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Show routing result using Clack native styling
|
|
131
|
+
* @param {object} routing - Routing result object
|
|
132
|
+
*/
|
|
133
|
+
export function showRoutingResult(routing) {
|
|
134
|
+
const { selectedAgents = [], domains = [], complexity = "SIMPLE", reasoning } = routing;
|
|
135
|
+
|
|
136
|
+
const config = COMPLEXITY_CONFIG[complexity] || COMPLEXITY_CONFIG.SIMPLE;
|
|
137
|
+
|
|
138
|
+
// Build content lines
|
|
139
|
+
const lines = [];
|
|
140
|
+
lines.push(`${pc.dim("Agents")} ${formatAgents(selectedAgents)}`);
|
|
141
|
+
lines.push(`${pc.dim("Domains")} ${formatDomains(domains)}`);
|
|
142
|
+
lines.push(`${pc.dim("Mode")} ${config.color(config.icon)} ${config.color(config.label)}`);
|
|
143
|
+
|
|
144
|
+
if (reasoning) {
|
|
145
|
+
lines.push("");
|
|
146
|
+
lines.push(`${pc.dim("→")} ${pc.italic(reasoning)}`);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// Use Clack's native note for consistent styling
|
|
150
|
+
p.note(lines.join("\n"), `${AGENT_EMOJI} Agent Routing`);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* Show compact inline routing info
|
|
155
|
+
* @param {string[]} selectedAgents
|
|
156
|
+
*/
|
|
157
|
+
export function showRoutingInline(selectedAgents) {
|
|
158
|
+
const formatted = selectedAgents
|
|
159
|
+
.map((agent, i) => `${getAgentIcon(agent, i === 0)} ${pc.cyan(`@${agent}`)}`)
|
|
160
|
+
.join(pc.dim(" → "));
|
|
161
|
+
|
|
162
|
+
console.log("");
|
|
163
|
+
console.log(`${pc.gray(S.BAR)}`);
|
|
164
|
+
console.log(`${pc.cyan(S.STEP_ACTIVE)} ${AGENT_EMOJI} ${pc.bold("Engaging")} ${formatted}`);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* Show routing with Clack log message style
|
|
169
|
+
* @param {object} routing
|
|
170
|
+
*/
|
|
171
|
+
export function showRoutingLog(routing) {
|
|
172
|
+
const { selectedAgents = [], complexity = "SIMPLE" } = routing;
|
|
173
|
+
const config = COMPLEXITY_CONFIG[complexity] || COMPLEXITY_CONFIG.SIMPLE;
|
|
174
|
+
|
|
175
|
+
const agentList = selectedAgents
|
|
176
|
+
.map((a, i) => `${getAgentIcon(a, i === 0)} @${a}`)
|
|
177
|
+
.join(" → ");
|
|
178
|
+
|
|
179
|
+
p.log.info(`${AGENT_EMOJI} ${pc.bold("Routing:")} ${pc.cyan(agentList)} ${config.color(`[${config.label}]`)}`);
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
/**
|
|
183
|
+
* Show routing step (for multi-step flows)
|
|
184
|
+
* @param {string} agent - Agent name
|
|
185
|
+
* @param {string} status - active | complete | pending
|
|
186
|
+
*/
|
|
187
|
+
export function showRoutingStep(agent, status = "active") {
|
|
188
|
+
const icons = {
|
|
189
|
+
active: pc.cyan(S.STEP_ACTIVE),
|
|
190
|
+
complete: pc.green(S.STEP_SUBMIT),
|
|
191
|
+
pending: pc.dim(S.RADIO_INACTIVE)
|
|
192
|
+
};
|
|
193
|
+
|
|
194
|
+
const labels = {
|
|
195
|
+
active: pc.cyan("working"),
|
|
196
|
+
complete: pc.green("done"),
|
|
197
|
+
pending: pc.dim("queued")
|
|
198
|
+
};
|
|
199
|
+
|
|
200
|
+
const icon = icons[status] || icons.pending;
|
|
201
|
+
const label = labels[status] || labels.pending;
|
|
202
|
+
const name = status === "active" ? pc.bold(pc.cyan(`@${agent}`)) : pc.dim(`@${agent}`);
|
|
203
|
+
|
|
204
|
+
console.log(`${pc.gray(S.BAR)} ${icon} ${name} ${pc.dim("·")} ${label}`);
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
/**
|
|
208
|
+
* Analyze request and show routing decision
|
|
209
|
+
* @param {string} userRequest - The user's request text
|
|
210
|
+
* @returns {object} - The routing decision
|
|
211
|
+
*/
|
|
212
|
+
export function analyzeAndShowRouting(userRequest) {
|
|
213
|
+
// Domain detection patterns
|
|
214
|
+
const patterns = {
|
|
215
|
+
"security": /auth|login|password|jwt|token|vulnerability|security|encrypt/i,
|
|
216
|
+
"frontend": /component|react|vue|css|html|tailwind|button|layout|style|ui/i,
|
|
217
|
+
"backend": /api|endpoint|server|express|route|post|get|database/i,
|
|
218
|
+
"mobile": /react native|flutter|ios|android|mobile|expo|screen/i,
|
|
219
|
+
"database": /prisma|sql|mongodb|schema|migration|table|query/i,
|
|
220
|
+
"testing": /test|jest|vitest|playwright|coverage|e2e/i,
|
|
221
|
+
"debug": /error|bug|crash|not working|broken|fix/i,
|
|
222
|
+
"performance": /slow|optimize|performance|speed|cache/i,
|
|
223
|
+
"analysis": /analyze|review|refactor|clean|improve|upgrade/i
|
|
224
|
+
};
|
|
225
|
+
|
|
226
|
+
// Detect domains
|
|
227
|
+
const domains = [];
|
|
228
|
+
for (const [domain, pattern] of Object.entries(patterns)) {
|
|
229
|
+
if (pattern.test(userRequest)) {
|
|
230
|
+
domains.push(domain);
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
// Determine complexity
|
|
235
|
+
let complexity = "SIMPLE";
|
|
236
|
+
let messagePool = ROUTING_MESSAGES.simple;
|
|
237
|
+
|
|
238
|
+
if (domains.length >= 3) {
|
|
239
|
+
complexity = "COMPLEX";
|
|
240
|
+
messagePool = ROUTING_MESSAGES.complex;
|
|
241
|
+
} else if (domains.length === 2) {
|
|
242
|
+
complexity = "MODERATE";
|
|
243
|
+
messagePool = ROUTING_MESSAGES.moderate;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
// Select agents based on domains (FAANG-style short names)
|
|
247
|
+
const agentMap = {
|
|
248
|
+
"security": "security",
|
|
249
|
+
"frontend": "frontend",
|
|
250
|
+
"backend": "backend",
|
|
251
|
+
"mobile": "mobile",
|
|
252
|
+
"database": "database",
|
|
253
|
+
"testing": "testing",
|
|
254
|
+
"debug": "debug",
|
|
255
|
+
"performance": "perf",
|
|
256
|
+
"analysis": "analyst"
|
|
257
|
+
};
|
|
258
|
+
|
|
259
|
+
let selectedAgents = domains.map(d => agentMap[d] || "lead");
|
|
260
|
+
|
|
261
|
+
// If complex or no agents, add lead
|
|
262
|
+
if (complexity === "COMPLEX" || selectedAgents.length === 0) {
|
|
263
|
+
selectedAgents = ["lead", ...selectedAgents];
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
// Remove duplicates
|
|
267
|
+
selectedAgents = [...new Set(selectedAgents)];
|
|
268
|
+
|
|
269
|
+
const routing = {
|
|
270
|
+
selectedAgents,
|
|
271
|
+
domains,
|
|
272
|
+
complexity,
|
|
273
|
+
reasoning: getRandomMessage(messagePool)
|
|
274
|
+
};
|
|
275
|
+
|
|
276
|
+
// Show the routing result
|
|
277
|
+
showRoutingResult(routing);
|
|
278
|
+
|
|
279
|
+
return routing;
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
/**
|
|
283
|
+
* Show agent intro message
|
|
284
|
+
* @param {string} message - Custom message
|
|
285
|
+
*/
|
|
286
|
+
export function showAgentIntro(message = "Ready to assist") {
|
|
287
|
+
p.intro(`${AGENT_EMOJI} ${pc.bold(message)}`);
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
/**
|
|
291
|
+
* Show agent outro message
|
|
292
|
+
* @param {string} message - Custom message
|
|
293
|
+
*/
|
|
294
|
+
export function showAgentOutro(message = "Task complete") {
|
|
295
|
+
p.outro(`${AGENT_EMOJI} ${pc.green(message)}`);
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
/**
|
|
299
|
+
* Show agent thinking indicator
|
|
300
|
+
* @param {string} action - What the agent is doing
|
|
301
|
+
*/
|
|
302
|
+
export function showAgentThinking(action = "Analyzing") {
|
|
303
|
+
console.log(`${pc.gray(S.BAR)}`);
|
|
304
|
+
console.log(`${pc.cyan(S.STEP_ACTIVE)} ${AGENT_EMOJI} ${pc.dim(action)}...`);
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
// ============================================================================
|
|
308
|
+
// EXPORTS
|
|
309
|
+
// ============================================================================
|
|
310
|
+
|
|
311
|
+
export default {
|
|
312
|
+
showRoutingResult,
|
|
313
|
+
showRoutingInline,
|
|
314
|
+
showRoutingLog,
|
|
315
|
+
showRoutingStep,
|
|
316
|
+
analyzeAndShowRouting,
|
|
317
|
+
showAgentIntro,
|
|
318
|
+
showAgentOutro,
|
|
319
|
+
showAgentThinking,
|
|
320
|
+
getAgentIcon,
|
|
321
|
+
AGENT_EMOJI,
|
|
322
|
+
S,
|
|
323
|
+
COMPLEXITY_CONFIG,
|
|
324
|
+
ROUTING_MESSAGES
|
|
325
|
+
};
|
|
@@ -0,0 +1,381 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Settings UI - Interactive settings configuration
|
|
3
|
+
*/
|
|
4
|
+
import * as p from "@clack/prompts";
|
|
5
|
+
import pc from "picocolors";
|
|
6
|
+
import {
|
|
7
|
+
loadSettings,
|
|
8
|
+
saveSettings,
|
|
9
|
+
toggleAutoLearning,
|
|
10
|
+
toggleAutoUpdating,
|
|
11
|
+
setApiKey,
|
|
12
|
+
getApiKey,
|
|
13
|
+
removeApiKey
|
|
14
|
+
} from "../settings.js";
|
|
15
|
+
import fs from 'fs';
|
|
16
|
+
import path from 'path';
|
|
17
|
+
import os from 'os';
|
|
18
|
+
|
|
19
|
+
// ============================================================================
|
|
20
|
+
// HELPERS
|
|
21
|
+
// ============================================================================
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Mask API key for display (show first 4 and last 4 chars)
|
|
25
|
+
*/
|
|
26
|
+
function maskApiKey(key) {
|
|
27
|
+
if (!key) return pc.dim("[Not Set]");
|
|
28
|
+
if (key.length < 12) return "***...***";
|
|
29
|
+
return `${key.substring(0, 4)}...${key.substring(key.length - 4)}`;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Get Antigravity OAuth credentials (if available)
|
|
34
|
+
* @returns {{accessToken: string, expiryDate: number} | null}
|
|
35
|
+
*/
|
|
36
|
+
function getAntigravityOAuthToken() {
|
|
37
|
+
try {
|
|
38
|
+
const credPath = path.join(os.homedir(), '.gemini', 'oauth_creds.json');
|
|
39
|
+
|
|
40
|
+
if (!fs.existsSync(credPath)) return null;
|
|
41
|
+
|
|
42
|
+
const creds = JSON.parse(fs.readFileSync(credPath, 'utf8'));
|
|
43
|
+
|
|
44
|
+
// Check if token is still valid
|
|
45
|
+
if (creds.access_token && creds.expiry_date) {
|
|
46
|
+
const now = Date.now();
|
|
47
|
+
if (now < creds.expiry_date) {
|
|
48
|
+
return {
|
|
49
|
+
accessToken: creds.access_token,
|
|
50
|
+
expiryDate: creds.expiry_date
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
return null;
|
|
56
|
+
} catch (e) {
|
|
57
|
+
return null;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Detect which API key Agent is actively using (from environment)
|
|
63
|
+
* Checks multiple sources in priority order:
|
|
64
|
+
* 1. Current process environment
|
|
65
|
+
* 2. Exported Settings API key
|
|
66
|
+
* 3. SelfEvolution .env file
|
|
67
|
+
* @returns {'gemini' | 'claude' | null}
|
|
68
|
+
*/
|
|
69
|
+
function getActiveApiKey() {
|
|
70
|
+
// Priority 1: Current process environment (Agent session)
|
|
71
|
+
if (process.env.GEMINI_API_KEY) return 'gemini';
|
|
72
|
+
if (process.env.CLAUDE_API_KEY || process.env.ANTHROPIC_API_KEY) return 'claude';
|
|
73
|
+
|
|
74
|
+
// Priority 2: Check if we have exported keys from Settings
|
|
75
|
+
const geminiKey = getApiKey('gemini');
|
|
76
|
+
const claudeKey = getApiKey('claude');
|
|
77
|
+
|
|
78
|
+
// If Settings has a key configured, consider it "active"
|
|
79
|
+
// (because we auto-export to SelfEvolution)
|
|
80
|
+
if (geminiKey) return 'gemini';
|
|
81
|
+
if (claudeKey) return 'claude';
|
|
82
|
+
|
|
83
|
+
// Priority 3: Check Antigravity OAuth credentials
|
|
84
|
+
const oauthCreds = getAntigravityOAuthToken();
|
|
85
|
+
if (oauthCreds) return 'gemini'; // Antigravity uses Google OAuth
|
|
86
|
+
|
|
87
|
+
return null;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Format API key status (ON if active, OFF if not)
|
|
92
|
+
*/
|
|
93
|
+
function formatApiKeyStatus(provider, storedKey) {
|
|
94
|
+
const activeKey = getActiveApiKey();
|
|
95
|
+
const isActive = activeKey === provider;
|
|
96
|
+
|
|
97
|
+
if (!storedKey) {
|
|
98
|
+
return pc.dim("[Not Set]");
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
if (isActive) {
|
|
102
|
+
return `${pc.green("[ON]")} ${maskApiKey(storedKey)}`;
|
|
103
|
+
} else {
|
|
104
|
+
return `${pc.dim("[OFF]")} ${maskApiKey(storedKey)}`;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// ============================================================================
|
|
109
|
+
// SETTINGS MENU
|
|
110
|
+
// ============================================================================
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Interactive settings menu with Clack icons
|
|
114
|
+
*/
|
|
115
|
+
export async function runSettingsUI() {
|
|
116
|
+
// No intro - go straight to settings menu
|
|
117
|
+
|
|
118
|
+
// Display active API key info ONCE at the start
|
|
119
|
+
const geminiKey = getApiKey('gemini');
|
|
120
|
+
const claudeKey = getApiKey('claude');
|
|
121
|
+
|
|
122
|
+
let bannerMsg;
|
|
123
|
+
|
|
124
|
+
// Priority 1: Check if running in Antigravity (check for oauth_creds.json)
|
|
125
|
+
try {
|
|
126
|
+
const credPath = path.join(os.homedir(), '.gemini', 'oauth_creds.json');
|
|
127
|
+
if (fs.existsSync(credPath)) {
|
|
128
|
+
// Running in Antigravity - always show this message
|
|
129
|
+
bannerMsg = pc.green(
|
|
130
|
+
`✓ Agent Coding is using system credentials\n` +
|
|
131
|
+
` Source: Antigravity Session (in-memory)\n` +
|
|
132
|
+
` → Configure permanent API key below for SelfEvolution features`
|
|
133
|
+
);
|
|
134
|
+
} else {
|
|
135
|
+
// Not in Antigravity, check for configured keys
|
|
136
|
+
if (geminiKey) {
|
|
137
|
+
bannerMsg = pc.green(`✓ Using Gemini API Key (Configured)\n → SelfEvolution features enabled`);
|
|
138
|
+
} else if (claudeKey) {
|
|
139
|
+
bannerMsg = pc.green(`✓ Using Claude API Key (Configured)\n → SelfEvolution features enabled`);
|
|
140
|
+
} else {
|
|
141
|
+
bannerMsg = pc.yellow(`⚠ No API Key Configured\n → Set up API key below to enable SelfEvolution features`);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
} catch (e) {
|
|
145
|
+
// Fallback: check configured keys
|
|
146
|
+
if (geminiKey) {
|
|
147
|
+
bannerMsg = pc.green(`✓ Using Gemini API Key (Configured)\n → SelfEvolution features enabled`);
|
|
148
|
+
} else if (claudeKey) {
|
|
149
|
+
bannerMsg = pc.green(`✓ Using Claude API Key (Configured)\n → SelfEvolution features enabled`);
|
|
150
|
+
} else {
|
|
151
|
+
bannerMsg = pc.yellow(`⚠ No API Key Configured\n → Set up API key below to enable SelfEvolution features`);
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
p.note(bannerMsg, "🔑 Active API Key");
|
|
156
|
+
|
|
157
|
+
while (true) {
|
|
158
|
+
const settings = loadSettings();
|
|
159
|
+
|
|
160
|
+
const action = await p.select({
|
|
161
|
+
message: "⚙️ Settings",
|
|
162
|
+
options: [
|
|
163
|
+
// AI BEHAVIOR GROUP
|
|
164
|
+
{
|
|
165
|
+
value: "autoLearn",
|
|
166
|
+
label: `🤖 Auto-Learning: ${settings.autoLearning ? pc.green("[ON]") : pc.dim("[OFF]")}`,
|
|
167
|
+
hint: "Learn from mistakes"
|
|
168
|
+
},
|
|
169
|
+
{
|
|
170
|
+
value: "autoUpdate",
|
|
171
|
+
label: `🔄 Auto-Updating: ${settings.autoUpdating ? pc.green("[ON]") : pc.dim("[OFF]")}`,
|
|
172
|
+
hint: "Update threshold hits"
|
|
173
|
+
},
|
|
174
|
+
{
|
|
175
|
+
value: "threshold",
|
|
176
|
+
label: `📈 Update Threshold: ${pc.cyan(settings.updateThreshold)}`,
|
|
177
|
+
hint: "Hits before update"
|
|
178
|
+
},
|
|
179
|
+
|
|
180
|
+
// API CONFIGURATION GROUP
|
|
181
|
+
{
|
|
182
|
+
value: "geminiKey",
|
|
183
|
+
label: `🔑 Gemini API: ${formatApiKeyStatus('gemini', getApiKey('gemini'))}`,
|
|
184
|
+
hint: "For agent coding"
|
|
185
|
+
},
|
|
186
|
+
{
|
|
187
|
+
value: "claudeKey",
|
|
188
|
+
label: `🔑 Claude API: ${formatApiKeyStatus('claude', getApiKey('claude'))}`,
|
|
189
|
+
hint: "Alternative AI provider"
|
|
190
|
+
},
|
|
191
|
+
{
|
|
192
|
+
value: "testOptimization",
|
|
193
|
+
label: "🧪 Test AI Optimization",
|
|
194
|
+
hint: "Verify SelfEvolution integration"
|
|
195
|
+
},
|
|
196
|
+
{ value: "back", label: "← Back", hint: "Return to main menu" }
|
|
197
|
+
]
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
if (p.isCancel(action) || action === "back") {
|
|
201
|
+
return;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
switch (action) {
|
|
205
|
+
case "autoLearn": {
|
|
206
|
+
const newValue = toggleAutoLearning();
|
|
207
|
+
p.note(
|
|
208
|
+
`Auto-Learning is now ${newValue ? pc.green("ON") : pc.red("OFF")}`,
|
|
209
|
+
"Setting Updated"
|
|
210
|
+
);
|
|
211
|
+
break; // Continue loop to show menu again
|
|
212
|
+
}
|
|
213
|
+
case "autoUpdate": {
|
|
214
|
+
const newValue = toggleAutoUpdating();
|
|
215
|
+
if (newValue) {
|
|
216
|
+
p.note(
|
|
217
|
+
`Auto-Updating is now ${pc.green("ON")}\n\n` +
|
|
218
|
+
`When patterns become valuable, Agent will:\n` +
|
|
219
|
+
`• Analyze learned lessons\n` +
|
|
220
|
+
`• Generate update proposals\n` +
|
|
221
|
+
`• Notify you for approval`,
|
|
222
|
+
"Setting Updated"
|
|
223
|
+
);
|
|
224
|
+
} else {
|
|
225
|
+
p.note(
|
|
226
|
+
`Auto-Updating is now ${pc.red("OFF")}`,
|
|
227
|
+
"Setting Updated"
|
|
228
|
+
);
|
|
229
|
+
}
|
|
230
|
+
break; // Continue loop
|
|
231
|
+
}
|
|
232
|
+
case "threshold": {
|
|
233
|
+
const newThreshold = await p.text({
|
|
234
|
+
message: "Set new threshold (1-20):",
|
|
235
|
+
placeholder: "5",
|
|
236
|
+
initialValue: String(settings.updateThreshold),
|
|
237
|
+
validate: (value) => {
|
|
238
|
+
const num = parseInt(value);
|
|
239
|
+
if (isNaN(num) || num < 1 || num > 20) {
|
|
240
|
+
return "Please enter a number between 1 and 20";
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
});
|
|
244
|
+
|
|
245
|
+
if (!p.isCancel(newThreshold)) {
|
|
246
|
+
settings.updateThreshold = parseInt(newThreshold);
|
|
247
|
+
saveSettings(settings);
|
|
248
|
+
p.note(
|
|
249
|
+
`Update threshold set to ${settings.updateThreshold}`,
|
|
250
|
+
"Setting Updated"
|
|
251
|
+
);
|
|
252
|
+
}
|
|
253
|
+
break; // Continue loop
|
|
254
|
+
}
|
|
255
|
+
case "geminiKey":
|
|
256
|
+
case "claudeKey": {
|
|
257
|
+
const provider = action === "geminiKey" ? "gemini" : "claude";
|
|
258
|
+
const providerName = provider.charAt(0).toUpperCase() + provider.slice(1);
|
|
259
|
+
const currentKey = getApiKey(provider);
|
|
260
|
+
|
|
261
|
+
// Ask user action
|
|
262
|
+
const keyAction = await p.select({
|
|
263
|
+
message: `${providerName} API Key:`,
|
|
264
|
+
options: [
|
|
265
|
+
{ value: "set", label: "Set/Update Key", hint: "Configure API key" },
|
|
266
|
+
...(currentKey ? [{ value: "remove", label: "Remove Key", hint: "Clear stored key" }] : []),
|
|
267
|
+
{ value: "cancel", label: "Cancel", hint: "Go back" }
|
|
268
|
+
]
|
|
269
|
+
});
|
|
270
|
+
|
|
271
|
+
if (p.isCancel(keyAction) || keyAction === "cancel") {
|
|
272
|
+
break; // Return to settings menu
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
if (keyAction === "set") {
|
|
276
|
+
const apiKey = await p.password({
|
|
277
|
+
message: `Enter ${providerName} API Key:`,
|
|
278
|
+
mask: "*",
|
|
279
|
+
validate: (value) => {
|
|
280
|
+
if (!value || value.length < 10) {
|
|
281
|
+
return "API key must be at least 10 characters";
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
});
|
|
285
|
+
|
|
286
|
+
if (!p.isCancel(apiKey)) {
|
|
287
|
+
const success = setApiKey(provider, apiKey);
|
|
288
|
+
if (success) {
|
|
289
|
+
p.note(
|
|
290
|
+
`${providerName} API key ${currentKey ? 'updated' : 'set'} successfully!\n\n` +
|
|
291
|
+
`Key: ${maskApiKey(apiKey)}\n` +
|
|
292
|
+
`Stored in: .agent/knowledge/settings.yaml`,
|
|
293
|
+
pc.green("✓ API Key Saved")
|
|
294
|
+
);
|
|
295
|
+
} else {
|
|
296
|
+
p.note(
|
|
297
|
+
`Failed to save ${providerName} API key`,
|
|
298
|
+
pc.red("✗ Error")
|
|
299
|
+
);
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
} else if (keyAction === "remove") {
|
|
303
|
+
const confirm = await p.confirm({
|
|
304
|
+
message: `Remove ${providerName} API key?`,
|
|
305
|
+
});
|
|
306
|
+
|
|
307
|
+
if (confirm && !p.isCancel(confirm)) {
|
|
308
|
+
removeApiKey(provider);
|
|
309
|
+
p.note(
|
|
310
|
+
`${providerName} API key removed`,
|
|
311
|
+
"Key Removed"
|
|
312
|
+
);
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
break;
|
|
316
|
+
}
|
|
317
|
+
case "testOptimization": {
|
|
318
|
+
const { exportApiKeysToSelfEvolution, verifySelfEvolutionAccess } =
|
|
319
|
+
await import('../selfevolution-bridge.js');
|
|
320
|
+
|
|
321
|
+
// Step 1: Export keys
|
|
322
|
+
const spinner = p.spinner();
|
|
323
|
+
spinner.start('Exporting API keys to SelfEvolution...');
|
|
324
|
+
|
|
325
|
+
const exportResult = exportApiKeysToSelfEvolution();
|
|
326
|
+
|
|
327
|
+
if (!exportResult.success) {
|
|
328
|
+
spinner.stop('Export failed');
|
|
329
|
+
p.note(
|
|
330
|
+
`❌ No API keys configured\n\n` +
|
|
331
|
+
`Please set Gemini or Claude API key first.\n\n` +
|
|
332
|
+
`Reason: ${exportResult.reason}`,
|
|
333
|
+
pc.red('✗ Cannot Test')
|
|
334
|
+
);
|
|
335
|
+
break;
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
spinner.message('Keys exported. Verifying Python access...');
|
|
339
|
+
|
|
340
|
+
// Step 2: Verify Python can access
|
|
341
|
+
const verifyResult = await verifySelfEvolutionAccess();
|
|
342
|
+
|
|
343
|
+
if (verifyResult.success && verifyResult.keyDetected) {
|
|
344
|
+
spinner.stop('Verification complete');
|
|
345
|
+
|
|
346
|
+
const exportedKeys = [];
|
|
347
|
+
if (exportResult.exported.gemini) exportedKeys.push('Gemini');
|
|
348
|
+
if (exportResult.exported.claude) exportedKeys.push('Claude');
|
|
349
|
+
|
|
350
|
+
p.note(
|
|
351
|
+
`✅ **API Key Integration Working!**\n\n` +
|
|
352
|
+
`Exported: ${exportedKeys.join(', ')}\n` +
|
|
353
|
+
`Location: ${exportResult.path}\n\n` +
|
|
354
|
+
`SelfEvolution can now:\n` +
|
|
355
|
+
`• Auto-learn from mistakes\n` +
|
|
356
|
+
`• AI-powered lesson optimization\n` +
|
|
357
|
+
`• Self-improve knowledge base\n\n` +
|
|
358
|
+
`${pc.dim('Python Output:')}\n${pc.dim(verifyResult.output)}`,
|
|
359
|
+
pc.green('✓ Optimization Ready')
|
|
360
|
+
);
|
|
361
|
+
} else {
|
|
362
|
+
spinner.stop('Verification failed');
|
|
363
|
+
p.note(
|
|
364
|
+
`⚠️ **Export OK, but verification failed**\n\n` +
|
|
365
|
+
`Keys exported to: ${exportResult.path}\n` +
|
|
366
|
+
`But Python script couldn't detect them.\n\n` +
|
|
367
|
+
`Error: ${verifyResult.error || 'Unknown'}\n\n` +
|
|
368
|
+
`Possible causes:\n` +
|
|
369
|
+
`• Python not installed\n` +
|
|
370
|
+
`• SelfEvolution scripts missing\n` +
|
|
371
|
+
`• Environment variable issue`,
|
|
372
|
+
pc.yellow('⚠ Partial Success')
|
|
373
|
+
);
|
|
374
|
+
}
|
|
375
|
+
break;
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
export default runSettingsUI;
|