@ekzs/cli 0.2.0 → 0.3.3
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 +49 -13
- package/dist/commands/agent.d.ts +0 -4
- package/dist/commands/agent.d.ts.map +1 -1
- package/dist/commands/agent.js +0 -6
- package/dist/commands/ask.d.ts +3 -5
- package/dist/commands/ask.d.ts.map +1 -1
- package/dist/commands/ask.js +50 -36
- package/dist/commands/local-agent.d.ts +0 -2
- package/dist/commands/local-agent.d.ts.map +1 -1
- package/dist/commands/local-agent.js +292 -131
- package/dist/commands/providers.d.ts +4 -0
- package/dist/commands/providers.d.ts.map +1 -0
- package/dist/commands/providers.js +6 -0
- package/dist/commands/setup.d.ts +5 -0
- package/dist/commands/setup.d.ts.map +1 -0
- package/dist/commands/setup.js +4 -0
- package/dist/index.js +41 -20
- package/dist/lib/commands-i18n.d.ts +12 -1
- package/dist/lib/commands-i18n.d.ts.map +1 -1
- package/dist/lib/commands-i18n.js +78 -14
- package/dist/lib/composer-model.d.ts +1 -1
- package/dist/lib/composer-model.d.ts.map +1 -1
- package/dist/lib/composer-model.js +2 -2
- package/dist/lib/global-config.d.ts +19 -0
- package/dist/lib/global-config.d.ts.map +1 -0
- package/dist/lib/global-config.js +52 -0
- package/dist/lib/help.d.ts.map +1 -1
- package/dist/lib/help.js +16 -12
- package/dist/lib/locale.d.ts.map +1 -1
- package/dist/lib/locale.js +2 -1
- package/dist/lib/onboarding.d.ts +20 -0
- package/dist/lib/onboarding.d.ts.map +1 -0
- package/dist/lib/onboarding.js +129 -0
- package/dist/lib/providers/agent-runner.d.ts +12 -0
- package/dist/lib/providers/agent-runner.d.ts.map +1 -0
- package/dist/lib/providers/agent-runner.js +167 -0
- package/dist/lib/providers/catalog.d.ts +15 -0
- package/dist/lib/providers/catalog.d.ts.map +1 -0
- package/dist/lib/providers/catalog.js +93 -0
- package/dist/lib/providers/chat.d.ts +10 -0
- package/dist/lib/providers/chat.d.ts.map +1 -0
- package/dist/lib/providers/chat.js +121 -0
- package/dist/lib/providers/credentials.d.ts +28 -0
- package/dist/lib/providers/credentials.d.ts.map +1 -0
- package/dist/lib/providers/credentials.js +70 -0
- package/dist/lib/providers/cursor-runner.d.ts +13 -0
- package/dist/lib/providers/cursor-runner.d.ts.map +1 -0
- package/dist/lib/providers/cursor-runner.js +89 -0
- package/dist/lib/providers/cursor-sdk.d.ts +8 -0
- package/dist/lib/providers/cursor-sdk.d.ts.map +1 -0
- package/dist/lib/providers/cursor-sdk.js +25 -0
- package/dist/lib/providers/store.d.ts +21 -0
- package/dist/lib/providers/store.d.ts.map +1 -0
- package/dist/lib/providers/store.js +84 -0
- package/dist/lib/providers/tools.d.ts +80 -0
- package/dist/lib/providers/tools.d.ts.map +1 -0
- package/dist/lib/providers/tools.js +145 -0
- package/dist/lib/providers/ui.d.ts +4 -0
- package/dist/lib/providers/ui.d.ts.map +1 -0
- package/dist/lib/providers/ui.js +188 -0
- package/dist/lib/setup-messages.d.ts +4 -0
- package/dist/lib/setup-messages.d.ts.map +1 -0
- package/dist/lib/setup-messages.js +13 -0
- package/dist/lib/ui/splash.d.ts.map +1 -1
- package/dist/lib/ui/splash.js +4 -0
- package/package.json +10 -2
- package/skills/ekz-sdk-cli/SKILL.md +6 -4
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
import readline from "readline";
|
|
2
|
+
import { providersCommandLabels } from "./commands-i18n.js";
|
|
3
|
+
import { markSetupComplete, saveGlobalConfig, setGlobalOffline, clearGlobalOffline, isGlobalOffline, } from "./global-config.js";
|
|
4
|
+
import { parseLocale } from "./locale.js";
|
|
5
|
+
import { formatSetupRequiredMessage } from "./setup-messages.js";
|
|
6
|
+
import { info, ok } from "./output.js";
|
|
7
|
+
import { ProviderSetupError, resolveActiveCredentials, resolveCredentialsWithOverride, } from "./providers/credentials.js";
|
|
8
|
+
import { runProvidersInteractive } from "./providers/ui.js";
|
|
9
|
+
import { c, muted } from "./theme.js";
|
|
10
|
+
function t(locale, pt, en, zh) {
|
|
11
|
+
if (locale === "pt")
|
|
12
|
+
return pt;
|
|
13
|
+
if (locale === "zh")
|
|
14
|
+
return zh;
|
|
15
|
+
return en;
|
|
16
|
+
}
|
|
17
|
+
async function askLine(prompt) {
|
|
18
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
19
|
+
return new Promise((resolve) => {
|
|
20
|
+
rl.question(prompt, (answer) => {
|
|
21
|
+
rl.close();
|
|
22
|
+
resolve(answer.trim());
|
|
23
|
+
});
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
function hasProvider(apiKey) {
|
|
27
|
+
if (apiKey?.trim())
|
|
28
|
+
return true;
|
|
29
|
+
return Boolean(resolveActiveCredentials());
|
|
30
|
+
}
|
|
31
|
+
export function offlineModelLabel(locale) {
|
|
32
|
+
if (locale === "pt")
|
|
33
|
+
return "offline · doctor/scan";
|
|
34
|
+
if (locale === "zh")
|
|
35
|
+
return "离线 · 诊断/扫描";
|
|
36
|
+
return "offline · doctor/scan";
|
|
37
|
+
}
|
|
38
|
+
async function pickLanguage() {
|
|
39
|
+
console.log("");
|
|
40
|
+
console.log(` ${c.bold}Ekz Connect — Setup${c.reset}`);
|
|
41
|
+
console.log("");
|
|
42
|
+
console.log(` ${muted("Choose language · Escolhe idioma · 选择语言")}`);
|
|
43
|
+
console.log("");
|
|
44
|
+
console.log(` ${c.bold}1.${c.reset} Português`);
|
|
45
|
+
console.log(` ${c.bold}2.${c.reset} English`);
|
|
46
|
+
console.log(` ${c.bold}3.${c.reset} 中文`);
|
|
47
|
+
console.log("");
|
|
48
|
+
const choice = (await askLine(" [1/2/3]: ")).toLowerCase();
|
|
49
|
+
if (choice === "2" || choice === "en" || choice === "english")
|
|
50
|
+
return "en";
|
|
51
|
+
if (choice === "3" || choice === "zh" || choice === "中文")
|
|
52
|
+
return "zh";
|
|
53
|
+
return "pt";
|
|
54
|
+
}
|
|
55
|
+
async function askUserName(locale) {
|
|
56
|
+
console.log("");
|
|
57
|
+
const prompt = t(locale, "Como devo te chamar? ", "What should we call you? (first name): ", "怎么称呼您?(名字): ");
|
|
58
|
+
const name = await askLine(` ${prompt}`);
|
|
59
|
+
if (!name)
|
|
60
|
+
return t(locale, "Developer", "Developer", "开发者");
|
|
61
|
+
return name.split(/\s+/)[0] ?? name;
|
|
62
|
+
}
|
|
63
|
+
async function providerStep(locale) {
|
|
64
|
+
const p = providersCommandLabels(locale);
|
|
65
|
+
console.log("");
|
|
66
|
+
console.log(` ${muted(t(locale, "LLM (BYOK) — opcional agora", "LLM (BYOK) — optional for now", "LLM (BYOK) — 可选"))}`);
|
|
67
|
+
console.log("");
|
|
68
|
+
console.log(` ${c.bold}1.${c.reset} ${t(locale, "Configurar provedor", "Configure provider", "配置 provider")}`);
|
|
69
|
+
console.log(` ${muted(t(locale, p.cli, p.cli, p.cli))}`);
|
|
70
|
+
console.log("");
|
|
71
|
+
console.log(` ${c.bold}2.${c.reset} ${t(locale, "Continuar offline", "Continue offline", "离线模式")}`);
|
|
72
|
+
console.log(` ${muted(t(locale, "doctor · scan · sem AI", "doctor · scan · no AI", "诊断 · 扫描 · 无 AI"))}`);
|
|
73
|
+
console.log("");
|
|
74
|
+
console.log(` ${muted("Enter · skip")}`);
|
|
75
|
+
console.log("");
|
|
76
|
+
const choice = (await askLine(t(locale, " Escolha [1/2/Enter]: ", " Choice [1/2/Enter]: ", " 选择 [1/2/Enter]: "))).toLowerCase();
|
|
77
|
+
if (choice === "2" || choice === "offline" || choice === "离线") {
|
|
78
|
+
setGlobalOffline(true);
|
|
79
|
+
ok(t(locale, "Modo offline activo.", "Offline mode enabled.", "已启用离线模式。"));
|
|
80
|
+
return "offline";
|
|
81
|
+
}
|
|
82
|
+
if (choice === "1" || choice === "p" || choice === "providers" || choice === "provedores" || choice === "供应商") {
|
|
83
|
+
await runProvidersInteractive(locale);
|
|
84
|
+
if (resolveActiveCredentials()) {
|
|
85
|
+
clearGlobalOffline();
|
|
86
|
+
ok(t(locale, "Provedor configurado.", "Provider configured.", "Provider 已配置。"));
|
|
87
|
+
return "configured";
|
|
88
|
+
}
|
|
89
|
+
info(t(locale, "Provedor não configurado.", "No provider configured.", "未配置 provider。"));
|
|
90
|
+
return "skip";
|
|
91
|
+
}
|
|
92
|
+
return "skip";
|
|
93
|
+
}
|
|
94
|
+
/** Full first-run wizard: language → name → provider/offline. */
|
|
95
|
+
export async function runSetupWizard(opts) {
|
|
96
|
+
if (!process.stdin.isTTY) {
|
|
97
|
+
console.error(formatSetupRequiredMessage());
|
|
98
|
+
process.exitCode = 1;
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
let locale = parseLocale(opts?.locale) ?? (await pickLanguage());
|
|
102
|
+
saveGlobalConfig({ locale });
|
|
103
|
+
const userName = await askUserName(locale);
|
|
104
|
+
saveGlobalConfig({ userName });
|
|
105
|
+
ok(t(locale, `Olá ${userName}!`, `Hello ${userName}!`, `你好 ${userName}!`));
|
|
106
|
+
await providerStep(locale);
|
|
107
|
+
markSetupComplete();
|
|
108
|
+
console.log("");
|
|
109
|
+
ok(t(locale, "Setup concluído. Corre `ekz` para começar.", "Setup complete. Run `ekz` to start.", "设置完成。运行 `ekz` 开始。"));
|
|
110
|
+
}
|
|
111
|
+
/** Ensures the agent can start — provider, offline, or throws setup hint. */
|
|
112
|
+
export async function ensureAgentReady(options) {
|
|
113
|
+
const { locale, offlineFlag, apiKey } = options;
|
|
114
|
+
if (offlineFlag || isGlobalOffline()) {
|
|
115
|
+
return { offline: true, creds: null };
|
|
116
|
+
}
|
|
117
|
+
if (hasProvider(apiKey)) {
|
|
118
|
+
clearGlobalOffline();
|
|
119
|
+
if (apiKey?.trim()) {
|
|
120
|
+
return { offline: false, creds: resolveCredentialsWithOverride(apiKey, locale) };
|
|
121
|
+
}
|
|
122
|
+
return { offline: false, creds: resolveActiveCredentials() };
|
|
123
|
+
}
|
|
124
|
+
throw new ProviderSetupError(formatSetupRequiredMessage());
|
|
125
|
+
}
|
|
126
|
+
export function offlineAgentHint(locale) {
|
|
127
|
+
const p = providersCommandLabels(locale);
|
|
128
|
+
return t(locale, `Modo offline — sem AI. Usa \`ekz setup\`, ${p.repl}, /diagnóstico ou /analisar.`, `Offline mode — no AI. Use \`ekz setup\`, ${p.repl}, /doctor, or /scan.`, `离线模式 — 无 AI。使用 \`ekz setup\`、${p.repl}、/诊断 或 /扫描。`);
|
|
129
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { type EkzLocale } from "../locale.js";
|
|
2
|
+
import { type EkzMode } from "../mode.js";
|
|
3
|
+
import { type ResolvedCredentials } from "./credentials.js";
|
|
4
|
+
export declare function runByokAgentTurn(options: {
|
|
5
|
+
cwd: string;
|
|
6
|
+
task: string;
|
|
7
|
+
creds: ResolvedCredentials;
|
|
8
|
+
locale: EkzLocale;
|
|
9
|
+
mode: EkzMode;
|
|
10
|
+
}): Promise<boolean>;
|
|
11
|
+
export declare function usesCursorSdk(creds: ResolvedCredentials): boolean;
|
|
12
|
+
//# sourceMappingURL=agent-runner.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"agent-runner.d.ts","sourceRoot":"","sources":["../../../src/lib/providers/agent-runner.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,KAAK,SAAS,EAAE,MAAM,cAAc,CAAC;AAC9C,OAAO,EAAE,KAAK,OAAO,EAAE,MAAM,YAAY,CAAC;AAG1C,OAAO,EAAE,KAAK,mBAAmB,EAAE,MAAM,kBAAkB,CAAC;AA+L5D,wBAAsB,gBAAgB,CAAC,OAAO,EAAE;IAC9C,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,mBAAmB,CAAC;IAC3B,MAAM,EAAE,SAAS,CAAC;IAClB,IAAI,EAAE,OAAO,CAAC;CACf,GAAG,OAAO,CAAC,OAAO,CAAC,CAgCnB;AAED,wBAAgB,aAAa,CAAC,KAAK,EAAE,mBAAmB,GAAG,OAAO,CAEjE"}
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
import { resolve } from "path";
|
|
2
|
+
import { buildAgentPrompt, buildBootstrapContext } from "../context.js";
|
|
3
|
+
import { fail } from "../output.js";
|
|
4
|
+
import { toolDone, toolError, toolRunning } from "../theme.js";
|
|
5
|
+
import { AGENT_TOOL_DEFINITIONS, executeAgentTool } from "./tools.js";
|
|
6
|
+
const MAX_TOOL_ROUNDS = 24;
|
|
7
|
+
function planInstruction(mode) {
|
|
8
|
+
if (mode === "plan") {
|
|
9
|
+
return "You are in **plan mode**: output a clear numbered plan first. Only call write_file after the plan is clear.";
|
|
10
|
+
}
|
|
11
|
+
if (mode === "ask") {
|
|
12
|
+
return "You are in **ask mode**: answer only. Do not call write_file or run_command.";
|
|
13
|
+
}
|
|
14
|
+
return "You are in **agent mode**: use tools to inspect and fix the e-Kwanza integration.";
|
|
15
|
+
}
|
|
16
|
+
async function openAiAgentLoop(creds, cwd, messages, enableTools) {
|
|
17
|
+
const base = (creds.baseUrl ?? "https://api.openai.com/v1").replace(/\/$/, "");
|
|
18
|
+
const url = base.endsWith("/chat/completions") ? base : `${base}/chat/completions`;
|
|
19
|
+
for (let round = 0; round < MAX_TOOL_ROUNDS; round++) {
|
|
20
|
+
const headers = { "Content-Type": "application/json" };
|
|
21
|
+
if (creds.apiKey && creds.apiKey !== "ollama") {
|
|
22
|
+
headers.Authorization = `Bearer ${creds.apiKey}`;
|
|
23
|
+
}
|
|
24
|
+
const body = {
|
|
25
|
+
model: creds.model,
|
|
26
|
+
messages,
|
|
27
|
+
temperature: 0.2,
|
|
28
|
+
};
|
|
29
|
+
if (enableTools)
|
|
30
|
+
body.tools = AGENT_TOOL_DEFINITIONS;
|
|
31
|
+
const res = await fetch(url, { method: "POST", headers, body: JSON.stringify(body) });
|
|
32
|
+
const data = (await res.json().catch(() => ({})));
|
|
33
|
+
if (!res.ok)
|
|
34
|
+
throw new Error(data.error?.message ?? `Provider error (${res.status})`);
|
|
35
|
+
const message = data.choices?.[0]?.message;
|
|
36
|
+
if (!message)
|
|
37
|
+
throw new Error("Empty response from provider.");
|
|
38
|
+
const toolCalls = message.tool_calls ?? [];
|
|
39
|
+
if (!toolCalls.length) {
|
|
40
|
+
const text = message.content?.trim();
|
|
41
|
+
if (!text)
|
|
42
|
+
throw new Error("Empty response from provider.");
|
|
43
|
+
return text;
|
|
44
|
+
}
|
|
45
|
+
messages.push({
|
|
46
|
+
role: "assistant",
|
|
47
|
+
content: message.content ?? null,
|
|
48
|
+
tool_calls: toolCalls,
|
|
49
|
+
});
|
|
50
|
+
for (const call of toolCalls) {
|
|
51
|
+
process.stdout.write(toolRunning(call.function.name));
|
|
52
|
+
let args = {};
|
|
53
|
+
try {
|
|
54
|
+
args = JSON.parse(call.function.arguments || "{}");
|
|
55
|
+
}
|
|
56
|
+
catch {
|
|
57
|
+
args = {};
|
|
58
|
+
}
|
|
59
|
+
try {
|
|
60
|
+
const result = await executeAgentTool(cwd, call.function.name, args);
|
|
61
|
+
process.stdout.write(toolDone(call.function.name));
|
|
62
|
+
messages.push({ role: "tool", tool_call_id: call.id, content: result });
|
|
63
|
+
}
|
|
64
|
+
catch (err) {
|
|
65
|
+
process.stdout.write(toolError(call.function.name));
|
|
66
|
+
messages.push({
|
|
67
|
+
role: "tool",
|
|
68
|
+
tool_call_id: call.id,
|
|
69
|
+
content: err instanceof Error ? err.message : String(err),
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
throw new Error("Tool loop exceeded maximum rounds.");
|
|
75
|
+
}
|
|
76
|
+
async function anthropicAgentLoop(creds, cwd, system, messages, enableTools) {
|
|
77
|
+
const tools = enableTools
|
|
78
|
+
? AGENT_TOOL_DEFINITIONS.map((t) => ({
|
|
79
|
+
name: t.function.name,
|
|
80
|
+
description: t.function.description,
|
|
81
|
+
input_schema: t.function.parameters,
|
|
82
|
+
}))
|
|
83
|
+
: undefined;
|
|
84
|
+
for (let round = 0; round < MAX_TOOL_ROUNDS; round++) {
|
|
85
|
+
const res = await fetch("https://api.anthropic.com/v1/messages", {
|
|
86
|
+
method: "POST",
|
|
87
|
+
headers: {
|
|
88
|
+
"Content-Type": "application/json",
|
|
89
|
+
"x-api-key": creds.apiKey,
|
|
90
|
+
"anthropic-version": "2023-06-01",
|
|
91
|
+
},
|
|
92
|
+
body: JSON.stringify({
|
|
93
|
+
model: creds.model,
|
|
94
|
+
max_tokens: 8192,
|
|
95
|
+
system,
|
|
96
|
+
messages,
|
|
97
|
+
...(tools ? { tools } : {}),
|
|
98
|
+
}),
|
|
99
|
+
});
|
|
100
|
+
const data = (await res.json().catch(() => ({})));
|
|
101
|
+
if (!res.ok)
|
|
102
|
+
throw new Error(data.error?.message ?? `Anthropic error (${res.status})`);
|
|
103
|
+
const blocks = data.content ?? [];
|
|
104
|
+
const textParts = blocks.filter((b) => b.type === "text").map((b) => b.text ?? "");
|
|
105
|
+
const toolUses = blocks.filter((b) => b.type === "tool_use");
|
|
106
|
+
messages.push({ role: "assistant", content: blocks });
|
|
107
|
+
if (!toolUses.length || data.stop_reason === "end_turn") {
|
|
108
|
+
const text = textParts.join("").trim();
|
|
109
|
+
if (!text && !toolUses.length)
|
|
110
|
+
throw new Error("Empty response from Anthropic.");
|
|
111
|
+
if (text)
|
|
112
|
+
return text;
|
|
113
|
+
}
|
|
114
|
+
if (!toolUses.length) {
|
|
115
|
+
return textParts.join("").trim() || "(done)";
|
|
116
|
+
}
|
|
117
|
+
const toolResults = [];
|
|
118
|
+
for (const tool of toolUses) {
|
|
119
|
+
const name = tool.name ?? "unknown";
|
|
120
|
+
process.stdout.write(toolRunning(name));
|
|
121
|
+
try {
|
|
122
|
+
const result = await executeAgentTool(cwd, name, tool.input ?? {});
|
|
123
|
+
process.stdout.write(toolDone(name));
|
|
124
|
+
toolResults.push({ type: "tool_result", tool_use_id: tool.id ?? name, content: result });
|
|
125
|
+
}
|
|
126
|
+
catch (err) {
|
|
127
|
+
process.stdout.write(toolError(name));
|
|
128
|
+
toolResults.push({
|
|
129
|
+
type: "tool_result",
|
|
130
|
+
tool_use_id: tool.id ?? name,
|
|
131
|
+
content: err instanceof Error ? err.message : String(err),
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
messages.push({ role: "user", content: toolResults });
|
|
136
|
+
}
|
|
137
|
+
throw new Error("Tool loop exceeded maximum rounds.");
|
|
138
|
+
}
|
|
139
|
+
export async function runByokAgentTurn(options) {
|
|
140
|
+
const cwd = resolve(options.cwd);
|
|
141
|
+
const ctx = await buildBootstrapContext(cwd);
|
|
142
|
+
const prompt = buildAgentPrompt(options.task, ctx, options.locale);
|
|
143
|
+
const system = `${planInstruction(options.mode)}\n\n${prompt.split("## Task")[0] ?? prompt}`;
|
|
144
|
+
const userTask = `## Task\n${options.task}`;
|
|
145
|
+
const enableTools = options.mode !== "ask";
|
|
146
|
+
try {
|
|
147
|
+
if (options.creds.definition.kind === "anthropic") {
|
|
148
|
+
const text = await anthropicAgentLoop(options.creds, cwd, system, [{ role: "user", content: userTask }], enableTools);
|
|
149
|
+
console.log("\n" + text);
|
|
150
|
+
return true;
|
|
151
|
+
}
|
|
152
|
+
const messages = [
|
|
153
|
+
{ role: "system", content: system },
|
|
154
|
+
{ role: "user", content: userTask },
|
|
155
|
+
];
|
|
156
|
+
const text = await openAiAgentLoop(options.creds, cwd, messages, enableTools);
|
|
157
|
+
console.log("\n" + text);
|
|
158
|
+
return true;
|
|
159
|
+
}
|
|
160
|
+
catch (err) {
|
|
161
|
+
fail(err instanceof Error ? err.message : String(err));
|
|
162
|
+
return false;
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
export function usesCursorSdk(creds) {
|
|
166
|
+
return creds.definition.kind === "cursor";
|
|
167
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export type ProviderKind = "cursor" | "openai-compatible" | "anthropic";
|
|
2
|
+
export type ProviderId = "cursor" | "openai" | "anthropic" | "deepseek" | "kimi" | "groq" | "mistral" | "google" | "ollama";
|
|
3
|
+
export type ProviderDefinition = {
|
|
4
|
+
id: ProviderId;
|
|
5
|
+
name: string;
|
|
6
|
+
kind: ProviderKind;
|
|
7
|
+
description: string;
|
|
8
|
+
defaultModel: string;
|
|
9
|
+
defaultBaseUrl?: string;
|
|
10
|
+
keyHint: string;
|
|
11
|
+
docsUrl: string;
|
|
12
|
+
};
|
|
13
|
+
export declare const PROVIDER_CATALOG: ProviderDefinition[];
|
|
14
|
+
export declare function getProviderDefinition(id: string): ProviderDefinition | undefined;
|
|
15
|
+
//# sourceMappingURL=catalog.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"catalog.d.ts","sourceRoot":"","sources":["../../../src/lib/providers/catalog.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,YAAY,GAAG,QAAQ,GAAG,mBAAmB,GAAG,WAAW,CAAC;AAExE,MAAM,MAAM,UAAU,GAClB,QAAQ,GACR,QAAQ,GACR,WAAW,GACX,UAAU,GACV,MAAM,GACN,MAAM,GACN,SAAS,GACT,QAAQ,GACR,QAAQ,CAAC;AAEb,MAAM,MAAM,kBAAkB,GAAG;IAC/B,EAAE,EAAE,UAAU,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,YAAY,CAAC;IACnB,WAAW,EAAE,MAAM,CAAC;IACpB,YAAY,EAAE,MAAM,CAAC;IACrB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;CACjB,CAAC;AAEF,eAAO,MAAM,gBAAgB,EAAE,kBAAkB,EAyFhD,CAAC;AAEF,wBAAgB,qBAAqB,CAAC,EAAE,EAAE,MAAM,GAAG,kBAAkB,GAAG,SAAS,CAEhF"}
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
export const PROVIDER_CATALOG = [
|
|
2
|
+
{
|
|
3
|
+
id: "cursor",
|
|
4
|
+
name: "Cursor",
|
|
5
|
+
kind: "cursor",
|
|
6
|
+
description: "Composer local agent (@cursor/sdk)",
|
|
7
|
+
defaultModel: "composer-2.5-fast",
|
|
8
|
+
keyHint: "crsr_…",
|
|
9
|
+
docsUrl: "https://cursor.com/dashboard/integrations",
|
|
10
|
+
},
|
|
11
|
+
{
|
|
12
|
+
id: "openai",
|
|
13
|
+
name: "OpenAI",
|
|
14
|
+
kind: "openai-compatible",
|
|
15
|
+
description: "GPT-4o, o-series",
|
|
16
|
+
defaultModel: "gpt-4o",
|
|
17
|
+
defaultBaseUrl: "https://api.openai.com/v1",
|
|
18
|
+
keyHint: "sk-…",
|
|
19
|
+
docsUrl: "https://platform.openai.com/api-keys",
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
id: "anthropic",
|
|
23
|
+
name: "Anthropic",
|
|
24
|
+
kind: "anthropic",
|
|
25
|
+
description: "Claude Sonnet / Opus",
|
|
26
|
+
defaultModel: "claude-sonnet-4-20250514",
|
|
27
|
+
keyHint: "sk-ant-…",
|
|
28
|
+
docsUrl: "https://console.anthropic.com/settings/keys",
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
id: "deepseek",
|
|
32
|
+
name: "DeepSeek",
|
|
33
|
+
kind: "openai-compatible",
|
|
34
|
+
description: "DeepSeek Chat / Reasoner",
|
|
35
|
+
defaultModel: "deepseek-chat",
|
|
36
|
+
defaultBaseUrl: "https://api.deepseek.com",
|
|
37
|
+
keyHint: "sk-…",
|
|
38
|
+
docsUrl: "https://platform.deepseek.com/api_keys",
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
id: "kimi",
|
|
42
|
+
name: "Kimi (Moonshot)",
|
|
43
|
+
kind: "openai-compatible",
|
|
44
|
+
description: "Moonshot AI",
|
|
45
|
+
defaultModel: "moonshot-v1-8k",
|
|
46
|
+
defaultBaseUrl: "https://api.moonshot.cn/v1",
|
|
47
|
+
keyHint: "sk-…",
|
|
48
|
+
docsUrl: "https://platform.moonshot.cn/console/api-keys",
|
|
49
|
+
},
|
|
50
|
+
{
|
|
51
|
+
id: "groq",
|
|
52
|
+
name: "Groq",
|
|
53
|
+
kind: "openai-compatible",
|
|
54
|
+
description: "Fast inference",
|
|
55
|
+
defaultModel: "llama-3.3-70b-versatile",
|
|
56
|
+
defaultBaseUrl: "https://api.groq.com/openai/v1",
|
|
57
|
+
keyHint: "gsk_…",
|
|
58
|
+
docsUrl: "https://console.groq.com/keys",
|
|
59
|
+
},
|
|
60
|
+
{
|
|
61
|
+
id: "mistral",
|
|
62
|
+
name: "Mistral",
|
|
63
|
+
kind: "openai-compatible",
|
|
64
|
+
description: "Mistral Large",
|
|
65
|
+
defaultModel: "mistral-large-latest",
|
|
66
|
+
defaultBaseUrl: "https://api.mistral.ai/v1",
|
|
67
|
+
keyHint: "…",
|
|
68
|
+
docsUrl: "https://console.mistral.ai/api-keys",
|
|
69
|
+
},
|
|
70
|
+
{
|
|
71
|
+
id: "google",
|
|
72
|
+
name: "Google Gemini",
|
|
73
|
+
kind: "openai-compatible",
|
|
74
|
+
description: "Gemini via OpenAI-compatible endpoint",
|
|
75
|
+
defaultModel: "gemini-2.0-flash",
|
|
76
|
+
defaultBaseUrl: "https://generativelanguage.googleapis.com/v1beta/openai",
|
|
77
|
+
keyHint: "AI…",
|
|
78
|
+
docsUrl: "https://aistudio.google.com/apikey",
|
|
79
|
+
},
|
|
80
|
+
{
|
|
81
|
+
id: "ollama",
|
|
82
|
+
name: "Ollama",
|
|
83
|
+
kind: "openai-compatible",
|
|
84
|
+
description: "Local models",
|
|
85
|
+
defaultModel: "llama3.2",
|
|
86
|
+
defaultBaseUrl: "http://127.0.0.1:11434/v1",
|
|
87
|
+
keyHint: "ollama (optional)",
|
|
88
|
+
docsUrl: "https://ollama.com",
|
|
89
|
+
},
|
|
90
|
+
];
|
|
91
|
+
export function getProviderDefinition(id) {
|
|
92
|
+
return PROVIDER_CATALOG.find((p) => p.id === id);
|
|
93
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { type EkzLocale } from "../locale.js";
|
|
2
|
+
import { type ResolvedCredentials } from "./credentials.js";
|
|
3
|
+
export declare function executeByokAskTurn(options: {
|
|
4
|
+
cwd: string;
|
|
5
|
+
question: string;
|
|
6
|
+
locale?: EkzLocale;
|
|
7
|
+
creds?: ResolvedCredentials | null;
|
|
8
|
+
quiet?: boolean;
|
|
9
|
+
}): Promise<boolean>;
|
|
10
|
+
//# sourceMappingURL=chat.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"chat.d.ts","sourceRoot":"","sources":["../../../src/lib/providers/chat.ts"],"names":[],"mappings":"AACA,OAAO,EAAuB,KAAK,SAAS,EAAE,MAAM,cAAc,CAAC;AAGnE,OAAO,EAGL,KAAK,mBAAmB,EACzB,MAAM,kBAAkB,CAAC;AAuG1B,wBAAsB,kBAAkB,CAAC,OAAO,EAAE;IAChD,GAAG,EAAE,MAAM,CAAC;IACZ,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,CAAC,EAAE,SAAS,CAAC;IACnB,KAAK,CAAC,EAAE,mBAAmB,GAAG,IAAI,CAAC;IACnC,KAAK,CAAC,EAAE,OAAO,CAAC;CACjB,GAAG,OAAO,CAAC,OAAO,CAAC,CAgDnB"}
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import { buildBootstrapContext } from "../context.js";
|
|
2
|
+
import { languageInstruction } from "../locale.js";
|
|
3
|
+
import { agentScopeBlock } from "../scope.js";
|
|
4
|
+
import { fail, info, ok } from "../output.js";
|
|
5
|
+
import { ProviderSetupError, requireActiveCredentials, } from "./credentials.js";
|
|
6
|
+
function buildAskSystemPrompt(ctx, locale) {
|
|
7
|
+
return `You are **Ekz Connect** — an e-Kwanza v2.4 integration assistant.
|
|
8
|
+
Answer in clear, actionable steps. You are in **ask mode**: explain and guide only; do not claim you edited files.
|
|
9
|
+
|
|
10
|
+
${agentScopeBlock(locale)}
|
|
11
|
+
|
|
12
|
+
${languageInstruction(locale)}
|
|
13
|
+
|
|
14
|
+
## Project context (doctor + scan)
|
|
15
|
+
${JSON.stringify({
|
|
16
|
+
issues: ctx.doctor.issues,
|
|
17
|
+
warnings: ctx.doctor.warnings,
|
|
18
|
+
rails: ctx.doctor.rails,
|
|
19
|
+
health: ctx.doctor.health,
|
|
20
|
+
scan: ctx.scan.slice(0, 12),
|
|
21
|
+
env: ctx.env,
|
|
22
|
+
}, null, 2)}`;
|
|
23
|
+
}
|
|
24
|
+
async function chatOpenAiCompatible(creds, messages) {
|
|
25
|
+
const base = (creds.baseUrl ?? "https://api.openai.com/v1").replace(/\/$/, "");
|
|
26
|
+
const url = base.endsWith("/chat/completions") ? base : `${base}/chat/completions`;
|
|
27
|
+
const headers = {
|
|
28
|
+
"Content-Type": "application/json",
|
|
29
|
+
};
|
|
30
|
+
if (creds.apiKey && creds.apiKey !== "ollama") {
|
|
31
|
+
headers.Authorization = `Bearer ${creds.apiKey}`;
|
|
32
|
+
}
|
|
33
|
+
const res = await fetch(url, {
|
|
34
|
+
method: "POST",
|
|
35
|
+
headers,
|
|
36
|
+
body: JSON.stringify({
|
|
37
|
+
model: creds.model,
|
|
38
|
+
messages,
|
|
39
|
+
temperature: 0.2,
|
|
40
|
+
}),
|
|
41
|
+
});
|
|
42
|
+
const data = (await res.json().catch(() => ({})));
|
|
43
|
+
if (!res.ok) {
|
|
44
|
+
throw new Error(data.error?.message ?? `Provider error (${res.status})`);
|
|
45
|
+
}
|
|
46
|
+
const text = data.choices?.[0]?.message?.content?.trim();
|
|
47
|
+
if (!text)
|
|
48
|
+
throw new Error("Empty response from provider.");
|
|
49
|
+
return text;
|
|
50
|
+
}
|
|
51
|
+
async function chatAnthropic(creds, system, question) {
|
|
52
|
+
const res = await fetch("https://api.anthropic.com/v1/messages", {
|
|
53
|
+
method: "POST",
|
|
54
|
+
headers: {
|
|
55
|
+
"Content-Type": "application/json",
|
|
56
|
+
"x-api-key": creds.apiKey,
|
|
57
|
+
"anthropic-version": "2023-06-01",
|
|
58
|
+
},
|
|
59
|
+
body: JSON.stringify({
|
|
60
|
+
model: creds.model,
|
|
61
|
+
max_tokens: 4096,
|
|
62
|
+
system,
|
|
63
|
+
messages: [{ role: "user", content: question }],
|
|
64
|
+
}),
|
|
65
|
+
});
|
|
66
|
+
const data = (await res.json().catch(() => ({})));
|
|
67
|
+
if (!res.ok) {
|
|
68
|
+
throw new Error(data.error?.message ?? `Anthropic error (${res.status})`);
|
|
69
|
+
}
|
|
70
|
+
const text = data.content
|
|
71
|
+
?.filter((b) => b.type === "text")
|
|
72
|
+
.map((b) => b.text ?? "")
|
|
73
|
+
.join("")
|
|
74
|
+
.trim();
|
|
75
|
+
if (!text)
|
|
76
|
+
throw new Error("Empty response from Anthropic.");
|
|
77
|
+
return text;
|
|
78
|
+
}
|
|
79
|
+
export async function executeByokAskTurn(options) {
|
|
80
|
+
const locale = options.locale ?? "pt";
|
|
81
|
+
const ctx = await buildBootstrapContext(options.cwd);
|
|
82
|
+
let creds;
|
|
83
|
+
try {
|
|
84
|
+
creds = options.creds ?? requireActiveCredentials(locale);
|
|
85
|
+
}
|
|
86
|
+
catch (err) {
|
|
87
|
+
if (err instanceof ProviderSetupError) {
|
|
88
|
+
fail(err.message);
|
|
89
|
+
return false;
|
|
90
|
+
}
|
|
91
|
+
throw err;
|
|
92
|
+
}
|
|
93
|
+
if (creds.definition.kind === "cursor") {
|
|
94
|
+
fail(locale === "pt"
|
|
95
|
+
? "Cursor em modo ask usa o agente Cursor (BYOK). Usa `/mode agent` ou escolhe OpenAI/Anthropic em `/provedores`."
|
|
96
|
+
: locale === "zh"
|
|
97
|
+
? "Cursor 问答模式请使用 Cursor 代理,或在 `/供应商` 选择 OpenAI/Anthropic。"
|
|
98
|
+
: "Cursor ask uses the Cursor agent path — switch provider or use agent mode.");
|
|
99
|
+
return false;
|
|
100
|
+
}
|
|
101
|
+
const system = buildAskSystemPrompt(ctx, locale);
|
|
102
|
+
if (!options.quiet) {
|
|
103
|
+
info(`${creds.definition.name} · ${creds.model}`);
|
|
104
|
+
}
|
|
105
|
+
try {
|
|
106
|
+
const answer = creds.definition.kind === "anthropic"
|
|
107
|
+
? await chatAnthropic(creds, system, options.question)
|
|
108
|
+
: await chatOpenAiCompatible(creds, [
|
|
109
|
+
{ role: "system", content: system },
|
|
110
|
+
{ role: "user", content: options.question },
|
|
111
|
+
]);
|
|
112
|
+
if (!options.quiet)
|
|
113
|
+
ok(creds.definition.name);
|
|
114
|
+
console.log("\n" + answer);
|
|
115
|
+
return true;
|
|
116
|
+
}
|
|
117
|
+
catch (err) {
|
|
118
|
+
fail(err instanceof Error ? err.message : String(err));
|
|
119
|
+
return false;
|
|
120
|
+
}
|
|
121
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { type ProviderDefinition, type ProviderId } from "./catalog.js";
|
|
2
|
+
import type { EkzLocale } from "../locale.js";
|
|
3
|
+
import { type StoredProvider } from "./store.js";
|
|
4
|
+
export type ResolvedCredentials = {
|
|
5
|
+
id: ProviderId;
|
|
6
|
+
definition: ProviderDefinition;
|
|
7
|
+
apiKey: string;
|
|
8
|
+
model: string;
|
|
9
|
+
baseUrl?: string;
|
|
10
|
+
maskedKey: string;
|
|
11
|
+
};
|
|
12
|
+
export declare class ProviderSetupError extends Error {
|
|
13
|
+
constructor(message: string);
|
|
14
|
+
}
|
|
15
|
+
export type ProviderRow = {
|
|
16
|
+
definition: ProviderDefinition;
|
|
17
|
+
stored?: StoredProvider;
|
|
18
|
+
active: boolean;
|
|
19
|
+
maskedKey: string | null;
|
|
20
|
+
model: string;
|
|
21
|
+
};
|
|
22
|
+
export declare function listProviderRows(): ProviderRow[];
|
|
23
|
+
export declare function getActiveProviderId(): ProviderId | undefined;
|
|
24
|
+
export declare function resolveActiveCredentials(): ResolvedCredentials | null;
|
|
25
|
+
export declare function requireActiveCredentials(locale?: "pt" | "en" | "zh"): ResolvedCredentials;
|
|
26
|
+
export declare function isCursorProvider(creds: ResolvedCredentials): boolean;
|
|
27
|
+
export declare function resolveCredentialsWithOverride(override?: string, locale?: EkzLocale): ResolvedCredentials;
|
|
28
|
+
//# sourceMappingURL=credentials.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"credentials.d.ts","sourceRoot":"","sources":["../../../src/lib/providers/credentials.ts"],"names":[],"mappings":"AAAA,OAAO,EAA2C,KAAK,kBAAkB,EAAE,KAAK,UAAU,EAAE,MAAM,cAAc,CAAC;AAGjH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAC9C,OAAO,EAKL,KAAK,cAAc,EACpB,MAAM,YAAY,CAAC;AAEpB,MAAM,MAAM,mBAAmB,GAAG;IAChC,EAAE,EAAE,UAAU,CAAC;IACf,UAAU,EAAE,kBAAkB,CAAC;IAC/B,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;CACnB,CAAC;AAEF,qBAAa,kBAAmB,SAAQ,KAAK;gBAC/B,OAAO,EAAE,MAAM;CAI5B;AAED,MAAM,MAAM,WAAW,GAAG;IACxB,UAAU,EAAE,kBAAkB,CAAC;IAC/B,MAAM,CAAC,EAAE,cAAc,CAAC;IACxB,MAAM,EAAE,OAAO,CAAC;IAChB,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,KAAK,EAAE,MAAM,CAAC;CACf,CAAC;AAEF,wBAAgB,gBAAgB,IAAI,WAAW,EAAE,CAYhD;AAED,wBAAgB,mBAAmB,IAAI,UAAU,GAAG,SAAS,CAE5D;AAED,wBAAgB,wBAAwB,IAAI,mBAAmB,GAAG,IAAI,CAgBrE;AAED,wBAAgB,wBAAwB,CAAC,MAAM,GAAE,IAAI,GAAG,IAAI,GAAG,IAAW,GAAG,mBAAmB,CAI/F;AAED,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,mBAAmB,GAAG,OAAO,CAEpE;AAED,wBAAgB,8BAA8B,CAC5C,QAAQ,CAAC,EAAE,MAAM,EACjB,MAAM,GAAE,SAAgB,GACvB,mBAAmB,CAarB"}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { PROVIDER_CATALOG, getProviderDefinition } from "./catalog.js";
|
|
2
|
+
import { formatSetupRequiredMessage } from "../setup-messages.js";
|
|
3
|
+
import { resolveComposerModel } from "../composer-model.js";
|
|
4
|
+
import { loadProvidersFile, maskApiKey, resolveBaseUrl, resolveModel, } from "./store.js";
|
|
5
|
+
export class ProviderSetupError extends Error {
|
|
6
|
+
constructor(message) {
|
|
7
|
+
super(message);
|
|
8
|
+
this.name = "ProviderSetupError";
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
export function listProviderRows() {
|
|
12
|
+
const file = loadProvidersFile();
|
|
13
|
+
return PROVIDER_CATALOG.map((definition) => {
|
|
14
|
+
const stored = file.providers[definition.id];
|
|
15
|
+
return {
|
|
16
|
+
definition,
|
|
17
|
+
stored,
|
|
18
|
+
active: file.active === definition.id,
|
|
19
|
+
maskedKey: stored?.apiKey ? maskApiKey(stored.apiKey) : null,
|
|
20
|
+
model: resolveModel(definition.id, stored),
|
|
21
|
+
};
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
export function getActiveProviderId() {
|
|
25
|
+
return loadProvidersFile().active;
|
|
26
|
+
}
|
|
27
|
+
export function resolveActiveCredentials() {
|
|
28
|
+
const file = loadProvidersFile();
|
|
29
|
+
const id = file.active;
|
|
30
|
+
if (!id)
|
|
31
|
+
return null;
|
|
32
|
+
const stored = file.providers[id];
|
|
33
|
+
if (!stored?.apiKey?.trim())
|
|
34
|
+
return null;
|
|
35
|
+
const definition = PROVIDER_CATALOG.find((p) => p.id === id);
|
|
36
|
+
if (!definition)
|
|
37
|
+
return null;
|
|
38
|
+
return {
|
|
39
|
+
id,
|
|
40
|
+
definition,
|
|
41
|
+
apiKey: stored.apiKey.trim(),
|
|
42
|
+
model: resolveModel(id, stored),
|
|
43
|
+
baseUrl: resolveBaseUrl(id, stored),
|
|
44
|
+
maskedKey: maskApiKey(stored.apiKey),
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
export function requireActiveCredentials(locale = "en") {
|
|
48
|
+
const creds = resolveActiveCredentials();
|
|
49
|
+
if (creds)
|
|
50
|
+
return creds;
|
|
51
|
+
throw new ProviderSetupError(formatSetupRequiredMessage());
|
|
52
|
+
}
|
|
53
|
+
export function isCursorProvider(creds) {
|
|
54
|
+
return creds.definition.kind === "cursor";
|
|
55
|
+
}
|
|
56
|
+
export function resolveCredentialsWithOverride(override, locale = "en") {
|
|
57
|
+
if (override?.trim()) {
|
|
58
|
+
const def = getProviderDefinition("cursor");
|
|
59
|
+
if (!def)
|
|
60
|
+
throw new Error("Cursor provider missing from catalog.");
|
|
61
|
+
return {
|
|
62
|
+
id: "cursor",
|
|
63
|
+
definition: def,
|
|
64
|
+
apiKey: override.trim(),
|
|
65
|
+
model: resolveComposerModel().id,
|
|
66
|
+
maskedKey: maskApiKey(override.trim()),
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
return requireActiveCredentials(locale);
|
|
70
|
+
}
|