@ekzs/cli 0.3.0 → 0.3.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (49) 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 +18 -1
  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/catalog.d.ts +5 -0
  24. package/dist/lib/providers/catalog.d.ts.map +1 -1
  25. package/dist/lib/providers/catalog.js +34 -1
  26. package/dist/lib/providers/credentials.d.ts +2 -0
  27. package/dist/lib/providers/credentials.d.ts.map +1 -1
  28. package/dist/lib/providers/credentials.js +19 -3
  29. package/dist/lib/providers/cursor-runner.d.ts +2 -2
  30. package/dist/lib/providers/cursor-runner.d.ts.map +1 -1
  31. package/dist/lib/providers/cursor-runner.js +7 -5
  32. package/dist/lib/providers/cursor-sdk.d.ts +8 -0
  33. package/dist/lib/providers/cursor-sdk.d.ts.map +1 -0
  34. package/dist/lib/providers/cursor-sdk.js +25 -0
  35. package/dist/lib/providers/store.d.ts +1 -1
  36. package/dist/lib/providers/store.d.ts.map +1 -1
  37. package/dist/lib/providers/store.js +13 -7
  38. package/dist/lib/providers/ui.d.ts +2 -0
  39. package/dist/lib/providers/ui.d.ts.map +1 -1
  40. package/dist/lib/providers/ui.js +60 -24
  41. package/dist/lib/setup-messages.d.ts +4 -0
  42. package/dist/lib/setup-messages.d.ts.map +1 -0
  43. package/dist/lib/setup-messages.js +13 -0
  44. package/dist/lib/ui/splash.d.ts.map +1 -1
  45. package/dist/lib/ui/splash.js +4 -0
  46. package/dist/lib/version.d.ts +2 -0
  47. package/dist/lib/version.d.ts.map +1 -0
  48. package/dist/lib/version.js +13 -0
  49. 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,7 +11,9 @@ 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";
16
+ import { packageVersion } from "./lib/version.js";
15
17
  const SUBCOMMANDS = new Set([
16
18
  "doctor",
17
19
  "health",
@@ -21,6 +23,7 @@ const SUBCOMMANDS = new Set([
21
23
  "fix",
22
24
  "ask",
23
25
  "providers",
26
+ "setup",
24
27
  ]);
25
28
  /** Bare `ekz` or `ekz "natural language task"` → agent. Resolves PT/zh CLI aliases. */
26
29
  function normalizeArgv(argv) {
@@ -38,7 +41,7 @@ const program = new Command();
38
41
  program
39
42
  .name("ekz")
40
43
  .description("EKZ CONNECT — e-Kwanza v2.4 coding agent by Alberto Moisés")
41
- .version("0.3.0");
44
+ .version(packageVersion());
42
45
  program
43
46
  .command("doctor")
44
47
  .description("Validate .env and run live credential checks (free)")
@@ -134,6 +137,20 @@ program
134
137
  process.exitCode = 1;
135
138
  }
136
139
  });
140
+ program
141
+ .command("setup")
142
+ .alias("configurar")
143
+ .description("First-run setup — language, name, provider or offline")
144
+ .option("-l, --lang <code>", "Skip language pick: pt, en, or zh")
145
+ .action(async (opts) => {
146
+ try {
147
+ await runSetupCommand({ locale: opts.lang });
148
+ }
149
+ catch (err) {
150
+ console.error(err instanceof Error ? err.message : err);
151
+ process.exitCode = 1;
152
+ }
153
+ });
137
154
  program
138
155
  .command("providers")
139
156
  .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"}