@ekzs/cli 0.2.0 → 0.3.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 +21 -11
- 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 +36 -35
- 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 +200 -125
- 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/index.js +25 -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 +69 -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/help.js +13 -12
- 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 +26 -0
- package/dist/lib/providers/credentials.d.ts.map +1 -0
- package/dist/lib/providers/credentials.js +54 -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 +87 -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 +81 -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/package.json +1 -1
- package/skills/ekz-sdk-cli/SKILL.md +6 -4
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
import { execFile } from "child_process";
|
|
2
|
+
import { existsSync, mkdirSync, readdirSync, readFileSync, statSync, writeFileSync } from "fs";
|
|
3
|
+
import { dirname, join, relative, resolve } from "path";
|
|
4
|
+
import { promisify } from "util";
|
|
5
|
+
import { detectShellEnvironment } from "../shell.js";
|
|
6
|
+
const execFileAsync = promisify(execFile);
|
|
7
|
+
const MAX_FILE_BYTES = 120_000;
|
|
8
|
+
const MAX_CMD_OUTPUT = 24_000;
|
|
9
|
+
function safePath(cwd, input) {
|
|
10
|
+
const root = resolve(cwd);
|
|
11
|
+
const abs = resolve(root, input.trim());
|
|
12
|
+
const rel = relative(root, abs);
|
|
13
|
+
if (rel.startsWith("..") || rel.includes("..")) {
|
|
14
|
+
throw new Error(`Path escapes project root: ${input}`);
|
|
15
|
+
}
|
|
16
|
+
return abs;
|
|
17
|
+
}
|
|
18
|
+
export async function toolReadFile(cwd, path) {
|
|
19
|
+
const abs = safePath(cwd, path);
|
|
20
|
+
if (!existsSync(abs))
|
|
21
|
+
return `Error: file not found: ${path}`;
|
|
22
|
+
const stat = statSync(abs);
|
|
23
|
+
if (!stat.isFile())
|
|
24
|
+
return `Error: not a file: ${path}`;
|
|
25
|
+
if (stat.size > MAX_FILE_BYTES) {
|
|
26
|
+
return readFileSync(abs, "utf8").slice(0, MAX_FILE_BYTES) + "\n…[truncated]";
|
|
27
|
+
}
|
|
28
|
+
return readFileSync(abs, "utf8");
|
|
29
|
+
}
|
|
30
|
+
export async function toolWriteFile(cwd, path, content) {
|
|
31
|
+
const abs = safePath(cwd, path);
|
|
32
|
+
mkdirSync(dirname(abs), { recursive: true });
|
|
33
|
+
writeFileSync(abs, content, "utf8");
|
|
34
|
+
return `Wrote ${relative(resolve(cwd), abs)} (${content.length} bytes)`;
|
|
35
|
+
}
|
|
36
|
+
export async function toolListFiles(cwd, path = ".") {
|
|
37
|
+
const abs = safePath(cwd, path);
|
|
38
|
+
if (!existsSync(abs))
|
|
39
|
+
return `Error: path not found: ${path}`;
|
|
40
|
+
const stat = statSync(abs);
|
|
41
|
+
if (!stat.isDirectory())
|
|
42
|
+
return `Error: not a directory: ${path}`;
|
|
43
|
+
const entries = readdirSync(abs, { withFileTypes: true })
|
|
44
|
+
.slice(0, 200)
|
|
45
|
+
.map((e) => `${e.isDirectory() ? "d" : "f"} ${join(path, e.name).replace(/\\/g, "/")}`);
|
|
46
|
+
return entries.join("\n") || "(empty)";
|
|
47
|
+
}
|
|
48
|
+
export async function toolRunCommand(cwd, command) {
|
|
49
|
+
const shell = detectShellEnvironment();
|
|
50
|
+
const trimmed = command.trim();
|
|
51
|
+
if (!trimmed)
|
|
52
|
+
return "Error: empty command";
|
|
53
|
+
try {
|
|
54
|
+
if (shell.agentShell === "powershell") {
|
|
55
|
+
const { stdout, stderr } = await execFileAsync("powershell.exe", ["-NoProfile", "-NonInteractive", "-Command", trimmed], { cwd: resolve(cwd), timeout: 90_000, maxBuffer: MAX_CMD_OUTPUT });
|
|
56
|
+
return [stdout, stderr].filter(Boolean).join("\n").slice(0, MAX_CMD_OUTPUT) || "(no output)";
|
|
57
|
+
}
|
|
58
|
+
if (shell.agentShell === "cmd") {
|
|
59
|
+
const { stdout, stderr } = await execFileAsync("cmd.exe", ["/c", trimmed], {
|
|
60
|
+
cwd: resolve(cwd),
|
|
61
|
+
timeout: 90_000,
|
|
62
|
+
maxBuffer: MAX_CMD_OUTPUT,
|
|
63
|
+
});
|
|
64
|
+
return [stdout, stderr].filter(Boolean).join("\n").slice(0, MAX_CMD_OUTPUT) || "(no output)";
|
|
65
|
+
}
|
|
66
|
+
const { stdout, stderr } = await execFileAsync(trimmed, {
|
|
67
|
+
cwd: resolve(cwd),
|
|
68
|
+
timeout: 90_000,
|
|
69
|
+
maxBuffer: MAX_CMD_OUTPUT,
|
|
70
|
+
shell: true,
|
|
71
|
+
});
|
|
72
|
+
return [stdout, stderr].filter(Boolean).join("\n").slice(0, MAX_CMD_OUTPUT) || "(no output)";
|
|
73
|
+
}
|
|
74
|
+
catch (err) {
|
|
75
|
+
const e = err;
|
|
76
|
+
const out = [e.stdout, e.stderr, e.message].filter(Boolean).join("\n");
|
|
77
|
+
return `Command failed:\n${out.slice(0, MAX_CMD_OUTPUT)}`;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
export const AGENT_TOOL_DEFINITIONS = [
|
|
81
|
+
{
|
|
82
|
+
type: "function",
|
|
83
|
+
function: {
|
|
84
|
+
name: "read_file",
|
|
85
|
+
description: "Read a UTF-8 text file relative to the project root.",
|
|
86
|
+
parameters: {
|
|
87
|
+
type: "object",
|
|
88
|
+
properties: { path: { type: "string", description: "Relative file path" } },
|
|
89
|
+
required: ["path"],
|
|
90
|
+
},
|
|
91
|
+
},
|
|
92
|
+
},
|
|
93
|
+
{
|
|
94
|
+
type: "function",
|
|
95
|
+
function: {
|
|
96
|
+
name: "write_file",
|
|
97
|
+
description: "Write or overwrite a UTF-8 text file relative to the project root.",
|
|
98
|
+
parameters: {
|
|
99
|
+
type: "object",
|
|
100
|
+
properties: {
|
|
101
|
+
path: { type: "string" },
|
|
102
|
+
content: { type: "string" },
|
|
103
|
+
},
|
|
104
|
+
required: ["path", "content"],
|
|
105
|
+
},
|
|
106
|
+
},
|
|
107
|
+
},
|
|
108
|
+
{
|
|
109
|
+
type: "function",
|
|
110
|
+
function: {
|
|
111
|
+
name: "list_files",
|
|
112
|
+
description: "List files and directories in a project-relative folder.",
|
|
113
|
+
parameters: {
|
|
114
|
+
type: "object",
|
|
115
|
+
properties: { path: { type: "string", description: "Directory path (default .)" } },
|
|
116
|
+
},
|
|
117
|
+
},
|
|
118
|
+
},
|
|
119
|
+
{
|
|
120
|
+
type: "function",
|
|
121
|
+
function: {
|
|
122
|
+
name: "run_command",
|
|
123
|
+
description: "Run a shell command in the project directory. Prefer ekz doctor/scan when possible.",
|
|
124
|
+
parameters: {
|
|
125
|
+
type: "object",
|
|
126
|
+
properties: { command: { type: "string" } },
|
|
127
|
+
required: ["command"],
|
|
128
|
+
},
|
|
129
|
+
},
|
|
130
|
+
},
|
|
131
|
+
];
|
|
132
|
+
export async function executeAgentTool(cwd, name, args) {
|
|
133
|
+
switch (name) {
|
|
134
|
+
case "read_file":
|
|
135
|
+
return toolReadFile(cwd, String(args.path ?? ""));
|
|
136
|
+
case "write_file":
|
|
137
|
+
return toolWriteFile(cwd, String(args.path ?? ""), String(args.content ?? ""));
|
|
138
|
+
case "list_files":
|
|
139
|
+
return toolListFiles(cwd, args.path ? String(args.path) : ".");
|
|
140
|
+
case "run_command":
|
|
141
|
+
return toolRunCommand(cwd, String(args.command ?? ""));
|
|
142
|
+
default:
|
|
143
|
+
return `Unknown tool: ${name}`;
|
|
144
|
+
}
|
|
145
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ui.d.ts","sourceRoot":"","sources":["../../../src/lib/providers/ui.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,KAAK,SAAS,EAAE,MAAM,cAAc,CAAC;AA+L9C,wBAAsB,uBAAuB,CAAC,MAAM,GAAE,SAAgB,iBAuDrE;AAED,wBAAgB,yBAAyB,CAAC,MAAM,EAAE,SAAS,GAAG,MAAM,CAMnE"}
|
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
import readline from "readline";
|
|
2
|
+
import { ok, warn } from "../output.js";
|
|
3
|
+
import { c, muted } from "../theme.js";
|
|
4
|
+
import { askWithPlaceholder, formatInputPrompt } from "../ui/prompt.js";
|
|
5
|
+
import { PROVIDER_CATALOG, getProviderDefinition } from "./catalog.js";
|
|
6
|
+
import { listProviderRows, resolveActiveCredentials, } from "./credentials.js";
|
|
7
|
+
import { maskApiKey, providersFilePath, removeStoredProvider, setActiveProvider, setStoredProvider, } from "./store.js";
|
|
8
|
+
function providersTitle(locale) {
|
|
9
|
+
if (locale === "pt")
|
|
10
|
+
return "Provedores (BYOK)";
|
|
11
|
+
if (locale === "zh")
|
|
12
|
+
return "供应商 (BYOK)";
|
|
13
|
+
return "Providers (BYOK)";
|
|
14
|
+
}
|
|
15
|
+
function t(locale, pt, en, zh) {
|
|
16
|
+
if (locale === "pt")
|
|
17
|
+
return pt;
|
|
18
|
+
if (locale === "zh")
|
|
19
|
+
return zh;
|
|
20
|
+
return en;
|
|
21
|
+
}
|
|
22
|
+
async function askLine(prompt, hidden = false) {
|
|
23
|
+
if (!hidden || !process.stdin.isTTY) {
|
|
24
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
25
|
+
return new Promise((resolve) => {
|
|
26
|
+
rl.question(prompt, (answer) => {
|
|
27
|
+
rl.close();
|
|
28
|
+
resolve(answer.trim());
|
|
29
|
+
});
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
process.stdout.write(prompt);
|
|
33
|
+
return new Promise((resolve) => {
|
|
34
|
+
const stdin = process.stdin;
|
|
35
|
+
const stdout = process.stdout;
|
|
36
|
+
readline.emitKeypressEvents(stdin);
|
|
37
|
+
stdin.setRawMode(true);
|
|
38
|
+
stdin.resume();
|
|
39
|
+
let value = "";
|
|
40
|
+
const cleanup = () => {
|
|
41
|
+
stdin.setRawMode(false);
|
|
42
|
+
stdin.removeListener("keypress", onKeypress);
|
|
43
|
+
stdout.write("\n");
|
|
44
|
+
};
|
|
45
|
+
const onKeypress = (_char, key) => {
|
|
46
|
+
if (!key)
|
|
47
|
+
return;
|
|
48
|
+
if (key.name === "return") {
|
|
49
|
+
cleanup();
|
|
50
|
+
resolve(value.trim());
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
if (key.name === "c" && key.ctrl) {
|
|
54
|
+
cleanup();
|
|
55
|
+
process.exit(130);
|
|
56
|
+
}
|
|
57
|
+
if (key.name === "backspace") {
|
|
58
|
+
if (value.length > 0) {
|
|
59
|
+
value = value.slice(0, -1);
|
|
60
|
+
stdout.write("\b \b");
|
|
61
|
+
}
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
const ch = key.sequence;
|
|
65
|
+
if (!ch || key.ctrl || key.meta)
|
|
66
|
+
return;
|
|
67
|
+
value += ch;
|
|
68
|
+
stdout.write("*");
|
|
69
|
+
};
|
|
70
|
+
stdin.on("keypress", onKeypress);
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
function printProviderList(rows, locale) {
|
|
74
|
+
console.log("");
|
|
75
|
+
console.log(` ${c.bold}${providersTitle(locale)}${c.reset}`);
|
|
76
|
+
console.log(` ${muted(providersFilePath())}`);
|
|
77
|
+
console.log("");
|
|
78
|
+
rows.forEach((row, index) => {
|
|
79
|
+
const n = index + 1;
|
|
80
|
+
const bullet = row.active ? `${c.green}●${c.reset}` : " ";
|
|
81
|
+
const keyCol = row.maskedKey ? muted(row.maskedKey) : muted("—");
|
|
82
|
+
const activeTag = row.active
|
|
83
|
+
? muted(t(locale, " activo", " active", " 当前"))
|
|
84
|
+
: "";
|
|
85
|
+
console.log(` ${bullet} ${c.bold}${String(n).padStart(2)}.${c.reset} ${row.definition.name.padEnd(14)} ${keyCol} ${muted(row.model)}${activeTag}`);
|
|
86
|
+
});
|
|
87
|
+
console.log("");
|
|
88
|
+
console.log(` ${muted(t(locale, "[número] configurar · u usar · r remover · q sair", "[number] configure · u use · r remove · q quit", "[数字] 配置 · u 使用 · r 删除 · q 退出"))}`);
|
|
89
|
+
console.log("");
|
|
90
|
+
}
|
|
91
|
+
async function configureProvider(id, locale) {
|
|
92
|
+
const def = getProviderDefinition(id);
|
|
93
|
+
if (!def)
|
|
94
|
+
return;
|
|
95
|
+
console.log("");
|
|
96
|
+
console.log(` ${c.bold}${def.name}${c.reset} — ${muted(def.description)}`);
|
|
97
|
+
console.log(` ${muted(def.docsUrl)}`);
|
|
98
|
+
console.log("");
|
|
99
|
+
const keyPrompt = t(locale, `Cola a API key (${def.keyHint}): `, `Paste API key (${def.keyHint}): `, `粘贴 API key (${def.keyHint}): `);
|
|
100
|
+
const apiKey = await askLine(keyPrompt, true);
|
|
101
|
+
if (!apiKey) {
|
|
102
|
+
warn(t(locale, "Chave vazia — cancelado.", "Empty key — cancelled.", "密钥为空,已取消。"));
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
const modelDefault = def.defaultModel;
|
|
106
|
+
const modelPrompt = t(locale, `Modelo [${modelDefault}]: `, `Model [${modelDefault}]: `, `模型 [${modelDefault}]: `);
|
|
107
|
+
const modelInput = await askLine(modelPrompt);
|
|
108
|
+
const model = modelInput || modelDefault;
|
|
109
|
+
let baseUrl;
|
|
110
|
+
if (def.kind === "openai-compatible" || def.defaultBaseUrl) {
|
|
111
|
+
const urlPrompt = t(locale, `Base URL [${def.defaultBaseUrl ?? "(default)"}]: `, `Base URL [${def.defaultBaseUrl ?? "(default)"}]: `, `Base URL [${def.defaultBaseUrl ?? "(default)"}]: `);
|
|
112
|
+
const urlInput = await askLine(urlPrompt);
|
|
113
|
+
if (urlInput)
|
|
114
|
+
baseUrl = urlInput;
|
|
115
|
+
}
|
|
116
|
+
setStoredProvider(id, { apiKey, model, baseUrl });
|
|
117
|
+
ok(t(locale, `Guardado ${def.name} (${maskApiKey(apiKey)})`, `Saved ${def.name} (${maskApiKey(apiKey)})`, `已保存 ${def.name} (${maskApiKey(apiKey)})`));
|
|
118
|
+
const active = resolveActiveCredentials();
|
|
119
|
+
if (!active || active.id !== id) {
|
|
120
|
+
const useNow = await askLine(t(locale, "Usar como provider activo? [Y/n]: ", "Use as active provider? [Y/n]: ", "设为当前 provider? [Y/n]: "), false);
|
|
121
|
+
if (!useNow || useNow.toLowerCase() === "y" || useNow.toLowerCase() === "s" || useNow === "") {
|
|
122
|
+
setActiveProvider(id);
|
|
123
|
+
ok(t(locale, `${def.name} activo.`, `${def.name} is now active.`, `${def.name} 已激活。`));
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
function resolveRowTarget(rows, target) {
|
|
128
|
+
const trimmed = target.trim().toLowerCase();
|
|
129
|
+
const num = Number(trimmed);
|
|
130
|
+
if (Number.isInteger(num) && num >= 1 && num <= rows.length) {
|
|
131
|
+
return rows[num - 1];
|
|
132
|
+
}
|
|
133
|
+
return rows.find((r) => r.definition.id === trimmed);
|
|
134
|
+
}
|
|
135
|
+
export async function runProvidersInteractive(locale = "pt") {
|
|
136
|
+
while (true) {
|
|
137
|
+
const rows = listProviderRows();
|
|
138
|
+
printProviderList(rows, locale);
|
|
139
|
+
const choice = (await askWithPlaceholder({
|
|
140
|
+
prompt: formatInputPrompt(),
|
|
141
|
+
placeholder: t(locale, "número ou u/r/q", "number or u/r/q", "数字或 u/r/q"),
|
|
142
|
+
})).trim();
|
|
143
|
+
if (!choice || choice.toLowerCase() === "q" || choice === "sair" || choice === "退出")
|
|
144
|
+
break;
|
|
145
|
+
const num = Number(choice);
|
|
146
|
+
if (Number.isInteger(num) && num >= 1 && num <= rows.length) {
|
|
147
|
+
await configureProvider(rows[num - 1].definition.id, locale);
|
|
148
|
+
continue;
|
|
149
|
+
}
|
|
150
|
+
const parts = choice.split(/\s+/);
|
|
151
|
+
const cmd = parts[0]?.toLowerCase();
|
|
152
|
+
if (cmd === "u" || cmd === "use" || cmd === "usar" || cmd === "使用") {
|
|
153
|
+
const target = parts[1] ?? (await askLine(t(locale, "Número ou id: ", "Number or id: ", "编号或 id: ")));
|
|
154
|
+
const row = resolveRowTarget(rows, target);
|
|
155
|
+
if (!row?.stored?.apiKey) {
|
|
156
|
+
warn(t(locale, "Provider sem chave.", "Provider has no key.", "该 provider 未配置密钥。"));
|
|
157
|
+
continue;
|
|
158
|
+
}
|
|
159
|
+
setActiveProvider(row.definition.id);
|
|
160
|
+
ok(t(locale, `${row.definition.name} activo.`, `${row.definition.name} active.`, `${row.definition.name} 已激活。`));
|
|
161
|
+
continue;
|
|
162
|
+
}
|
|
163
|
+
if (cmd === "r" || cmd === "remove" || cmd === "remover" || cmd === "删除") {
|
|
164
|
+
const target = parts[1] ?? (await askLine(t(locale, "Número ou id: ", "Number or id: ", "编号或 id: ")));
|
|
165
|
+
const row = resolveRowTarget(rows, target);
|
|
166
|
+
if (!row) {
|
|
167
|
+
warn(t(locale, "Provider inválido.", "Invalid provider.", "无效的 provider。"));
|
|
168
|
+
continue;
|
|
169
|
+
}
|
|
170
|
+
removeStoredProvider(row.definition.id);
|
|
171
|
+
ok(t(locale, `${row.definition.name} removido.`, `${row.definition.name} removed.`, `${row.definition.name} 已删除。`));
|
|
172
|
+
continue;
|
|
173
|
+
}
|
|
174
|
+
const byId = PROVIDER_CATALOG.find((p) => p.id === choice.toLowerCase());
|
|
175
|
+
if (byId) {
|
|
176
|
+
await configureProvider(byId.id, locale);
|
|
177
|
+
continue;
|
|
178
|
+
}
|
|
179
|
+
warn(t(locale, "Opção inválida.", "Invalid option.", "无效选项。"));
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
export function formatActiveProviderLabel(locale) {
|
|
183
|
+
const creds = resolveActiveCredentials();
|
|
184
|
+
if (!creds) {
|
|
185
|
+
return t(locale, "sem provider", "no provider", "未配置");
|
|
186
|
+
}
|
|
187
|
+
return `${creds.definition.name} · ${creds.maskedKey}`;
|
|
188
|
+
}
|
package/package.json
CHANGED
|
@@ -35,15 +35,17 @@ Model env: `EKZ_AGENT_MODEL=composer-2.5-fast` → `composer-2.5` + `fast: true`
|
|
|
35
35
|
| `ekz doctor` | Validate `.env`, live OAuth (free) |
|
|
36
36
|
| `ekz scan` | Common integration mistakes (free) |
|
|
37
37
|
| `ekz webhook <url>` | POST sample payload (free) |
|
|
38
|
-
| `ekz
|
|
38
|
+
| `ekz health` | OAuth + rail health (free) |
|
|
39
|
+
| `ekz providers` | Configure LLM API keys — BYOK (`ekz provedores` / `ekz 供应商`) |
|
|
40
|
+
| `ekz agent` | Local coding agent (BYOK — configure via `ekz providers`) |
|
|
39
41
|
| `ekz fix` | Auto-audit + fix integration |
|
|
40
|
-
| `ekz ask` |
|
|
42
|
+
| `ekz ask` | BYOK Q&A only (no file edits) |
|
|
41
43
|
|
|
42
44
|
Modes: `--mode agent` (default) | `--mode plan` | `--mode ask`. Shorthand: `--plan` = plan mode.
|
|
43
|
-
In the REPL: `/help`, `/mode agent|plan|ask`, `/doctor`, `/scan`, `/lang`, `/save`, `/resume`.
|
|
45
|
+
In the REPL: `/help`, `/providers` (`/provedores`, `/供应商`), `/mode agent|plan|ask`, `/doctor`, `/scan`, `/lang`, `/save`, `/resume`.
|
|
44
46
|
|
|
45
47
|
```bash
|
|
46
|
-
|
|
48
|
+
ekz providers # BYOK keys → ~/.ekz/providers.json
|
|
47
49
|
ekz # interactive REPL (agent mode)
|
|
48
50
|
ekz --mode plan # plan before edits
|
|
49
51
|
ekz --mode ask # remote Q&A, no edits
|