@ekzs/cli 0.3.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.
Files changed (39) hide show
  1. package/README.md +31 -5
  2. package/dist/commands/ask.d.ts.map +1 -1
  3. package/dist/commands/ask.js +16 -3
  4. package/dist/commands/local-agent.d.ts.map +1 -1
  5. package/dist/commands/local-agent.js +146 -60
  6. package/dist/commands/setup.d.ts +5 -0
  7. package/dist/commands/setup.d.ts.map +1 -0
  8. package/dist/commands/setup.js +4 -0
  9. package/dist/index.js +16 -0
  10. package/dist/lib/commands-i18n.d.ts +2 -2
  11. package/dist/lib/commands-i18n.d.ts.map +1 -1
  12. package/dist/lib/commands-i18n.js +21 -12
  13. package/dist/lib/global-config.d.ts +19 -0
  14. package/dist/lib/global-config.d.ts.map +1 -0
  15. package/dist/lib/global-config.js +52 -0
  16. package/dist/lib/help.d.ts.map +1 -1
  17. package/dist/lib/help.js +3 -0
  18. package/dist/lib/locale.d.ts.map +1 -1
  19. package/dist/lib/locale.js +2 -1
  20. package/dist/lib/onboarding.d.ts +20 -0
  21. package/dist/lib/onboarding.d.ts.map +1 -0
  22. package/dist/lib/onboarding.js +129 -0
  23. package/dist/lib/providers/credentials.d.ts +2 -0
  24. package/dist/lib/providers/credentials.d.ts.map +1 -1
  25. package/dist/lib/providers/credentials.js +19 -3
  26. package/dist/lib/providers/cursor-runner.d.ts +2 -2
  27. package/dist/lib/providers/cursor-runner.d.ts.map +1 -1
  28. package/dist/lib/providers/cursor-runner.js +7 -5
  29. package/dist/lib/providers/cursor-sdk.d.ts +8 -0
  30. package/dist/lib/providers/cursor-sdk.d.ts.map +1 -0
  31. package/dist/lib/providers/cursor-sdk.js +25 -0
  32. package/dist/lib/providers/store.d.ts.map +1 -1
  33. package/dist/lib/providers/store.js +3 -0
  34. package/dist/lib/setup-messages.d.ts +4 -0
  35. package/dist/lib/setup-messages.d.ts.map +1 -0
  36. package/dist/lib/setup-messages.js +13 -0
  37. package/dist/lib/ui/splash.d.ts.map +1 -1
  38. package/dist/lib/ui/splash.js +4 -0
  39. package/package.json +10 -2
package/README.md CHANGED
@@ -8,17 +8,43 @@ Runs **in your repo** with Composer 2.5 Fast. Edits files directly.
8
8
 
9
9
  ```bash
10
10
  npm install -g @ekzs/cli
11
- # or from this monorepo:
12
- npm run build --prefix packages/cli
11
+ ```
12
+
13
+ Clean install (~15 packages) — BYOK with OpenAI, Anthropic, DeepSeek, etc. Keys via `ekz providers`.
14
+
15
+ **Cursor provider only** (optional — pulls native deps + npm deprecation noise from `@cursor/sdk`):
16
+
17
+ ```bash
18
+ npm install -g @cursor/sdk
19
+ ```
20
+
21
+ Or install both at once:
22
+
23
+ ```bash
24
+ npm install -g @ekzs/cli @cursor/sdk
13
25
  ```
14
26
 
15
27
  ## Setup (BYOK)
16
28
 
17
- Configure your LLM API keys once — stored in `~/.ekz/providers.json` (not in `.env`):
29
+ First run:
18
30
 
19
31
  ```bash
20
- ekz providers
21
- # or inside the REPL: /providers
32
+ ekz setup
33
+ # aliases: ekz configurar · ekz 设置
34
+ ```
35
+
36
+ Wizard steps: **language** → **your name** → **Configurar provedor** (or offline).
37
+
38
+ Stored in `~/.ekz/config.json` (name, locale) and `~/.ekz/providers.json` (API keys).
39
+
40
+ Without a provider, `ekz` shows:
41
+
42
+ ```
43
+ Setup required · Configuração necessária · 需要设置
44
+
45
+ pt: Corre `ekz setup` ou `ekz --offline`
46
+ en: Run `ekz setup` or `ekz --offline`
47
+ zh: 运行 `ekz setup` 或 `ekz --offline`
22
48
  ```
23
49
 
24
50
  Supported providers: **Cursor**, **OpenAI**, **Anthropic**, **DeepSeek**, **Kimi**, **Groq**, **Mistral**, **Google Gemini**, **Ollama**.
@@ -1 +1 @@
1
- {"version":3,"file":"ask.d.ts","sourceRoot":"","sources":["../../src/commands/ask.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,KAAK,SAAS,EAA0B,MAAM,kBAAkB,CAAC;AAyC1E,MAAM,MAAM,UAAU,GAAG;IACvB,GAAG,EAAE,MAAM,CAAC;IACZ,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,MAAM,CAAC,EAAE,SAAS,CAAC;CACpB,CAAC;AAEF,mDAAmD;AACnD,wBAAsB,cAAc,CAAC,OAAO,EAAE,UAAU,GAAG,OAAO,CAAC,OAAO,CAAC,CA0B1E;AAED,wBAAsB,MAAM,CAAC,OAAO,EAAE,IAAI,CAAC,UAAU,EAAE,OAAO,CAAC,iBAO9D;AAgDD,wBAAsB,iBAAiB,CAAC,IAAI,EAAE;IAC5C,GAAG,EAAE,MAAM,CAAC;IACZ,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB,iBA2DA;AAED,wBAAsB,eAAe,CAAC,GAAG,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CAM/E"}
1
+ {"version":3,"file":"ask.d.ts","sourceRoot":"","sources":["../../src/commands/ask.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,KAAK,SAAS,EAA0B,MAAM,kBAAkB,CAAC;AAyC1E,MAAM,MAAM,UAAU,GAAG;IACvB,GAAG,EAAE,MAAM,CAAC;IACZ,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,MAAM,CAAC,EAAE,SAAS,CAAC;CACpB,CAAC;AAEF,mDAAmD;AACnD,wBAAsB,cAAc,CAAC,OAAO,EAAE,UAAU,GAAG,OAAO,CAAC,OAAO,CAAC,CA0B1E;AAED,wBAAsB,MAAM,CAAC,OAAO,EAAE,IAAI,CAAC,UAAU,EAAE,OAAO,CAAC,iBAO9D;AAgDD,wBAAsB,iBAAiB,CAAC,IAAI,EAAE;IAC5C,GAAG,EAAE,MAAM,CAAC;IACZ,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB,iBAwEA;AAED,wBAAsB,eAAe,CAAC,GAAG,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CAM/E"}
@@ -5,6 +5,7 @@ import { parseLocale, uiStrings } from "../lib/locale.js";
5
5
  import { parseMode } from "../lib/mode.js";
6
6
  import { fail, heading, info, ok } from "../lib/output.js";
7
7
  import { loadPreferences, savePreferences } from "../lib/preferences.js";
8
+ import { ensureAgentReady, offlineModelLabel } from "../lib/onboarding.js";
8
9
  import { executeByokAskTurn } from "../lib/providers/chat.js";
9
10
  import { executeCursorAskTurn } from "../lib/providers/cursor-runner.js";
10
11
  import { isCursorProvider, requireActiveCredentials, ProviderSetupError, } from "../lib/providers/credentials.js";
@@ -13,7 +14,6 @@ import { redactText } from "../lib/redact.js";
13
14
  import { askWithPlaceholder, formatInputPrompt } from "../lib/ui/prompt.js";
14
15
  import { inputPlaceholder } from "../lib/ui/splash.js";
15
16
  function deterministicAnswer(question, ctx, locale) {
16
- const p = providersCommandLabels(locale);
17
17
  const lines = ["## Ekz assistant (offline)", "", question, ""];
18
18
  if (ctx.doctor.issues.length) {
19
19
  lines.push("### Issues", ...ctx.doctor.issues.map((i) => `- ${i}`), "");
@@ -21,7 +21,7 @@ function deterministicAnswer(question, ctx, locale) {
21
21
  if (ctx.scan.length) {
22
22
  lines.push("### Scan", ...ctx.scan.slice(0, 8).map((f) => `- \`${f.file}:${f.line}\` · ${f.message}`), "");
23
23
  }
24
- lines.push("### Next steps", `- Run \`${p.cli}\` or \`${p.repl}\` and paste your LLM API key (BYOK)`, "- Or run `ekz --mode agent` with your provider configured");
24
+ lines.push("### Next steps", "- Run `ekz setup` to configure language, name, and provider", "- Or `ekz --offline` for doctor/scan only");
25
25
  return lines.join("\n");
26
26
  }
27
27
  /** Q&A via BYOK provider (no local file edits). */
@@ -94,10 +94,23 @@ function handleAskMeta(line, locale) {
94
94
  export async function runInteractiveAsk(opts) {
95
95
  const prefs = loadPreferences(opts.cwd, opts.locale, "ask");
96
96
  let currentLocale = prefs.locale;
97
+ let askOffline = Boolean(opts.offline);
98
+ try {
99
+ const ready = await ensureAgentReady({
100
+ locale: currentLocale,
101
+ offlineFlag: opts.offline,
102
+ });
103
+ askOffline = ready.offline || askOffline;
104
+ }
105
+ catch (err) {
106
+ fail(err instanceof Error ? err.message : String(err));
107
+ return;
108
+ }
97
109
  printWelcome({
98
110
  cwd: opts.cwd,
99
111
  locale: currentLocale,
100
112
  mode: "ask",
113
+ model: askOffline ? offlineModelLabel(currentLocale) : undefined,
101
114
  });
102
115
  let turn = 0;
103
116
  while (true) {
@@ -134,7 +147,7 @@ export async function runInteractiveAsk(opts) {
134
147
  await executeAskTurn({
135
148
  cwd: opts.cwd,
136
149
  question: line,
137
- offline: opts.offline,
150
+ offline: askOffline,
138
151
  quiet: true,
139
152
  locale: currentLocale,
140
153
  });
@@ -1 +1 @@
1
- {"version":3,"file":"local-agent.d.ts","sourceRoot":"","sources":["../../src/commands/local-agent.ts"],"names":[],"mappings":"AAWA,OAAO,EAAsC,KAAK,OAAO,EAAE,MAAM,gBAAgB,CAAC;AAsBlF,MAAM,MAAM,iBAAiB,GAAG;IAC9B,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB,CAAC;AAkPF,wBAAsB,aAAa,CAAC,IAAI,EAAE,iBAAiB,iBAuG1D;AAED,wBAAsB,mBAAmB,CAAC,IAAI,EAAE,IAAI,CAAC,iBAAiB,EAAE,MAAM,GAAG,aAAa,CAAC,iBAwE9F"}
1
+ {"version":3,"file":"local-agent.d.ts","sourceRoot":"","sources":["../../src/commands/local-agent.ts"],"names":[],"mappings":"AAWA,OAAO,EAAsC,KAAK,OAAO,EAAE,MAAM,gBAAgB,CAAC;AAsBlF,MAAM,MAAM,iBAAiB,GAAG;IAC9B,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB,CAAC;AA2PF,wBAAsB,aAAa,CAAC,IAAI,EAAE,iBAAiB,iBA0I1D;AAED,wBAAsB,mBAAmB,CAAC,IAAI,EAAE,IAAI,CAAC,iBAAiB,EAAE,MAAM,GAAG,aAAa,CAAC,iBAmG9F"}
@@ -1,10 +1,10 @@
1
1
  import { resolve } from "path";
2
- import { Agent, CursorAgentError } from "@cursor/sdk";
3
2
  import { executeAskTurn } from "../commands/ask.js";
4
3
  import { runDoctor } from "../commands/doctor.js";
5
4
  import { runScan } from "../commands/scan.js";
6
5
  import { inputPlaceholder, printHelp, printLangSwitch, printModeSwitch, printWelcome } from "../lib/banner.js";
7
6
  import { loadProjectEnv } from "../lib/env.js";
7
+ import { ensureAgentReady, offlineAgentHint, offlineModelLabel } from "../lib/onboarding.js";
8
8
  import { resolveReplMetaCommand, isProvidersMetaCommand } from "../lib/commands-i18n.js";
9
9
  import { buildAgentPrompt, buildBootstrapContext } from "../lib/context.js";
10
10
  import { resolveComposerModel } from "../lib/composer-model.js";
@@ -12,29 +12,31 @@ import { parseLocale, uiStrings } from "../lib/locale.js";
12
12
  import { modeRequiresCursorAgent, parseMode } from "../lib/mode.js";
13
13
  import { loadPreferences, savePreferences } from "../lib/preferences.js";
14
14
  import { runByokAgentTurn, usesCursorSdk } from "../lib/providers/agent-runner.js";
15
+ import { CursorSdkMissingError, loadCursorSdk } from "../lib/providers/cursor-sdk.js";
15
16
  import { disposeAgent, streamRun } from "../lib/providers/cursor-runner.js";
16
- import { getProviderDefinition } from "../lib/providers/catalog.js";
17
- import { requireActiveCredentials, } from "../lib/providers/credentials.js";
17
+ import { resolveActiveCredentials, resolveCredentialsWithOverride, } from "../lib/providers/credentials.js";
18
18
  import { runProvidersInteractive, formatActiveProviderLabel } from "../lib/providers/ui.js";
19
- import { maskApiKey } from "../lib/providers/store.js";
20
19
  import { clearSession, getSessionByIndex, loadSession, persistSession, saveNamedSession, } from "../lib/session.js";
21
20
  import { fail, info, ok, warn } from "../lib/output.js";
22
21
  import { turnDivider } from "../lib/theme.js";
23
22
  import { askWithPlaceholder, formatInputPrompt } from "../lib/ui/prompt.js";
24
- function resolveAgentCredentials(override, locale = "pt") {
25
- if (override?.trim()) {
26
- const def = getProviderDefinition("cursor");
27
- if (!def)
28
- throw new Error("Cursor provider missing from catalog.");
29
- return {
30
- id: "cursor",
31
- definition: def,
32
- apiKey: override.trim(),
33
- model: resolveComposerModel().id,
34
- maskedKey: maskApiKey(override.trim()),
35
- };
23
+ async function prepareAgentSession(opts, prefs) {
24
+ try {
25
+ const ready = await ensureAgentReady({
26
+ locale: prefs.locale,
27
+ offlineFlag: opts.askOffline,
28
+ apiKey: opts.apiKey,
29
+ });
30
+ return { offline: ready.offline, creds: ready.creds };
31
+ }
32
+ catch (err) {
33
+ fail(err instanceof Error ? err.message : String(err));
34
+ process.exitCode = 1;
35
+ return null;
36
36
  }
37
- return requireActiveCredentials(locale);
37
+ }
38
+ function resolveAgentCredentials(override, locale = "pt") {
39
+ return resolveCredentialsWithOverride(override, locale);
38
40
  }
39
41
  function resolveCursorModel(creds) {
40
42
  return resolveComposerModel(process.env, creds.model);
@@ -53,12 +55,12 @@ async function runAgentTurn(options) {
53
55
  const ctx = await buildBootstrapContext(options.cwd);
54
56
  const prompt = buildAgentPrompt(options.task, ctx, options.locale);
55
57
  try {
56
- await streamRun(options.cursorAgent, prompt);
58
+ await streamRun(options.cursorAgent, prompt, options.locale);
57
59
  console.log("\n");
58
60
  return { cursorAgent: options.cursorAgent, ok: true };
59
61
  }
60
62
  catch (err) {
61
- if (err instanceof CursorAgentError) {
63
+ if (err instanceof Error && err.name === "CursorAgentError") {
62
64
  fail(err.message);
63
65
  return { cursorAgent: options.cursorAgent, ok: false };
64
66
  }
@@ -77,6 +79,7 @@ async function runAgentTurn(options) {
77
79
  async function createOrResumeAgent(opts, locale = "pt") {
78
80
  const cwd = resolve(opts.cwd);
79
81
  const creds = resolveAgentCredentials(opts.apiKey, locale);
82
+ const { Agent } = await loadCursorSdk(locale);
80
83
  const apiKey = creds.apiKey;
81
84
  const model = resolveCursorModel(creds);
82
85
  const usePlan = opts.mode === "plan" || Boolean(opts.plan);
@@ -195,6 +198,7 @@ async function resumeAgentInRepl(cwd, current, agentId, locale, apiKeyOverride)
195
198
  ? "恢复会话仅适用于 Cursor provider。"
196
199
  : "Resume session is Cursor-only.");
197
200
  }
201
+ const { Agent } = await loadCursorSdk(locale);
198
202
  const model = resolveCursorModel(creds);
199
203
  const agent = await Agent.resume(agentId, {
200
204
  apiKey: creds.apiKey,
@@ -210,24 +214,24 @@ export async function runLocalAgent(opts) {
210
214
  loadProjectEnv(cwd);
211
215
  const prefs = loadPreferences(cwd, opts.locale, opts.mode, opts.plan);
212
216
  const mode = prefs.mode;
213
- const providerLabel = formatActiveProviderLabel(prefs.locale);
217
+ const session = await prepareAgentSession(opts, prefs);
218
+ if (!session)
219
+ return;
220
+ const { offline, creds: sessionCreds } = session;
221
+ const askOffline = offline || Boolean(opts.askOffline);
222
+ const providerLabel = askOffline
223
+ ? offlineModelLabel(prefs.locale)
224
+ : sessionCreds
225
+ ? formatActiveProviderLabel(prefs.locale)
226
+ : formatActiveProviderLabel(prefs.locale);
214
227
  if (mode === "ask") {
215
- let askCreds;
216
- try {
217
- askCreds = resolveAgentCredentials(opts.apiKey, prefs.locale);
218
- }
219
- catch (err) {
220
- fail(err instanceof Error ? err.message : String(err));
221
- process.exitCode = 1;
222
- return;
223
- }
224
228
  printWelcome({ cwd, locale: prefs.locale, mode: "ask", model: providerLabel });
225
229
  if (opts.task.trim()) {
226
230
  info(`${uiStrings(prefs.locale).workingOn}: ${opts.task.slice(0, 120)}${opts.task.length > 120 ? "…" : ""}\n`);
227
231
  await executeAskTurn({
228
232
  cwd,
229
233
  question: opts.task.trim(),
230
- offline: opts.askOffline,
234
+ offline: askOffline,
231
235
  quiet: true,
232
236
  locale: prefs.locale,
233
237
  });
@@ -240,23 +244,54 @@ export async function runLocalAgent(opts) {
240
244
  locale: prefs.locale,
241
245
  mode: "ask",
242
246
  apiKey: opts.apiKey,
243
- askOffline: opts.askOffline,
244
- creds: askCreds,
247
+ askOffline,
248
+ creds: sessionCreds,
249
+ offline: askOffline,
250
+ });
251
+ }
252
+ return;
253
+ }
254
+ if (askOffline) {
255
+ printWelcome({ cwd, locale: prefs.locale, mode, model: providerLabel });
256
+ savePreferences(cwd, { mode });
257
+ if (opts.task.trim()) {
258
+ info(`${uiStrings(prefs.locale).workingOn}: ${opts.task.slice(0, 120)}${opts.task.length > 120 ? "…" : ""}\n`);
259
+ warn(offlineAgentHint(prefs.locale));
260
+ }
261
+ if (opts.interactive !== false) {
262
+ await runRepl({
263
+ agent: null,
264
+ cwd,
265
+ locale: prefs.locale,
266
+ mode,
267
+ apiKey: opts.apiKey,
268
+ askOffline: true,
269
+ creds: null,
270
+ offline: true,
245
271
  });
246
272
  }
247
273
  return;
248
274
  }
275
+ if (!sessionCreds) {
276
+ fail(offlineAgentHint(prefs.locale));
277
+ process.exitCode = 1;
278
+ return;
279
+ }
249
280
  let agent = null;
250
281
  let resumed = false;
251
- let creds;
282
+ const creds = sessionCreds;
252
283
  try {
253
- creds = resolveAgentCredentials(opts.apiKey, prefs.locale);
254
284
  if (usesCursorSdk(creds)) {
255
285
  ({ agent, resumed } = await createOrResumeAgent({ ...opts, mode }, prefs.locale));
256
286
  }
257
287
  }
258
288
  catch (err) {
259
- if (err instanceof CursorAgentError) {
289
+ if (err instanceof CursorSdkMissingError) {
290
+ fail(err.message);
291
+ process.exitCode = 1;
292
+ return;
293
+ }
294
+ if (err instanceof Error && err.name === "CursorAgentError") {
260
295
  fail(`Agent startup failed: ${err.message}`);
261
296
  process.exitCode = 1;
262
297
  return;
@@ -298,8 +333,9 @@ export async function runLocalAgent(opts) {
298
333
  locale: prefs.locale,
299
334
  mode,
300
335
  apiKey: opts.apiKey,
301
- askOffline: opts.askOffline,
336
+ askOffline,
302
337
  creds,
338
+ offline: false,
303
339
  });
304
340
  }
305
341
  else {
@@ -316,17 +352,15 @@ export async function runInteractiveAgent(opts) {
316
352
  loadProjectEnv(cwd);
317
353
  const prefs = loadPreferences(cwd, opts.locale, opts.mode, opts.plan);
318
354
  const mode = prefs.mode;
319
- const providerLabel = formatActiveProviderLabel(prefs.locale);
355
+ const session = await prepareAgentSession({ ...opts, task: "" }, prefs);
356
+ if (!session)
357
+ return;
358
+ const { offline, creds: sessionCreds } = session;
359
+ const askOffline = offline || Boolean(opts.askOffline);
360
+ const providerLabel = askOffline
361
+ ? offlineModelLabel(prefs.locale)
362
+ : formatActiveProviderLabel(prefs.locale);
320
363
  if (mode === "ask") {
321
- let creds;
322
- try {
323
- creds = resolveAgentCredentials(opts.apiKey, prefs.locale);
324
- }
325
- catch (err) {
326
- fail(err instanceof Error ? err.message : String(err));
327
- process.exitCode = 1;
328
- return;
329
- }
330
364
  printWelcome({ cwd, locale: prefs.locale, mode: "ask", model: providerLabel });
331
365
  await runRepl({
332
366
  agent: null,
@@ -334,22 +368,47 @@ export async function runInteractiveAgent(opts) {
334
368
  locale: prefs.locale,
335
369
  mode: "ask",
336
370
  apiKey: opts.apiKey,
337
- askOffline: opts.askOffline,
338
- creds,
371
+ askOffline,
372
+ creds: sessionCreds,
373
+ offline: askOffline,
339
374
  });
340
375
  return;
341
376
  }
377
+ if (askOffline) {
378
+ printWelcome({ cwd, locale: prefs.locale, mode, model: providerLabel });
379
+ savePreferences(cwd, { mode });
380
+ await runRepl({
381
+ agent: null,
382
+ cwd,
383
+ locale: prefs.locale,
384
+ mode,
385
+ apiKey: opts.apiKey,
386
+ askOffline: true,
387
+ creds: null,
388
+ offline: true,
389
+ });
390
+ return;
391
+ }
392
+ if (!sessionCreds) {
393
+ fail(offlineAgentHint(prefs.locale));
394
+ process.exitCode = 1;
395
+ return;
396
+ }
342
397
  let agent = null;
343
398
  let resumed = false;
344
- let creds;
399
+ const creds = sessionCreds;
345
400
  try {
346
- creds = resolveAgentCredentials(opts.apiKey, prefs.locale);
347
401
  if (usesCursorSdk(creds)) {
348
402
  ({ agent, resumed } = await createOrResumeAgent({ ...opts, task: "", mode }, prefs.locale));
349
403
  }
350
404
  }
351
405
  catch (err) {
352
- if (err instanceof CursorAgentError) {
406
+ if (err instanceof CursorSdkMissingError) {
407
+ fail(err.message);
408
+ process.exitCode = 1;
409
+ return;
410
+ }
411
+ if (err instanceof Error && err.name === "CursorAgentError") {
353
412
  fail(`Agent startup failed: ${err.message}`);
354
413
  process.exitCode = 1;
355
414
  return;
@@ -376,8 +435,9 @@ export async function runInteractiveAgent(opts) {
376
435
  locale: prefs.locale,
377
436
  mode,
378
437
  apiKey: opts.apiKey,
379
- askOffline: opts.askOffline,
438
+ askOffline,
380
439
  creds,
440
+ offline: false,
381
441
  });
382
442
  }
383
443
  finally {
@@ -390,16 +450,31 @@ async function runRepl(opts) {
390
450
  let currentMode = opts.mode;
391
451
  let currentAgent = opts.agent;
392
452
  let currentCreds = opts.creds;
453
+ let currentOffline = opts.offline;
393
454
  let turn = 0;
394
455
  const t = () => uiStrings(currentLocale);
395
456
  const resolveCreds = () => {
457
+ if (currentOffline) {
458
+ try {
459
+ const live = resolveActiveCredentials();
460
+ if (live) {
461
+ currentOffline = false;
462
+ currentCreds = live;
463
+ return live;
464
+ }
465
+ }
466
+ catch {
467
+ /* stay offline */
468
+ }
469
+ return null;
470
+ }
396
471
  try {
397
472
  currentCreds = resolveAgentCredentials(opts.apiKey, currentLocale);
473
+ return currentCreds;
398
474
  }
399
475
  catch {
400
- /* keep previous */
476
+ return currentCreds;
401
477
  }
402
- return currentCreds;
403
478
  };
404
479
  while (true) {
405
480
  const line = (await askWithPlaceholder({
@@ -410,7 +485,11 @@ async function runRepl(opts) {
410
485
  break;
411
486
  if (isProvidersMetaCommand(line.trim().split(/\s+/)[0] ?? "")) {
412
487
  await runProvidersInteractive(currentLocale);
413
- resolveCreds();
488
+ const live = resolveActiveCredentials();
489
+ if (live) {
490
+ currentOffline = false;
491
+ currentCreds = live;
492
+ }
414
493
  continue;
415
494
  }
416
495
  const meta = handleMetaCommand(line, currentLocale, opts.cwd, currentMode);
@@ -432,7 +511,7 @@ async function runRepl(opts) {
432
511
  }
433
512
  else {
434
513
  const creds = resolveCreds();
435
- if (usesCursorSdk(creds)) {
514
+ if (creds && usesCursorSdk(creds)) {
436
515
  currentAgent = await recreateAgentForMode(opts.cwd, currentAgent, currentMode, opts.apiKey, currentLocale);
437
516
  }
438
517
  else if (currentAgent) {
@@ -447,7 +526,8 @@ async function runRepl(opts) {
447
526
  continue;
448
527
  }
449
528
  if (meta?.kind === "resume") {
450
- if (currentMode === "ask" || !usesCursorSdk(resolveCreds())) {
529
+ const resumeCreds = resolveCreds();
530
+ if (currentMode === "ask" || !resumeCreds || !usesCursorSdk(resumeCreds)) {
451
531
  warn(currentLocale === "pt"
452
532
  ? "Sessões guardadas só com provider Cursor."
453
533
  : currentLocale === "zh"
@@ -468,7 +548,8 @@ async function runRepl(opts) {
468
548
  continue;
469
549
  }
470
550
  if (meta?.kind === "save") {
471
- if (!currentAgent || !usesCursorSdk(resolveCreds())) {
551
+ const saveCreds = resolveCreds();
552
+ if (!currentAgent || !saveCreds || !usesCursorSdk(saveCreds)) {
472
553
  warn(currentLocale === "pt"
473
554
  ? "Guardar sessão só com provider Cursor."
474
555
  : currentLocale === "zh"
@@ -492,7 +573,7 @@ async function runRepl(opts) {
492
573
  await executeAskTurn({
493
574
  cwd: opts.cwd,
494
575
  question: line,
495
- offline: opts.askOffline,
576
+ offline: currentOffline || Boolean(opts.askOffline),
496
577
  quiet: true,
497
578
  locale: currentLocale,
498
579
  });
@@ -500,7 +581,12 @@ async function runRepl(opts) {
500
581
  turn++;
501
582
  continue;
502
583
  }
503
- const creds = resolveCreds();
584
+ if (currentOffline || !resolveCreds()) {
585
+ warn(offlineAgentHint(currentLocale));
586
+ turn++;
587
+ continue;
588
+ }
589
+ const creds = currentCreds;
504
590
  if (usesCursorSdk(creds) && !currentAgent) {
505
591
  fail(currentLocale === "pt" ? "Sem agente Cursor activo." : "No active Cursor agent.");
506
592
  continue;
@@ -0,0 +1,5 @@
1
+ export declare function runSetupCommand(opts: {
2
+ locale?: string;
3
+ force?: boolean;
4
+ }): Promise<void>;
5
+ //# sourceMappingURL=setup.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"setup.d.ts","sourceRoot":"","sources":["../../src/commands/setup.ts"],"names":[],"mappings":"AAEA,wBAAsB,eAAe,CAAC,IAAI,EAAE;IAAE,MAAM,CAAC,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,OAAO,CAAA;CAAE,iBAE/E"}
@@ -0,0 +1,4 @@
1
+ import { runSetupWizard } from "../lib/onboarding.js";
2
+ export async function runSetupCommand(opts) {
3
+ await runSetupWizard(opts);
4
+ }
package/dist/index.js CHANGED
@@ -11,6 +11,7 @@ import { runFixCommand } from "./commands/agent.js";
11
11
  import { runHealth } from "./commands/health.js";
12
12
  import { runScan } from "./commands/scan.js";
13
13
  import { runProvidersCommand } from "./commands/providers.js";
14
+ import { runSetupCommand } from "./commands/setup.js";
14
15
  import { runWebhookTest } from "./commands/webhook.js";
15
16
  const SUBCOMMANDS = new Set([
16
17
  "doctor",
@@ -21,6 +22,7 @@ const SUBCOMMANDS = new Set([
21
22
  "fix",
22
23
  "ask",
23
24
  "providers",
25
+ "setup",
24
26
  ]);
25
27
  /** Bare `ekz` or `ekz "natural language task"` → agent. Resolves PT/zh CLI aliases. */
26
28
  function normalizeArgv(argv) {
@@ -134,6 +136,20 @@ program
134
136
  process.exitCode = 1;
135
137
  }
136
138
  });
139
+ program
140
+ .command("setup")
141
+ .alias("configurar")
142
+ .description("First-run setup — language, name, provider or offline")
143
+ .option("-l, --lang <code>", "Skip language pick: pt, en, or zh")
144
+ .action(async (opts) => {
145
+ try {
146
+ await runSetupCommand({ locale: opts.lang });
147
+ }
148
+ catch (err) {
149
+ console.error(err instanceof Error ? err.message : err);
150
+ process.exitCode = 1;
151
+ }
152
+ });
137
153
  program
138
154
  .command("providers")
139
155
  .alias("provedores")
@@ -6,9 +6,9 @@ export type ProvidersCommandLabels = {
6
6
  replAliases: string[];
7
7
  };
8
8
  export declare function providersCommandLabels(locale: EkzLocale): ProvidersCommandLabels;
9
- export declare function providersSetupHint(locale: EkzLocale): string;
9
+ export declare function providersSetupHint(_locale?: EkzLocale): string;
10
10
  export declare function isProvidersMetaCommand(token: string): boolean;
11
- export type CliCommandId = "doctor" | "health" | "scan" | "webhook" | "agent" | "fix" | "ask" | "providers";
11
+ export type CliCommandId = "doctor" | "health" | "scan" | "webhook" | "agent" | "fix" | "ask" | "providers" | "setup";
12
12
  export declare function resolveCliSubcommand(token: string): string;
13
13
  export declare function isKnownSubcommand(token: string): boolean;
14
14
  export declare const SPLASH_TOOLS: Record<EkzLocale, Array<{
@@ -1 +1 @@
1
- {"version":3,"file":"commands-i18n.d.ts","sourceRoot":"","sources":["../../src/lib/commands-i18n.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAE7C,MAAM,MAAM,sBAAsB,GAAG;IACnC,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,MAAM,CAAC;IACb,UAAU,EAAE,MAAM,EAAE,CAAC;IACrB,WAAW,EAAE,MAAM,EAAE,CAAC;CACvB,CAAC;AAEF,wBAAgB,sBAAsB,CAAC,MAAM,EAAE,SAAS,GAAG,sBAAsB,CAuBhF;AAED,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,SAAS,GAAG,MAAM,CAS5D;AAED,wBAAgB,sBAAsB,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAE7D;AAED,MAAM,MAAM,YAAY,GACpB,QAAQ,GACR,QAAQ,GACR,MAAM,GACN,SAAS,GACT,OAAO,GACP,KAAK,GACL,KAAK,GACL,WAAW,CAAC;AAoDhB,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAK1D;AAED,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAGxD;AAED,eAAO,MAAM,YAAY,EAAE,MAAM,CAC/B,SAAS,EACT,KAAK,CAAC;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC,CAgCxC,CAAC;AAEF,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,SAAS,EAAE,IAAI,EAAE;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG,MAAM,CAGlG;AAED,uDAAuD;AACvD,wBAAgB,qBAAqB,CAAC,MAAM,EAAE,SAAS,GAAG,MAAM,CAE/D;AAED,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,SAAS,GAAG,MAAM,EAAE,CAU9D;AAED,wBAAgB,cAAc,CAAC,MAAM,EAAE,SAAS,GAAG,MAAM,CAIxD;AAED,wBAAgB,cAAc,CAAC,MAAM,EAAE,SAAS,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,GAAG,MAAM,CAQxF;AAED,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,SAAS,EAAE,GAAG,EAAE,OAAO,GAAG,UAAU,GAAG,MAAM,CAMvF;AAED,wDAAwD;AACxD,eAAO,MAAM,iBAAiB,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAkCpD,CAAC;AAEF,wBAAgB,sBAAsB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAI5D"}
1
+ {"version":3,"file":"commands-i18n.d.ts","sourceRoot":"","sources":["../../src/lib/commands-i18n.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAG7C,MAAM,MAAM,sBAAsB,GAAG;IACnC,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,MAAM,CAAC;IACb,UAAU,EAAE,MAAM,EAAE,CAAC;IACrB,WAAW,EAAE,MAAM,EAAE,CAAC;CACvB,CAAC;AAEF,wBAAgB,sBAAsB,CAAC,MAAM,EAAE,SAAS,GAAG,sBAAsB,CAuBhF;AAED,wBAAgB,kBAAkB,CAAC,OAAO,CAAC,EAAE,SAAS,GAAG,MAAM,CAE9D;AAED,wBAAgB,sBAAsB,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAE7D;AAED,MAAM,MAAM,YAAY,GACpB,QAAQ,GACR,QAAQ,GACR,MAAM,GACN,SAAS,GACT,OAAO,GACP,KAAK,GACL,KAAK,GACL,WAAW,GACX,OAAO,CAAC;AA2DZ,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAK1D;AAED,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAGxD;AAED,eAAO,MAAM,YAAY,EAAE,MAAM,CAC/B,SAAS,EACT,KAAK,CAAC;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC,CAmCxC,CAAC;AAEF,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,SAAS,EAAE,IAAI,EAAE;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG,MAAM,CAGlG;AAED,uDAAuD;AACvD,wBAAgB,qBAAqB,CAAC,MAAM,EAAE,SAAS,GAAG,MAAM,CAE/D;AAED,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,SAAS,GAAG,MAAM,EAAE,CAgB9D;AAED,wBAAgB,cAAc,CAAC,MAAM,EAAE,SAAS,GAAG,MAAM,CAIxD;AAED,wBAAgB,cAAc,CAAC,MAAM,EAAE,SAAS,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,GAAG,MAAM,CAQxF;AAED,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,SAAS,EAAE,GAAG,EAAE,OAAO,GAAG,UAAU,GAAG,MAAM,CAMvF;AAED,wDAAwD;AACxD,eAAO,MAAM,iBAAiB,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAkCpD,CAAC;AAEF,wBAAgB,sBAAsB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAI5D"}
@@ -1,3 +1,4 @@
1
+ import { formatSetupRequiredMessage } from "./setup-messages.js";
1
2
  export function providersCommandLabels(locale) {
2
3
  if (locale === "pt") {
3
4
  return {
@@ -22,15 +23,8 @@ export function providersCommandLabels(locale) {
22
23
  replAliases: ["/provider"],
23
24
  };
24
25
  }
25
- export function providersSetupHint(locale) {
26
- const labels = providersCommandLabels(locale);
27
- if (locale === "pt") {
28
- return `Nenhum provider activo. Corre \`${labels.cli}\` ou \`${labels.repl}\` e cola a tua API key.`;
29
- }
30
- if (locale === "zh") {
31
- return `未配置 provider。运行 \`${labels.cli}\` 或 \`${labels.repl}\` 并粘贴 API key。`;
32
- }
33
- return `No active provider. Run \`${labels.cli}\` or \`${labels.repl}\` and paste your API key.`;
26
+ export function providersSetupHint(_locale) {
27
+ return formatSetupRequiredMessage();
34
28
  }
35
29
  export function isProvidersMetaCommand(token) {
36
30
  return resolveReplMetaCommand(token) === "providers";
@@ -44,6 +38,7 @@ const EN_SUBCOMMANDS = new Set([
44
38
  "fix",
45
39
  "ask",
46
40
  "providers",
41
+ "setup",
47
42
  ]);
48
43
  /** Map localized CLI tokens (and English) to canonical subcommand ids. */
49
44
  const CLI_ALIASES = {
@@ -76,6 +71,12 @@ const CLI_ALIASES = {
76
71
  供应商: "providers",
77
72
  提供商: "providers",
78
73
  模型: "providers",
74
+ setup: "setup",
75
+ configurar: "setup",
76
+ configuracao: "setup",
77
+ "configuração": "setup",
78
+ 设置: "setup",
79
+ 配置: "setup",
79
80
  诊断: "doctor",
80
81
  凭证: "health",
81
82
  扫描: "scan",
@@ -104,6 +105,7 @@ export const SPLASH_TOOLS = {
104
105
  { label: "agente", hint: "agent" },
105
106
  { label: "perguntar", hint: "ask" },
106
107
  { label: "provedores", hint: "BYOK" },
108
+ { label: "setup", hint: "configurar" },
107
109
  ],
108
110
  en: [
109
111
  { label: "doctor" },
@@ -114,6 +116,7 @@ export const SPLASH_TOOLS = {
114
116
  { label: "agent" },
115
117
  { label: "ask" },
116
118
  { label: "providers", hint: "BYOK" },
119
+ { label: "setup" },
117
120
  ],
118
121
  zh: [
119
122
  { label: "诊断", hint: "doctor" },
@@ -124,6 +127,7 @@ export const SPLASH_TOOLS = {
124
127
  { label: "代理", hint: "agent" },
125
128
  { label: "问答", hint: "ask" },
126
129
  { label: "供应商", hint: "BYOK" },
130
+ { label: "设置", hint: "setup" },
127
131
  ],
128
132
  };
129
133
  export function formatSplashTool(locale, tool) {
@@ -137,14 +141,19 @@ export function formatCliToolsSummary(locale) {
137
141
  }
138
142
  export function splashSessionLines(locale) {
139
143
  const p = providersCommandLabels(locale);
144
+ const setupLine = locale === "pt"
145
+ ? "ekz setup · ekz configurar"
146
+ : locale === "zh"
147
+ ? "ekz setup · ekz 设置"
148
+ : "ekz setup";
140
149
  const providersLine = `${p.repl} · ${p.cli}`;
141
150
  if (locale === "pt") {
142
- return [providersLine, "/guardar <nome>", "/retomar · /retomar 2", "ekz --resume", "ekz --fresh"];
151
+ return [setupLine, providersLine, "/guardar <nome>", "/retomar · /retomar 2", "ekz --resume", "ekz --fresh"];
143
152
  }
144
153
  if (locale === "zh") {
145
- return [providersLine, "/保存 <名称>", "/恢复 · /恢复 2", "ekz --resume", "ekz --fresh"];
154
+ return [setupLine, providersLine, "/保存 <名称>", "/恢复 · /恢复 2", "ekz --resume", "ekz --fresh"];
146
155
  }
147
- return [providersLine, "/save <name>", "/resume · /resume 2", "ekz --resume", "ekz --fresh"];
156
+ return [setupLine, providersLine, "/save <name>", "/resume · /resume 2", "ekz --resume", "ekz --fresh"];
148
157
  }
149
158
  export function splashHelpHint(locale) {
150
159
  if (locale === "pt")
@@ -0,0 +1,19 @@
1
+ import type { EkzLocale } from "./locale.js";
2
+ export type EkzGlobalConfig = {
3
+ locale?: EkzLocale;
4
+ userName?: string;
5
+ /** User chose offline mode (no LLM provider). */
6
+ offline?: boolean;
7
+ setupComplete?: boolean;
8
+ };
9
+ export declare function globalConfigPath(): string;
10
+ export declare function loadGlobalConfig(): EkzGlobalConfig;
11
+ export declare function saveGlobalConfig(patch: Partial<EkzGlobalConfig>): void;
12
+ export declare function getGlobalLocale(): EkzLocale | undefined;
13
+ export declare function getGlobalUserName(): string | undefined;
14
+ export declare function isGlobalOffline(): boolean;
15
+ export declare function setGlobalOffline(offline: boolean): void;
16
+ export declare function clearGlobalOffline(): void;
17
+ export declare function isSetupComplete(): boolean;
18
+ export declare function markSetupComplete(): void;
19
+ //# sourceMappingURL=global-config.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"global-config.d.ts","sourceRoot":"","sources":["../../src/lib/global-config.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAK7C,MAAM,MAAM,eAAe,GAAG;IAC5B,MAAM,CAAC,EAAE,SAAS,CAAC;IACnB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,iDAAiD;IACjD,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,aAAa,CAAC,EAAE,OAAO,CAAC;CACzB,CAAC;AAMF,wBAAgB,gBAAgB,IAAI,MAAM,CAEzC;AAED,wBAAgB,gBAAgB,IAAI,eAAe,CAQlD;AAED,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,OAAO,CAAC,eAAe,CAAC,QAI/D;AAED,wBAAgB,eAAe,IAAI,SAAS,GAAG,SAAS,CAGvD;AAED,wBAAgB,iBAAiB,IAAI,MAAM,GAAG,SAAS,CAEtD;AAED,wBAAgB,eAAe,IAAI,OAAO,CAEzC;AAED,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,OAAO,QAEhD;AAED,wBAAgB,kBAAkB,SAGjC;AAED,wBAAgB,eAAe,IAAI,OAAO,CAEzC;AAED,wBAAgB,iBAAiB,SAEhC"}
@@ -0,0 +1,52 @@
1
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
2
+ import { homedir } from "os";
3
+ import { join } from "path";
4
+ const EKZ_HOME = join(homedir(), ".ekz");
5
+ const CONFIG_PATH = join(EKZ_HOME, "config.json");
6
+ function ensureEkzHome() {
7
+ if (!existsSync(EKZ_HOME))
8
+ mkdirSync(EKZ_HOME, { recursive: true, mode: 0o700 });
9
+ }
10
+ export function globalConfigPath() {
11
+ return CONFIG_PATH;
12
+ }
13
+ export function loadGlobalConfig() {
14
+ ensureEkzHome();
15
+ if (!existsSync(CONFIG_PATH))
16
+ return {};
17
+ try {
18
+ return JSON.parse(readFileSync(CONFIG_PATH, "utf8"));
19
+ }
20
+ catch {
21
+ return {};
22
+ }
23
+ }
24
+ export function saveGlobalConfig(patch) {
25
+ ensureEkzHome();
26
+ const next = { ...loadGlobalConfig(), ...patch };
27
+ writeFileSync(CONFIG_PATH, JSON.stringify(next, null, 2) + "\n", { mode: 0o600 });
28
+ }
29
+ export function getGlobalLocale() {
30
+ const l = loadGlobalConfig().locale;
31
+ return l === "pt" || l === "en" || l === "zh" ? l : undefined;
32
+ }
33
+ export function getGlobalUserName() {
34
+ return loadGlobalConfig().userName?.trim() || undefined;
35
+ }
36
+ export function isGlobalOffline() {
37
+ return loadGlobalConfig().offline === true;
38
+ }
39
+ export function setGlobalOffline(offline) {
40
+ saveGlobalConfig({ offline, setupComplete: true });
41
+ }
42
+ export function clearGlobalOffline() {
43
+ const cfg = loadGlobalConfig();
44
+ if (cfg.offline)
45
+ saveGlobalConfig({ offline: false });
46
+ }
47
+ export function isSetupComplete() {
48
+ return loadGlobalConfig().setupComplete === true;
49
+ }
50
+ export function markSetupComplete() {
51
+ saveGlobalConfig({ setupComplete: true });
52
+ }
@@ -1 +1 @@
1
- {"version":3,"file":"help.d.ts","sourceRoot":"","sources":["../../src/lib/help.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAE7C,OAAO,EAAe,KAAK,OAAO,EAAE,MAAM,WAAW,CAAC;AAGtD,KAAK,WAAW,GAAG;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,EAAE,CAAA;CAAE,CAAC;AAatD,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,SAAS,EAAE,WAAW,CAAC,EAAE,OAAO,GAAG,WAAW,EAAE,CA4HzF;AAED,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,SAAS,EAAE,WAAW,CAAC,EAAE,OAAO,QAezE"}
1
+ {"version":3,"file":"help.d.ts","sourceRoot":"","sources":["../../src/lib/help.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAE7C,OAAO,EAAe,KAAK,OAAO,EAAE,MAAM,WAAW,CAAC;AAGtD,KAAK,WAAW,GAAG;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,EAAE,CAAA;CAAE,CAAC;AAatD,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,SAAS,EAAE,WAAW,CAAC,EAAE,OAAO,GAAG,WAAW,EAAE,CA+HzF;AAED,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,SAAS,EAAE,WAAW,CAAC,EAAE,OAAO,QAezE"}
package/dist/lib/help.js CHANGED
@@ -44,6 +44,7 @@ export function buildHelpSections(locale, currentMode) {
44
44
  ? [
45
45
  "Escreve perguntas ou tarefas em linguagem natural.",
46
46
  `${p.repl} · ${p.cli} (BYOK)`,
47
+ "ekz setup · ekz configurar",
47
48
  "/ajuda · /help",
48
49
  "/modo agente|plan|ask · /mode agent|plan|ask",
49
50
  "/idioma pt · /lang pt|en|zh",
@@ -55,6 +56,7 @@ export function buildHelpSections(locale, currentMode) {
55
56
  ? [
56
57
  "用自然语言提问或描述任务。",
57
58
  `${p.repl} · ${p.cli} (BYOK)`,
59
+ "ekz setup · ekz 设置",
58
60
  "/帮助 · /help · /ajuda",
59
61
  "/模式 代理|plan|ask · /mode agent|plan|ask",
60
62
  "/语言 pt · /lang pt|en|zh",
@@ -65,6 +67,7 @@ export function buildHelpSections(locale, currentMode) {
65
67
  : [
66
68
  "Type questions or tasks in natural language.",
67
69
  `${p.repl} · ${p.cli} (BYOK)`,
70
+ "ekz setup",
68
71
  "/help · /ajuda · /帮助",
69
72
  "/mode agent|plan|ask",
70
73
  "/lang pt · /lang en · /lang zh",
@@ -1 +1 @@
1
- {"version":3,"file":"locale.d.ts","sourceRoot":"","sources":["../../src/lib/locale.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,SAAS,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,CAAC;AAE3C,eAAO,MAAM,aAAa,EAAE,MAAM,CAAC,SAAS,EAAE,MAAM,CAInD,CAAC;AAEF,eAAO,MAAM,YAAY,EAAE,MAAM,CAAC,SAAS,EAAE,MAAM,CAIlD,CAAC;AAEF,wBAAgB,WAAW,CAAC,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAAG,IAAI,CAOnE;AAED,sEAAsE;AACtE,wBAAgB,aAAa,CAAC,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI,EAAE,KAAK,CAAC,EAAE,SAAS,GAAG,IAAI,GAAG,SAAS,CAE3F;AAED,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,SAAS,GAAG,MAAM,CAgB7D;AAED,wBAAgB,SAAS,CAAC,MAAM,EAAE,SAAS;;;;;;;;;;;;;;;;;;;sBAqBnB,MAAM;;;;;;;;;;EA4H7B"}
1
+ {"version":3,"file":"locale.d.ts","sourceRoot":"","sources":["../../src/lib/locale.ts"],"names":[],"mappings":"AAEA,MAAM,MAAM,SAAS,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,CAAC;AAE3C,eAAO,MAAM,aAAa,EAAE,MAAM,CAAC,SAAS,EAAE,MAAM,CAInD,CAAC;AAEF,eAAO,MAAM,YAAY,EAAE,MAAM,CAAC,SAAS,EAAE,MAAM,CAIlD,CAAC;AAEF,wBAAgB,WAAW,CAAC,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAAG,IAAI,CAOnE;AAED,sEAAsE;AACtE,wBAAgB,aAAa,CAAC,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI,EAAE,KAAK,CAAC,EAAE,SAAS,GAAG,IAAI,GAAG,SAAS,CAE3F;AAED,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,SAAS,GAAG,MAAM,CAgB7D;AAED,wBAAgB,SAAS,CAAC,MAAM,EAAE,SAAS;;;;;;;;;;;;;;;;;;;sBAqBnB,MAAM;;;;;;;;;;EA4H7B"}
@@ -1,3 +1,4 @@
1
+ import { getGlobalLocale } from "./global-config.js";
1
2
  export const LOCALE_LABELS = {
2
3
  pt: "Português (Angola)",
3
4
  en: "English",
@@ -22,7 +23,7 @@ export function parseLocale(input) {
22
23
  }
23
24
  /** Default: Portuguese (Angola). Override with EKZ_LANG or --lang. */
24
25
  export function resolveLocale(explicit, saved) {
25
- return parseLocale(explicit) ?? parseLocale(process.env.EKZ_LANG) ?? saved ?? "pt";
26
+ return parseLocale(explicit) ?? parseLocale(process.env.EKZ_LANG) ?? saved ?? getGlobalLocale() ?? "pt";
26
27
  }
27
28
  export function languageInstruction(locale) {
28
29
  if (locale === "pt") {
@@ -0,0 +1,20 @@
1
+ import { type EkzLocale } from "./locale.js";
2
+ import { type ResolvedCredentials } from "./providers/credentials.js";
3
+ export type AgentReadyState = {
4
+ offline: boolean;
5
+ creds: ResolvedCredentials | null;
6
+ };
7
+ export declare function offlineModelLabel(locale: EkzLocale): string;
8
+ /** Full first-run wizard: language → name → provider/offline. */
9
+ export declare function runSetupWizard(opts?: {
10
+ locale?: string;
11
+ force?: boolean;
12
+ }): Promise<void>;
13
+ /** Ensures the agent can start — provider, offline, or throws setup hint. */
14
+ export declare function ensureAgentReady(options: {
15
+ locale: EkzLocale;
16
+ offlineFlag?: boolean;
17
+ apiKey?: string;
18
+ }): Promise<AgentReadyState>;
19
+ export declare function offlineAgentHint(locale: EkzLocale): string;
20
+ //# sourceMappingURL=onboarding.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"onboarding.d.ts","sourceRoot":"","sources":["../../src/lib/onboarding.ts"],"names":[],"mappings":"AAUA,OAAO,EAAE,KAAK,SAAS,EAAe,MAAM,aAAa,CAAC;AAG1D,OAAO,EAIL,KAAK,mBAAmB,EACzB,MAAM,4BAA4B,CAAC;AAyBpC,MAAM,MAAM,eAAe,GAAG;IAC5B,OAAO,EAAE,OAAO,CAAC;IACjB,KAAK,EAAE,mBAAmB,GAAG,IAAI,CAAC;CACnC,CAAC;AAEF,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,SAAS,GAAG,MAAM,CAI3D;AAoFD,iEAAiE;AACjE,wBAAsB,cAAc,CAAC,IAAI,CAAC,EAAE;IAAE,MAAM,CAAC,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,OAAO,CAAA;CAAE,iBA6B/E;AAED,6EAA6E;AAC7E,wBAAsB,gBAAgB,CAAC,OAAO,EAAE;IAC9C,MAAM,EAAE,SAAS,CAAC;IAClB,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB,GAAG,OAAO,CAAC,eAAe,CAAC,CAgB3B;AAED,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,SAAS,GAAG,MAAM,CAQ1D"}
@@ -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
+ }
@@ -1,4 +1,5 @@
1
1
  import { type ProviderDefinition, type ProviderId } from "./catalog.js";
2
+ import type { EkzLocale } from "../locale.js";
2
3
  import { type StoredProvider } from "./store.js";
3
4
  export type ResolvedCredentials = {
4
5
  id: ProviderId;
@@ -23,4 +24,5 @@ export declare function getActiveProviderId(): ProviderId | undefined;
23
24
  export declare function resolveActiveCredentials(): ResolvedCredentials | null;
24
25
  export declare function requireActiveCredentials(locale?: "pt" | "en" | "zh"): ResolvedCredentials;
25
26
  export declare function isCursorProvider(creds: ResolvedCredentials): boolean;
27
+ export declare function resolveCredentialsWithOverride(override?: string, locale?: EkzLocale): ResolvedCredentials;
26
28
  //# sourceMappingURL=credentials.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"credentials.d.ts","sourceRoot":"","sources":["../../../src/lib/providers/credentials.ts"],"names":[],"mappings":"AAAA,OAAO,EAAoB,KAAK,kBAAkB,EAAE,KAAK,UAAU,EAAE,MAAM,cAAc,CAAC;AAE1F,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"}
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"}
@@ -1,5 +1,6 @@
1
- import { PROVIDER_CATALOG } from "./catalog.js";
2
- import { providersSetupHint } from "../commands-i18n.js";
1
+ import { PROVIDER_CATALOG, getProviderDefinition } from "./catalog.js";
2
+ import { formatSetupRequiredMessage } from "../setup-messages.js";
3
+ import { resolveComposerModel } from "../composer-model.js";
3
4
  import { loadProvidersFile, maskApiKey, resolveBaseUrl, resolveModel, } from "./store.js";
4
5
  export class ProviderSetupError extends Error {
5
6
  constructor(message) {
@@ -47,8 +48,23 @@ export function requireActiveCredentials(locale = "en") {
47
48
  const creds = resolveActiveCredentials();
48
49
  if (creds)
49
50
  return creds;
50
- throw new ProviderSetupError(providersSetupHint(locale));
51
+ throw new ProviderSetupError(formatSetupRequiredMessage());
51
52
  }
52
53
  export function isCursorProvider(creds) {
53
54
  return creds.definition.kind === "cursor";
54
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
+ }
@@ -1,8 +1,8 @@
1
- import { type SDKAgent } from "@cursor/sdk";
2
1
  import { type EkzLocale } from "../locale.js";
2
+ import { type SDKAgent } from "./cursor-sdk.js";
3
3
  import { type ResolvedCredentials } from "./credentials.js";
4
4
  declare function disposeAgent(agent: SDKAgent): Promise<void>;
5
- declare function streamRun(agent: SDKAgent, prompt: string): Promise<void>;
5
+ declare function streamRun(agent: SDKAgent, prompt: string, locale: EkzLocale): Promise<void>;
6
6
  export declare function executeCursorAskTurn(options: {
7
7
  cwd: string;
8
8
  question: string;
@@ -1 +1 @@
1
- {"version":3,"file":"cursor-runner.d.ts","sourceRoot":"","sources":["../../../src/lib/providers/cursor-runner.ts"],"names":[],"mappings":"AACA,OAAO,EAA2C,KAAK,QAAQ,EAAE,MAAM,aAAa,CAAC;AAIrF,OAAO,EAAE,KAAK,SAAS,EAAE,MAAM,cAAc,CAAC;AAK9C,OAAO,EAAE,KAAK,mBAAmB,EAAE,MAAM,kBAAkB,CAAC;AAE5D,iBAAe,YAAY,CAAC,KAAK,EAAE,QAAQ,iBAI1C;AAED,iBAAe,SAAS,CAAC,KAAK,EAAE,QAAQ,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAkCvE;AAED,wBAAsB,oBAAoB,CAAC,OAAO,EAAE;IAClD,GAAG,EAAE,MAAM,CAAC;IACZ,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,mBAAmB,CAAC;IAC3B,MAAM,EAAE,SAAS,CAAC;CACnB,GAAG,OAAO,CAAC,OAAO,CAAC,CAkCnB;AAED,OAAO,EAAE,YAAY,EAAE,SAAS,EAAE,CAAC"}
1
+ {"version":3,"file":"cursor-runner.d.ts","sourceRoot":"","sources":["../../../src/lib/providers/cursor-runner.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,KAAK,SAAS,EAAuB,MAAM,cAAc,CAAC;AAInE,OAAO,EAAiB,KAAK,QAAQ,EAAE,MAAM,iBAAiB,CAAC;AAC/D,OAAO,EAAE,KAAK,mBAAmB,EAAE,MAAM,kBAAkB,CAAC;AAE5D,iBAAe,YAAY,CAAC,KAAK,EAAE,QAAQ,iBAI1C;AAED,iBAAe,SAAS,CAAC,KAAK,EAAE,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,SAAS,GAAG,OAAO,CAAC,IAAI,CAAC,CAmC1F;AAED,wBAAsB,oBAAoB,CAAC,OAAO,EAAE;IAClD,GAAG,EAAE,MAAM,CAAC;IACZ,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,mBAAmB,CAAC;IAC3B,MAAM,EAAE,SAAS,CAAC;CACnB,GAAG,OAAO,CAAC,OAAO,CAAC,CAmCnB;AAED,OAAO,EAAE,YAAY,EAAE,SAAS,EAAE,CAAC"}
@@ -1,11 +1,11 @@
1
1
  import { resolve } from "path";
2
- import { Agent, AgentBusyError, CursorAgentError } from "@cursor/sdk";
3
2
  import { buildBootstrapContext } from "../context.js";
4
3
  import { resolveComposerModel } from "../composer-model.js";
5
- import { fail } from "../output.js";
6
- import { toolDone, toolError, toolRunning } from "../theme.js";
7
4
  import { languageInstruction } from "../locale.js";
5
+ import { fail } from "../output.js";
8
6
  import { agentScopeBlock } from "../scope.js";
7
+ import { toolDone, toolError, toolRunning } from "../theme.js";
8
+ import { loadCursorSdk } from "./cursor-sdk.js";
9
9
  async function disposeAgent(agent) {
10
10
  const disposer = agent[Symbol.asyncDispose];
11
11
  if (typeof disposer === "function")
@@ -13,7 +13,8 @@ async function disposeAgent(agent) {
13
13
  else
14
14
  agent.close();
15
15
  }
16
- async function streamRun(agent, prompt) {
16
+ async function streamRun(agent, prompt, locale) {
17
+ const { AgentBusyError, CursorAgentError } = await loadCursorSdk(locale);
17
18
  const sendOptions = {
18
19
  onDelta: ({ update }) => {
19
20
  if (update.type === "text-delta" && update.text)
@@ -52,6 +53,7 @@ export async function executeCursorAskTurn(options) {
52
53
  const cwd = resolve(options.cwd);
53
54
  const ctx = await buildBootstrapContext(cwd);
54
55
  const model = resolveComposerModel(process.env, options.creds.model);
56
+ const { Agent } = await loadCursorSdk(options.locale);
55
57
  const prompt = `${agentScopeBlock(options.locale)}
56
58
 
57
59
  ${languageInstruction(options.locale)}
@@ -71,7 +73,7 @@ ${options.question}`;
71
73
  model: { id: model.id, params: model.params },
72
74
  local: { cwd, settingSources: ["project"] },
73
75
  });
74
- await streamRun(agent, prompt);
76
+ await streamRun(agent, prompt, options.locale);
75
77
  console.log("");
76
78
  return true;
77
79
  }
@@ -0,0 +1,8 @@
1
+ import type { EkzLocale } from "../locale.js";
2
+ export declare class CursorSdkMissingError extends Error {
3
+ constructor(locale?: EkzLocale);
4
+ }
5
+ type CursorSdkModule = typeof import("@cursor/sdk");
6
+ export declare function loadCursorSdk(locale?: EkzLocale): Promise<CursorSdkModule>;
7
+ export type { SDKAgent } from "@cursor/sdk";
8
+ //# sourceMappingURL=cursor-sdk.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cursor-sdk.d.ts","sourceRoot":"","sources":["../../../src/lib/providers/cursor-sdk.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAE9C,qBAAa,qBAAsB,SAAQ,KAAK;gBAClC,MAAM,GAAE,SAAgB;CAWrC;AAED,KAAK,eAAe,GAAG,cAAc,aAAa,CAAC,CAAC;AAIpD,wBAAsB,aAAa,CAAC,MAAM,GAAE,SAAgB,GAAG,OAAO,CAAC,eAAe,CAAC,CAQtF;AAED,YAAY,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC"}
@@ -0,0 +1,25 @@
1
+ import { providersCommandLabels } from "../commands-i18n.js";
2
+ export class CursorSdkMissingError extends Error {
3
+ constructor(locale = "en") {
4
+ const p = providersCommandLabels(locale);
5
+ const msg = locale === "pt"
6
+ ? `Provider Cursor requer @cursor/sdk. Instala: npm install -g @cursor/sdk\nOu usa outro provider em ${p.repl}.`
7
+ : locale === "zh"
8
+ ? `Cursor provider 需要 @cursor/sdk。安装: npm install -g @cursor/sdk\n或在 ${p.repl} 选择其他 provider。`
9
+ : `Cursor provider requires @cursor/sdk. Run: npm install -g @cursor/sdk\nOr pick another provider via ${p.repl}.`;
10
+ super(msg);
11
+ this.name = "CursorSdkMissingError";
12
+ }
13
+ }
14
+ let cached = null;
15
+ export async function loadCursorSdk(locale = "en") {
16
+ if (cached)
17
+ return cached;
18
+ try {
19
+ cached = await import("@cursor/sdk");
20
+ return cached;
21
+ }
22
+ catch {
23
+ throw new CursorSdkMissingError(locale);
24
+ }
25
+ }
@@ -1 +1 @@
1
- {"version":3,"file":"store.d.ts","sourceRoot":"","sources":["../../../src/lib/providers/store.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,KAAK,UAAU,EAAyB,MAAM,cAAc,CAAC;AAEtE,MAAM,MAAM,cAAc,GAAG;IAC3B,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB,CAAC;AAEF,MAAM,MAAM,aAAa,GAAG;IAC1B,MAAM,CAAC,EAAE,UAAU,CAAC;IACpB,SAAS,EAAE,OAAO,CAAC,MAAM,CAAC,UAAU,EAAE,cAAc,CAAC,CAAC,CAAC;CACxD,CAAC;AAWF,wBAAgB,iBAAiB,IAAI,MAAM,CAE1C;AAED,wBAAgB,iBAAiB,IAAI,aAAa,CAcjD;AAED,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,aAAa,QAQpD;AAED,wBAAgB,UAAU,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAI9C;AAED,wBAAgB,iBAAiB,CAAC,EAAE,EAAE,UAAU,GAAG,cAAc,GAAG,SAAS,CAE5E;AAED,wBAAgB,iBAAiB,CAAC,EAAE,EAAE,UAAU,EAAE,KAAK,EAAE,cAAc,QAKtE;AAED,wBAAgB,oBAAoB,CAAC,EAAE,EAAE,UAAU,QAOlD;AAED,wBAAgB,iBAAiB,CAAC,EAAE,EAAE,UAAU,QAO/C;AAED,wBAAgB,YAAY,CAAC,EAAE,EAAE,UAAU,EAAE,MAAM,CAAC,EAAE,cAAc,GAAG,MAAM,CAG5E;AAED,wBAAgB,cAAc,CAAC,EAAE,EAAE,UAAU,EAAE,MAAM,CAAC,EAAE,cAAc,GAAG,MAAM,GAAG,SAAS,CAG1F"}
1
+ {"version":3,"file":"store.d.ts","sourceRoot":"","sources":["../../../src/lib/providers/store.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,KAAK,UAAU,EAAyB,MAAM,cAAc,CAAC;AAGtE,MAAM,MAAM,cAAc,GAAG;IAC3B,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB,CAAC;AAEF,MAAM,MAAM,aAAa,GAAG;IAC1B,MAAM,CAAC,EAAE,UAAU,CAAC;IACpB,SAAS,EAAE,OAAO,CAAC,MAAM,CAAC,UAAU,EAAE,cAAc,CAAC,CAAC,CAAC;CACxD,CAAC;AAWF,wBAAgB,iBAAiB,IAAI,MAAM,CAE1C;AAED,wBAAgB,iBAAiB,IAAI,aAAa,CAcjD;AAED,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,aAAa,QAQpD;AAED,wBAAgB,UAAU,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAI9C;AAED,wBAAgB,iBAAiB,CAAC,EAAE,EAAE,UAAU,GAAG,cAAc,GAAG,SAAS,CAE5E;AAED,wBAAgB,iBAAiB,CAAC,EAAE,EAAE,UAAU,EAAE,KAAK,EAAE,cAAc,QAOtE;AAED,wBAAgB,oBAAoB,CAAC,EAAE,EAAE,UAAU,QAOlD;AAED,wBAAgB,iBAAiB,CAAC,EAAE,EAAE,UAAU,QAO/C;AAED,wBAAgB,YAAY,CAAC,EAAE,EAAE,UAAU,EAAE,MAAM,CAAC,EAAE,cAAc,GAAG,MAAM,CAG5E;AAED,wBAAgB,cAAc,CAAC,EAAE,EAAE,UAAU,EAAE,MAAM,CAAC,EAAE,cAAc,GAAG,MAAM,GAAG,SAAS,CAG1F"}
@@ -2,6 +2,7 @@ import { chmodSync, existsSync, mkdirSync, readFileSync, writeFileSync } from "f
2
2
  import { homedir } from "os";
3
3
  import { join } from "path";
4
4
  import { getProviderDefinition } from "./catalog.js";
5
+ import { clearGlobalOffline, markSetupComplete } from "../global-config.js";
5
6
  const EKZ_HOME = join(homedir(), ".ekz");
6
7
  const PROVIDERS_PATH = join(EKZ_HOME, "providers.json");
7
8
  function ensureEkzHome() {
@@ -53,6 +54,8 @@ export function setStoredProvider(id, entry) {
53
54
  if (!file.active)
54
55
  file.active = id;
55
56
  saveProvidersFile(file);
57
+ clearGlobalOffline();
58
+ markSetupComplete();
56
59
  }
57
60
  export function removeStoredProvider(id) {
58
61
  const file = loadProvidersFile();
@@ -0,0 +1,4 @@
1
+ /** Shown when agent needs provider but setup was not run (all locales). */
2
+ export declare function formatSetupRequiredMessage(): string;
3
+ export declare function providersSetupHint(_locale?: string): string;
4
+ //# sourceMappingURL=setup-messages.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"setup-messages.d.ts","sourceRoot":"","sources":["../../src/lib/setup-messages.ts"],"names":[],"mappings":"AAAA,2EAA2E;AAC3E,wBAAgB,0BAA0B,IAAI,MAAM,CAQnD;AAED,wBAAgB,kBAAkB,CAAC,OAAO,CAAC,EAAE,MAAM,GAAG,MAAM,CAE3D"}
@@ -0,0 +1,13 @@
1
+ /** Shown when agent needs provider but setup was not run (all locales). */
2
+ export function formatSetupRequiredMessage() {
3
+ return [
4
+ "Setup required · Configuração necessária · 需要设置",
5
+ "",
6
+ " pt: Corre `ekz setup` ou `ekz --offline`",
7
+ " en: Run `ekz setup` or `ekz --offline`",
8
+ " zh: 运行 `ekz setup` 或 `ekz --offline`",
9
+ ].join("\n");
10
+ }
11
+ export function providersSetupHint(_locale) {
12
+ return formatSetupRequiredMessage();
13
+ }
@@ -1 +1 @@
1
- {"version":3,"file":"splash.d.ts","sourceRoot":"","sources":["../../../src/lib/ui/splash.ts"],"names":[],"mappings":"AAaA,OAAO,EAAE,KAAK,SAAS,EAAa,MAAM,cAAc,CAAC;AACzD,OAAO,EAAe,KAAK,OAAO,EAAE,MAAM,YAAY,CAAC;AAMvD,MAAM,MAAM,aAAa,GAAG;IAC1B,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,SAAS,CAAC;IAClB,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB,CAAC;AA2GF,wBAAgB,YAAY,CAAC,IAAI,EAAE,aAAa,GAAG,IAAI,CAQtD;AAED,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,SAAS,GAAG,IAAI,CAG1D;AAED,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,SAAS,EAAE,KAAK,SAAI,GAAG,MAAM,CAIrE"}
1
+ {"version":3,"file":"splash.d.ts","sourceRoot":"","sources":["../../../src/lib/ui/splash.ts"],"names":[],"mappings":"AAcA,OAAO,EAAE,KAAK,SAAS,EAAa,MAAM,cAAc,CAAC;AACzD,OAAO,EAAe,KAAK,OAAO,EAAE,MAAM,YAAY,CAAC;AAMvD,MAAM,MAAM,aAAa,GAAG;IAC1B,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,SAAS,CAAC;IAClB,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB,CAAC;AA6GF,wBAAgB,YAAY,CAAC,IAAI,EAAE,aAAa,GAAG,IAAI,CAQtD;AAED,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,SAAS,GAAG,IAAI,CAG1D;AAED,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,SAAS,EAAE,KAAK,SAAI,GAAG,MAAM,CAIrE"}
@@ -4,6 +4,7 @@ import { readFileSync } from "fs";
4
4
  import { dirname, join } from "path";
5
5
  import { fileURLToPath } from "url";
6
6
  import { formatSplashTool, splashHelpHint, splashSectionTitle, SPLASH_TOOLS, splashSessionLines, welcomeMessage, } from "../commands-i18n.js";
7
+ import { getGlobalUserName } from "../global-config.js";
7
8
  import { uiStrings } from "../locale.js";
8
9
  import { MODE_LABELS } from "../mode.js";
9
10
  import { detectShellEnvironment, shellLabel } from "../shell.js";
@@ -24,6 +25,9 @@ function packageVersion() {
24
25
  }
25
26
  }
26
27
  function displayName() {
28
+ const fromConfig = getGlobalUserName();
29
+ if (fromConfig)
30
+ return fromConfig.split(/\s+/)[0] ?? fromConfig;
27
31
  const fromEnv = process.env.EKZ_USER_NAME?.trim();
28
32
  if (fromEnv)
29
33
  return fromEnv.split(/\s+/)[0] ?? fromEnv;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ekzs/cli",
3
- "version": "0.3.0",
3
+ "version": "0.3.3",
4
4
  "description": "CLI agent for e-Kwanza v2.4 — health checks, code scan, webhook tests, AI assistance",
5
5
  "type": "module",
6
6
  "bin": {
@@ -17,12 +17,20 @@
17
17
  "prepublishOnly": "npm run build"
18
18
  },
19
19
  "dependencies": {
20
- "@cursor/sdk": "^1.0.13",
21
20
  "@ekzs/connect": "^0.1.0",
22
21
  "commander": "^12.1.0",
23
22
  "dotenv": "^16.4.7"
24
23
  },
24
+ "peerDependencies": {
25
+ "@cursor/sdk": "^1.0.13"
26
+ },
27
+ "peerDependenciesMeta": {
28
+ "@cursor/sdk": {
29
+ "optional": true
30
+ }
31
+ },
25
32
  "devDependencies": {
33
+ "@cursor/sdk": "^1.0.13",
26
34
  "@types/node": "^20",
27
35
  "typescript": "^5"
28
36
  },