apex-dev 3.10.21 → 3.10.25

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 (4) hide show
  1. package/cli.js +121 -13
  2. package/dist/apex-dev +45094 -41465
  3. package/dist/index.js +312 -121
  4. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -104,6 +104,7 @@ var require_store = __commonJS((exports, module2) => {
104
104
  messages: state.messages.map((m2) => m2.id === id ? { ...m2, ...updates } : m2)
105
105
  };
106
106
  notify();
107
+ return id;
107
108
  }
108
109
  function toggleMessageExpanded(id) {
109
110
  state = {
@@ -304,6 +305,90 @@ var require_config = __commonJS((exports, module) => {
304
305
  const OpenAI = __require("openai");
305
306
  const fs = __require("fs");
306
307
  const path = __require("path");
308
+ const os = __require("os");
309
+ const CONFIG_PATH = path.join(os.homedir(), ".apex-dev", "config.json");
310
+ function readSavedApiKeys() {
311
+ try {
312
+ if (!fs.existsSync(CONFIG_PATH))
313
+ return {};
314
+ const parsed = JSON.parse(fs.readFileSync(CONFIG_PATH, "utf-8"));
315
+ return parsed && typeof parsed === "object" ? parsed : {};
316
+ } catch {
317
+ return {};
318
+ }
319
+ }
320
+ function writeSavedApiKeys(config) {
321
+ const dir = path.dirname(CONFIG_PATH);
322
+ if (!fs.existsSync(dir)) {
323
+ fs.mkdirSync(dir, { recursive: true });
324
+ }
325
+ fs.writeFileSync(CONFIG_PATH, JSON.stringify(config, null, 2));
326
+ fs.chmodSync(CONFIG_PATH, 384);
327
+ }
328
+ function getSavedApiKey(providerKey) {
329
+ const config = readSavedApiKeys();
330
+ return config[providerKey] || "";
331
+ }
332
+ function getProviderLoginState(providerKey) {
333
+ const provider = PROVIDERS[providerKey];
334
+ if (!provider)
335
+ return "empty";
336
+ if (process.env[provider.envKey])
337
+ return "logged-in";
338
+ if (getSavedApiKey(providerKey))
339
+ return "saved";
340
+ return "empty";
341
+ }
342
+ function updateSavedApiKey(providerKey, apiKey) {
343
+ const config = readSavedApiKeys();
344
+ if (apiKey) {
345
+ config[providerKey] = apiKey;
346
+ } else {
347
+ delete config[providerKey];
348
+ }
349
+ if (Object.keys(config).length === 0) {
350
+ try {
351
+ fs.unlinkSync(CONFIG_PATH);
352
+ } catch {}
353
+ return config;
354
+ }
355
+ writeSavedApiKeys(config);
356
+ return config;
357
+ }
358
+ function clearSavedApiKey(providerKey) {
359
+ return updateSavedApiKey(providerKey, "");
360
+ }
361
+ function loginProvider(providerKey, apiKey) {
362
+ updateSavedApiKey(providerKey, apiKey);
363
+ setProvider(providerKey, apiKey);
364
+ return { providerKey, apiKey };
365
+ }
366
+ function logoutProvider(providerKey) {
367
+ clearSavedApiKey(providerKey);
368
+ const provider = PROVIDERS[providerKey];
369
+ if (provider) {
370
+ delete process.env[provider.envKey];
371
+ if (currentProvider === providerKey) {
372
+ setProvider(providerKey, "");
373
+ }
374
+ }
375
+ const remaining = getFirstSavedProvider();
376
+ if (remaining) {
377
+ currentProvider = remaining.providerKey;
378
+ process.env[remaining.provider.envKey] = remaining.apiKey;
379
+ setProvider(remaining.providerKey, remaining.apiKey);
380
+ }
381
+ return remaining;
382
+ }
383
+ function getFirstSavedProvider() {
384
+ const config = readSavedApiKeys();
385
+ for (const [providerKey, provider] of Object.entries(PROVIDERS)) {
386
+ if (config[providerKey]) {
387
+ return { providerKey, apiKey: config[providerKey], provider };
388
+ }
389
+ }
390
+ return null;
391
+ }
307
392
  const PROVIDERS = {
308
393
  fireworks: {
309
394
  label: "Fireworks AI",
@@ -412,6 +497,16 @@ var require_config = __commonJS((exports, module) => {
412
497
  return "fireworks";
413
498
  }
414
499
  let currentProvider = detectInitialProvider();
500
+ try {
501
+ const hasEnvKey = Object.values(PROVIDERS).some((p) => process.env[p.envKey]);
502
+ if (!hasEnvKey) {
503
+ const saved = getFirstSavedProvider();
504
+ if (saved) {
505
+ currentProvider = saved.providerKey;
506
+ process.env[saved.provider.envKey] = saved.apiKey;
507
+ }
508
+ }
509
+ } catch {}
415
510
  const currentModels = Object.assign({}, PROVIDERS[currentProvider].models);
416
511
  const MAX_TOOL_ITERATIONS = 50;
417
512
  const MAX_OUTPUT_LEN = 12000;
@@ -452,7 +547,7 @@ var require_config = __commonJS((exports, module) => {
452
547
 
453
548
  - **Idiomatic Changes:** When editing, understand the local context (imports, functions/classes) to ensure your changes integrate naturally and idiomatically.
454
549
 
455
- - **Simplicity & Minimalism:** You should make as few changes as possible to the codebase to address the user's request. Only do what the user has asked for and no more. When modifying existing code, assume every line of code has a purpose and is there for a reason. Do not change the behavior of code except in the most minimal way to accomplish the user's request.
550
+ - **Simplicity & Minimalism:** You should make as few changes as possible to the codebase to address the user's request. Only do what the user has asked and no more. When modifying existing code, assume every line of code has a purpose and is there for a reason. Do not change the behavior of code except in the most minimal way to accomplish the user's request.
456
551
 
457
552
  - **Code Reuse:** Always reuse helper functions, components, classes, etc., whenever possible! Don't reimplement what already exists elsewhere in the codebase.
458
553
 
@@ -488,7 +583,7 @@ var require_config = __commonJS((exports, module) => {
488
583
 
489
584
  Use the spawn_agents tool to spawn specialized agents to help you complete the user's request.
490
585
 
491
- - **Spawn multiple agents in parallel:** This increases the speed of your response **and** allows you to be more comprehensive by spawning more total agents to synthesize the best response.
586
+ - **Spawn multiple agents in parallel:** This increases the speed of your response **and** allows you to be more comprehensive by spawning more total agents to synthesize the best solution.
492
587
 
493
588
  - **Sequence agents properly:** Keep in mind dependencies when spawning different agents. Don't spawn agents in parallel that depend on each other.
494
589
 
@@ -868,25 +963,6 @@ The user asks you to implement a new feature. You respond in multiple steps:
868
963
  deepseek: { model: "deepseek/deepseek-chat-v3", temperature: 0.1, maxTokens: 8192 },
869
964
  minimax: { model: "minimax/minimax-01", temperature: 0.1, maxTokens: 8192 }
870
965
  };
871
- const os = __require("os");
872
- let savedProvider = null;
873
- try {
874
- const configPath = path.join(os.homedir(), ".apex-dev", "config.json");
875
- if (fs.existsSync(configPath)) {
876
- const savedConfig = JSON.parse(fs.readFileSync(configPath, "utf-8"));
877
- const hasEnvKey = Object.values(PROVIDERS).some((p) => process.env[p.envKey]);
878
- if (!hasEnvKey) {
879
- for (const [providerKey, provider] of Object.entries(PROVIDERS)) {
880
- if (savedConfig[providerKey]) {
881
- currentProvider = providerKey;
882
- process.env[provider.envKey] = savedConfig[providerKey];
883
- savedProvider = providerKey;
884
- break;
885
- }
886
- }
887
- }
888
- }
889
- } catch (e) {}
890
966
  const _initialProvider = PROVIDERS[currentProvider];
891
967
  const _initialKey = process.env[_initialProvider.envKey] || "no-key";
892
968
  let _internalClient = new OpenAI({
@@ -1015,6 +1091,15 @@ The user asks you to implement a new feature. You respond in multiple steps:
1015
1091
  },
1016
1092
  detectInitialProvider,
1017
1093
  setProvider,
1094
+ readSavedApiKeys,
1095
+ writeSavedApiKeys,
1096
+ getSavedApiKey,
1097
+ getProviderLoginState,
1098
+ updateSavedApiKey,
1099
+ clearSavedApiKey,
1100
+ loginProvider,
1101
+ logoutProvider,
1102
+ getFirstSavedProvider,
1018
1103
  agentConfigs,
1019
1104
  agentModes,
1020
1105
  codeEditorModelVariants,
@@ -3300,7 +3385,7 @@ var require_commands = __commonJS((exports, module2) => {
3300
3385
  var fs2 = __require2("fs");
3301
3386
  var path2 = __require2("path");
3302
3387
  var { execSync } = __require2("child_process");
3303
- var { PROJECT_ROOT, session, resolvePath } = require_config();
3388
+ var { PROJECT_ROOT, session, resolvePath, logoutProvider, getProviderLoginState } = require_config();
3304
3389
  var { executeTool } = require_toolExecutors();
3305
3390
  var store = require_store();
3306
3391
  async function handleSlashCommand(input) {
@@ -3310,6 +3395,20 @@ var require_commands = __commonJS((exports, module2) => {
3310
3395
  case "/help":
3311
3396
  store.setState({ showHelp: true });
3312
3397
  break;
3398
+ case "/login":
3399
+ case "/provider":
3400
+ store.setState({ showHelp: false, needsConfig: true });
3401
+ break;
3402
+ case "/logout": {
3403
+ const provider = store.getSnapshot().provider;
3404
+ if (getProviderLoginState(provider) === "logged-in") {
3405
+ logoutProvider(provider);
3406
+ store.setState({ showHelp: false, needsConfig: true, apiKey: "" });
3407
+ } else {
3408
+ store.setState({ showHelp: false, needsConfig: true });
3409
+ }
3410
+ break;
3411
+ }
3313
3412
  case "/clear":
3314
3413
  session.conversationHistory = [];
3315
3414
  store.clearMessages();
@@ -3437,13 +3536,15 @@ function Header() {
3437
3536
  setBranch(b2);
3438
3537
  } catch {}
3439
3538
  }, []);
3440
- const provider = import_store_h.getSnapshot().provider;
3539
+ const snapshot = import_store_h.getSnapshot();
3540
+ const provider = snapshot.provider;
3441
3541
  const providerLabel = import_config.PROVIDERS[provider]?.label || provider;
3542
+ const configReady = !snapshot.needsConfig;
3442
3543
  return /* @__PURE__ */ jsx_runtime.jsxs("box", {
3443
- style: { flexDirection: "row", paddingLeft: 1, paddingRight: 1 },
3544
+ style: { flexDirection: "row", paddingLeft: 1, paddingRight: 1, paddingTop: 1, paddingBottom: 0 },
3444
3545
  children: [
3445
3546
  /* @__PURE__ */ jsx_runtime.jsx("box", {
3446
- style: { flexGrow: 1 },
3547
+ style: { flexGrow: 1, flexDirection: "column" },
3447
3548
  children: /* @__PURE__ */ jsx_runtime.jsxs("text", {
3448
3549
  children: [
3449
3550
  /* @__PURE__ */ jsx_runtime.jsx("span", {
@@ -3482,15 +3583,36 @@ function Header() {
3482
3583
  ]
3483
3584
  })
3484
3585
  }),
3485
- !isNarrow ? /* @__PURE__ */ jsx_runtime.jsxs("text", {
3586
+ !isNarrow ? /* @__PURE__ */ jsx_runtime.jsxs("box", {
3587
+ style: { flexDirection: "column", alignItems: "flex-end" },
3486
3588
  children: [
3487
- /* @__PURE__ */ jsx_runtime.jsx("span", {
3488
- fg: import_theme.colors.dim,
3489
- children: "\xB7 "
3589
+ /* @__PURE__ */ jsx_runtime.jsxs("text", {
3590
+ children: [
3591
+ /* @__PURE__ */ jsx_runtime.jsx("span", {
3592
+ fg: configReady ? import_theme.colors.green : import_theme.colors.yellow,
3593
+ children: configReady ? "\u25CF" : "\u25CB"
3594
+ }),
3595
+ /* @__PURE__ */ jsx_runtime.jsx("span", {
3596
+ fg: import_theme.colors.dim,
3597
+ children: " "
3598
+ }),
3599
+ /* @__PURE__ */ jsx_runtime.jsx("span", {
3600
+ fg: import_theme.colors.muted,
3601
+ children: configReady ? "ready" : "needs setup"
3602
+ })
3603
+ ]
3490
3604
  }),
3491
- /* @__PURE__ */ jsx_runtime.jsx("span", {
3492
- fg: import_theme.colors.muted,
3493
- children: providerLabel
3605
+ /* @__PURE__ */ jsx_runtime.jsxs("text", {
3606
+ children: [
3607
+ /* @__PURE__ */ jsx_runtime.jsx("span", {
3608
+ fg: import_theme.colors.dim,
3609
+ children: "provider "
3610
+ }),
3611
+ /* @__PURE__ */ jsx_runtime.jsx("span", {
3612
+ fg: import_theme.colors.primary,
3613
+ children: providerLabel
3614
+ })
3615
+ ]
3494
3616
  })
3495
3617
  ]
3496
3618
  }) : null
@@ -3513,7 +3635,7 @@ var jsx_runtime3 = __toESM(require_jsx_runtime(), 1);
3513
3635
  function Welcome() {
3514
3636
  const { isNarrow } = useLayout();
3515
3637
  return /* @__PURE__ */ jsx_runtime3.jsxs("box", {
3516
- style: { flexDirection: "column", paddingLeft: 1, marginTop: 1 },
3638
+ style: { flexDirection: "column", paddingLeft: 1, marginTop: 1, marginBottom: 1 },
3517
3639
  children: [
3518
3640
  /* @__PURE__ */ jsx_runtime3.jsx("text", {
3519
3641
  fg: import_theme3.colors.white,
@@ -3522,8 +3644,13 @@ function Welcome() {
3522
3644
  }),
3523
3645
  /* @__PURE__ */ jsx_runtime3.jsx("text", {
3524
3646
  fg: import_theme3.colors.dim,
3525
- content: isNarrow ? `Max ${import_config2.MAX_TOOL_ITERATIONS} iterations` : `Tools available \xB7 Max ${import_config2.MAX_TOOL_ITERATIONS} iterations per turn`
3526
- })
3647
+ content: isNarrow ? `Type a message or /help` : `Apex can read, edit, run commands, and review your code. Use /help to see shortcuts.`
3648
+ }),
3649
+ !isNarrow ? /* @__PURE__ */ jsx_runtime3.jsx("text", {
3650
+ fg: import_theme3.colors.dim,
3651
+ style: { marginTop: 0 },
3652
+ content: `Shortcuts \xB7 /help \xB7 /files \xB7 /diff \xB7 /cost \xB7 /quit \xB7 Max ${import_config2.MAX_TOOL_ITERATIONS} iterations`
3653
+ }) : null
3527
3654
  ]
3528
3655
  });
3529
3656
  }
@@ -4051,16 +4178,18 @@ function InputBar({ disabled, onSubmit }) {
4051
4178
  inputRef.current.value = "";
4052
4179
  onSubmit(trimmed);
4053
4180
  };
4054
- const hint = isNarrow ? "^C \xB7 /?" : "Ctrl+C exit \xB7 /help";
4181
+ const hint = isNarrow ? "Ctrl+C \xB7 /" : "Ctrl+C exit \xB7 /help \xB7 /files";
4182
+ const placeholder = disabled ? "setup in progress..." : isNarrow ? "Message or /cmd" : "Ask Apex anything, or use /commands";
4055
4183
  return /* @__PURE__ */ jsx_runtime12.jsx("box", {
4056
- style: { flexDirection: "column" },
4184
+ style: { flexDirection: "column", paddingLeft: 1, paddingRight: 1, paddingBottom: 1 },
4057
4185
  children: /* @__PURE__ */ jsx_runtime12.jsxs("box", {
4058
4186
  style: {
4059
4187
  flexDirection: "row",
4060
4188
  paddingLeft: 1,
4061
4189
  paddingRight: 1,
4062
4190
  borderStyle: "rounded",
4063
- borderColor: disabled ? import_theme12.colors.dim : import_theme12.colors.border
4191
+ borderColor: disabled ? import_theme12.colors.dim : import_theme12.colors.border,
4192
+ backgroundColor: disabled ? import_theme12.colors.surface : undefined
4064
4193
  },
4065
4194
  children: [
4066
4195
  /* @__PURE__ */ jsx_runtime12.jsx("text", {
@@ -4071,7 +4200,7 @@ function InputBar({ disabled, onSubmit }) {
4071
4200
  /* @__PURE__ */ jsx_runtime12.jsx("input", {
4072
4201
  ref: inputRef,
4073
4202
  focused: !disabled,
4074
- placeholder: disabled ? "processing..." : "Type a message or /command",
4203
+ placeholder,
4075
4204
  onSubmit: handleSubmit,
4076
4205
  fg: import_theme12.colors.text,
4077
4206
  style: { flexGrow: 1 }
@@ -4087,10 +4216,13 @@ function InputBar({ disabled, onSubmit }) {
4087
4216
  var import_react_sb = __toESM(require_react(), 1);
4088
4217
  var import_theme13 = __toESM(require_theme(), 1);
4089
4218
  var import_config3 = __toESM(require_config(), 1);
4219
+ var import_store3 = __toESM(require_store(), 1);
4090
4220
  var jsx_runtime13 = __toESM(require_jsx_runtime(), 1);
4091
4221
  var SPINNER_FRAMES = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
4092
4222
  function StatusBar({ isProcessing }) {
4093
4223
  const { isNarrow } = useLayout();
4224
+ const snapshot = import_config3.session;
4225
+ const state = import_store3.getSnapshot();
4094
4226
  const [tick, setTick] = import_react_sb.useState(0);
4095
4227
  import_react_sb.useEffect(() => {
4096
4228
  const id = setInterval(() => setTick((t) => t + 1), 1000);
@@ -4103,11 +4235,12 @@ function StatusBar({ isProcessing }) {
4103
4235
  const id = setInterval(() => setSpinFrame((f) => (f + 1) % SPINNER_FRAMES.length), 80);
4104
4236
  return () => clearInterval(id);
4105
4237
  }, [isProcessing]);
4106
- const elapsed = (tick, ((Date.now() - import_config3.session.startTime) / 1000 / 60).toFixed(1));
4107
- const { totalCost, totalTokens, toolCallCount, turnCount, filesModified } = import_config3.session;
4108
- const tokStr = totalTokens >= 1000 ? (totalTokens / 1000).toFixed(1) + "k" : String(totalTokens);
4238
+ const elapsed = (tick, ((Date.now() - snapshot.startTime) / 1000 / 60).toFixed(1));
4239
+ const tokStr = snapshot.totalTokens >= 1000 ? (snapshot.totalTokens / 1000).toFixed(1) + "k" : String(snapshot.totalTokens);
4240
+ const configReady = !state.needsConfig;
4241
+ const providerLabel = state.provider ? import_config3.PROVIDERS[state.provider]?.label || state.provider : "unknown";
4109
4242
  return /* @__PURE__ */ jsx_runtime13.jsxs("box", {
4110
- style: { flexDirection: "row", paddingLeft: isNarrow ? 1 : 2, paddingRight: isNarrow ? 1 : 2 },
4243
+ style: { flexDirection: "row", paddingLeft: isNarrow ? 1 : 2, paddingRight: isNarrow ? 1 : 2, paddingBottom: 1 },
4111
4244
  children: [
4112
4245
  /* @__PURE__ */ jsx_runtime13.jsx("box", {
4113
4246
  style: { flexGrow: 1 },
@@ -4120,19 +4253,15 @@ function StatusBar({ isProcessing }) {
4120
4253
  /* @__PURE__ */ jsx_runtime13.jsx("span", { fg: import_theme13.colors.dim, children: " \xB7 " }),
4121
4254
  /* @__PURE__ */ jsx_runtime13.jsxs("span", {
4122
4255
  fg: import_theme13.colors.dim,
4123
- children: [turnCount, " turns"]
4256
+ children: [snapshot.turnCount, " turns"]
4124
4257
  }),
4125
4258
  !isNarrow ? /* @__PURE__ */ jsx_runtime13.jsxs(jsx_runtime13.Fragment, {
4126
4259
  children: [
4127
4260
  /* @__PURE__ */ jsx_runtime13.jsx("span", { fg: import_theme13.colors.dim, children: " \xB7 " }),
4128
4261
  /* @__PURE__ */ jsx_runtime13.jsxs("span", {
4129
4262
  fg: import_theme13.colors.dim,
4130
- children: [toolCallCount, " tools"]
4131
- })
4132
- ]
4133
- }) : null,
4134
- !isNarrow ? /* @__PURE__ */ jsx_runtime13.jsxs(jsx_runtime13.Fragment, {
4135
- children: [
4263
+ children: [snapshot.toolCallCount, " tools"]
4264
+ }),
4136
4265
  /* @__PURE__ */ jsx_runtime13.jsx("span", { fg: import_theme13.colors.dim, children: " \xB7 " }),
4137
4266
  /* @__PURE__ */ jsx_runtime13.jsxs("span", {
4138
4267
  fg: import_theme13.colors.dim,
@@ -4143,20 +4272,27 @@ function StatusBar({ isProcessing }) {
4143
4272
  /* @__PURE__ */ jsx_runtime13.jsx("span", { fg: import_theme13.colors.dim, children: " \xB7 " }),
4144
4273
  /* @__PURE__ */ jsx_runtime13.jsxs("span", {
4145
4274
  fg: import_theme13.colors.dim,
4146
- children: ["$", totalCost.toFixed(4)]
4147
- }),
4148
- filesModified.size > 0 && !isNarrow ? /* @__PURE__ */ jsx_runtime13.jsxs(jsx_runtime13.Fragment, {
4149
- children: [
4150
- /* @__PURE__ */ jsx_runtime13.jsx("span", { fg: import_theme13.colors.dim, children: " \xB7 " }),
4151
- /* @__PURE__ */ jsx_runtime13.jsxs("span", {
4152
- fg: import_theme13.colors.yellow,
4153
- children: [filesModified.size, " modified"]
4154
- })
4155
- ]
4156
- }) : null
4275
+ children: ["$", snapshot.totalCost.toFixed(4)]
4276
+ })
4157
4277
  ]
4158
4278
  })
4159
4279
  }),
4280
+ !isNarrow ? /* @__PURE__ */ jsx_runtime13.jsxs("text", {
4281
+ children: [
4282
+ /* @__PURE__ */ jsx_runtime13.jsx("span", {
4283
+ fg: configReady ? import_theme13.colors.green : import_theme13.colors.yellow,
4284
+ children: configReady ? "\u25CF" : "\u25CB"
4285
+ }),
4286
+ /* @__PURE__ */ jsx_runtime13.jsx("span", {
4287
+ fg: import_theme13.colors.dim,
4288
+ children: " "
4289
+ }),
4290
+ /* @__PURE__ */ jsx_runtime13.jsx("span", {
4291
+ fg: import_theme13.colors.muted,
4292
+ children: configReady ? providerLabel : "setup required"
4293
+ })
4294
+ ]
4295
+ }) : null,
4160
4296
  isProcessing ? /* @__PURE__ */ jsx_runtime13.jsxs("text", {
4161
4297
  children: [
4162
4298
  /* @__PURE__ */ jsx_runtime13.jsx("span", {
@@ -4165,11 +4301,11 @@ function StatusBar({ isProcessing }) {
4165
4301
  }),
4166
4302
  /* @__PURE__ */ jsx_runtime13.jsx("span", {
4167
4303
  fg: import_theme13.colors.accent,
4168
- children: isNarrow ? " ..." : " thinking "
4304
+ children: isNarrow ? " \u2026" : " thinking"
4169
4305
  }),
4170
4306
  !isNarrow ? /* @__PURE__ */ jsx_runtime13.jsx("span", {
4171
4307
  fg: import_theme13.colors.dim,
4172
- children: "\u25A0 Esc"
4308
+ children: " \xB7 Esc"
4173
4309
  }) : null
4174
4310
  ]
4175
4311
  }) : null
@@ -4189,6 +4325,11 @@ var COMMANDS = [
4189
4325
  { cmd: "/git <cmd>", desc: "Run a git command" },
4190
4326
  { cmd: "/quit", desc: "Exit" }
4191
4327
  ];
4328
+ var QUICK_TIPS = [
4329
+ "Ctrl+C exits the app",
4330
+ "Esc closes overlays and thinking blocks",
4331
+ "On first launch, choose a provider and paste your API key"
4332
+ ];
4192
4333
  var TOOLS = [
4193
4334
  "Read",
4194
4335
  "Write",
@@ -4264,6 +4405,19 @@ function HelpModal({ onClose, onCommand }) {
4264
4405
  })
4265
4406
  }, cmd))
4266
4407
  }),
4408
+ /* @__PURE__ */ jsx_runtime14.jsx("text", {
4409
+ fg: import_theme14.colors.white,
4410
+ attributes: TextAttributes.BOLD,
4411
+ style: { marginTop: 1 },
4412
+ content: "Quick Tips"
4413
+ }),
4414
+ /* @__PURE__ */ jsx_runtime14.jsx("box", {
4415
+ style: { flexDirection: "column", marginTop: 0 },
4416
+ children: QUICK_TIPS.map((tip) => /* @__PURE__ */ jsx_runtime14.jsx("text", {
4417
+ fg: import_theme14.colors.dim,
4418
+ content: `\u2022 ${tip}`
4419
+ }, tip))
4420
+ }),
4267
4421
  /* @__PURE__ */ jsx_runtime14.jsx("text", {
4268
4422
  fg: import_theme14.colors.white,
4269
4423
  attributes: TextAttributes.BOLD,
@@ -4304,7 +4458,7 @@ var import_useLayout = __toESM(require_useLayout(), 1);
4304
4458
  var jsx_runtime = __toESM(require_jsx_runtime(), 1);
4305
4459
  var PROVIDER_ORDER = ["fireworks", "openai", "openrouter", "groq", "gemini", "together"];
4306
4460
  var PROVIDER_EMOJI = {
4307
- fireworks: "\uD83C\uDF86",
4461
+ fireworks: "\uD83D\uDD25",
4308
4462
  openai: "\uD83E\uDD16",
4309
4463
  openrouter: "\uD83D\uDD00",
4310
4464
  groq: "\u26A1",
@@ -4320,34 +4474,62 @@ function ProviderSelector() {
4320
4474
  var providers = import_config.PROVIDERS;
4321
4475
  var providerKey = PROVIDER_ORDER[focusedIdx];
4322
4476
  var provider = providers[providerKey];
4477
+ function getStoredKey(key) {
4478
+ return import_config.getSavedApiKey(key);
4479
+ }
4480
+ function loginState(key) {
4481
+ return import_config.getProviderLoginState(key);
4482
+ }
4323
4483
  function isConfigured(key) {
4324
- var envKey = providers[key].envKey;
4325
- var hasEnv = Boolean(process.env[envKey]);
4326
- var hasStored = key === state.provider && Boolean(state.apiKey);
4327
- return hasEnv || hasStored;
4484
+ return loginState(key) !== "empty";
4328
4485
  }
4329
- function isDefault(key) {
4330
- return key === state.provider && Boolean(state.apiKey);
4486
+ function isLoggedIn(key) {
4487
+ return loginState(key) === "logged-in";
4331
4488
  }
4332
- function handleSelect() {
4333
- if (isConfigured(providerKey)) {
4334
- var key = process.env[provider.envKey] || state.apiKey;
4335
- import_config.setProvider(providerKey, key);
4489
+ function finishLogin(providerKey2, key) {
4490
+ import_config.loginProvider(providerKey2, key);
4491
+ import_store.setState({
4492
+ apiKey: key,
4493
+ provider: providerKey2,
4494
+ needsConfig: false
4495
+ });
4496
+ }
4497
+ function handleLogin() {
4498
+ var key = input.trim();
4499
+ if (!key)
4500
+ return;
4501
+ finishLogin(providerKey, key);
4502
+ setInput("");
4503
+ setStep("select");
4504
+ }
4505
+ function handleLogout() {
4506
+ var remaining = import_config.logoutProvider(providerKey);
4507
+ if (remaining) {
4336
4508
  import_store.setState({
4337
- apiKey: key,
4338
- provider: providerKey,
4509
+ apiKey: remaining.apiKey,
4510
+ provider: remaining.providerKey,
4339
4511
  needsConfig: false
4340
4512
  });
4341
4513
  } else {
4342
- setStep("key");
4514
+ import_store.setState({
4515
+ apiKey: "",
4516
+ provider: providerKey,
4517
+ needsConfig: true
4518
+ });
4343
4519
  }
4520
+ setInput("");
4521
+ setStep("select");
4344
4522
  }
4345
- function handleSubmitKey() {
4346
- var key = input.trim();
4347
- if (!key)
4523
+ function handleSelect() {
4524
+ if (isLoggedIn(providerKey)) {
4525
+ handleLogout();
4348
4526
  return;
4349
- import_config.setProvider(providerKey, key);
4350
- import_store.setState({ apiKey: key, provider: providerKey, needsConfig: false });
4527
+ }
4528
+ if (isConfigured(providerKey)) {
4529
+ finishLogin(providerKey, getStoredKey(providerKey));
4530
+ return;
4531
+ }
4532
+ setStep("key");
4351
4533
  }
4352
4534
  var handleKeyPress = function(key) {
4353
4535
  if (step === "select") {
@@ -4361,21 +4543,25 @@ function ProviderSelector() {
4361
4543
  });
4362
4544
  } else if (key.name === "return" || key.name === "enter") {
4363
4545
  handleSelect();
4546
+ } else if (key.name === "l") {
4547
+ if (isLoggedIn(providerKey))
4548
+ handleLogout();
4364
4549
  }
4365
4550
  } else {
4366
4551
  if (key.name === "escape") {
4367
4552
  setStep("select");
4368
4553
  setInput("");
4369
4554
  } else if (key.name === "return" || key.name === "enter") {
4370
- handleSubmitKey();
4555
+ handleLogin();
4371
4556
  }
4372
4557
  }
4373
4558
  };
4559
+ var selectedState = loginState(providerKey);
4374
4560
  return jsx_runtime.jsx("box", {
4375
4561
  style: {
4376
4562
  flexDirection: "column",
4377
4563
  flexGrow: 1,
4378
- paddingTop: 3
4564
+ paddingTop: 2
4379
4565
  },
4380
4566
  onKeyDown: handleKeyPress,
4381
4567
  focused: true,
@@ -4386,36 +4572,39 @@ function ProviderSelector() {
4386
4572
  children: jsx_runtime.jsx("text", {
4387
4573
  attributes: TextAttributes.BOLD,
4388
4574
  fg: import_theme.colors.white,
4389
- children: "\u26A1 Select AI Provider"
4575
+ children: "Choose your AI provider"
4390
4576
  })
4391
4577
  }),
4392
4578
  jsx_runtime.jsx("box", {
4393
4579
  style: { paddingLeft: 4, paddingRight: 4, marginBottom: 1 },
4394
4580
  children: jsx_runtime.jsx("text", {
4395
4581
  fg: import_theme.colors.dim,
4396
- children: "\u2191\u2193 or j/k to navigate \xB7 Enter to select \xB7 Ctrl+C to exit"
4582
+ children: "Use \u2191\u2193 or j/k to navigate. Enter logs in or out depending on the selected provider's state."
4397
4583
  })
4398
4584
  }),
4399
4585
  PROVIDER_ORDER.map(function(key, idx) {
4400
4586
  var focused = idx === focusedIdx;
4401
- var configured = isConfigured(key);
4402
- var def = isDefault(key);
4403
- var statusFg = def ? import_theme.colors.accent : configured ? import_theme.colors.green : import_theme.colors.dim;
4404
- var statusText = def ? "\u2713 Active" : configured ? "\u2713 Configured" : "\u2717 Not configured";
4587
+ var stateLabel = loginState(key);
4588
+ var statusFg = stateLabel === "logged-in" ? import_theme.colors.green : stateLabel === "saved" ? import_theme.colors.yellow : import_theme.colors.dim;
4589
+ var statusText = stateLabel === "logged-in" ? "Logged in" : stateLabel === "saved" ? "Logged out" : "Needs key";
4405
4590
  return jsx_runtime.jsxs("box", {
4406
4591
  style: {
4407
4592
  flexDirection: "row",
4408
- paddingLeft: focused ? 4 : 4,
4409
- paddingRight: 4,
4410
- paddingTop: 0,
4411
- paddingBottom: 0
4593
+ paddingLeft: 4,
4594
+ paddingRight: 4
4412
4595
  },
4413
4596
  onMouseEnter: function() {
4414
4597
  setFocusedIdx(idx);
4415
4598
  },
4416
4599
  onMouseDown: function() {
4417
4600
  setFocusedIdx(idx);
4418
- handleSelect();
4601
+ if (stateLabel === "logged-in") {
4602
+ handleLogout();
4603
+ } else if (stateLabel === "saved") {
4604
+ finishLogin(key, getStoredKey(key));
4605
+ } else {
4606
+ setStep("key");
4607
+ }
4419
4608
  },
4420
4609
  children: [
4421
4610
  jsx_runtime.jsx("text", {
@@ -4443,7 +4632,7 @@ function ProviderSelector() {
4443
4632
  style: { paddingLeft: 4, paddingRight: 4, marginTop: 2 },
4444
4633
  children: jsx_runtime.jsx("text", {
4445
4634
  fg: import_theme.colors.dim,
4446
- children: "Keys are stored in ~/.apex-dev/config.json or set via environment variables"
4635
+ children: selectedState === "logged-in" ? "Press Enter to log out of the selected provider." : selectedState === "saved" ? "Press Enter to log in with the saved key." : "Press Enter to log in with a new key. Keys are stored in ~/.apex-dev/config.json or can be supplied via environment variables."
4447
4636
  })
4448
4637
  })
4449
4638
  ]
@@ -4488,7 +4677,7 @@ function ProviderSelector() {
4488
4677
  focused: true,
4489
4678
  value: input,
4490
4679
  onChange: setInput,
4491
- onSubmit: handleSubmitKey,
4680
+ onSubmit: handleLogin,
4492
4681
  placeholder: "Paste your API key here...",
4493
4682
  fg: import_theme.colors.text
4494
4683
  })
@@ -4498,7 +4687,7 @@ function ProviderSelector() {
4498
4687
  style: { paddingLeft: 4, paddingRight: 4 },
4499
4688
  children: jsx_runtime.jsx("text", {
4500
4689
  fg: import_theme.colors.dim,
4501
- children: "Press Enter to confirm"
4690
+ children: "Press Enter to login"
4502
4691
  })
4503
4692
  })
4504
4693
  ]
@@ -4697,32 +4886,34 @@ function App() {
4697
4886
  });
4698
4887
  }
4699
4888
  }, []);
4700
- const showConfig = state.needsConfig || process.env.APEX_DEV_NEEDS_CONFIG === "true";
4889
+ const shouldShowSetup = process.env.APEX_DEV_NEEDS_CONFIG === "true" || state.needsConfig;
4701
4890
  return /* @__PURE__ */ jsx_runtime15.jsxs("box", {
4702
4891
  style: { flexDirection: "column", flexGrow: 1 },
4703
4892
  children: [
4704
- /* @__PURE__ */ jsx_runtime15.jsx(Header, {}),
4705
- /* @__PURE__ */ jsx_runtime15.jsx(Divider, {}),
4706
- /* @__PURE__ */ jsx_runtime15.jsx(ChatArea, {
4707
- messages: state.messages,
4708
- streamingContent: state.streamingContent,
4709
- streamingThinking: state.streamingThinking,
4710
- isProcessing: state.isProcessing
4711
- }),
4712
- /* @__PURE__ */ jsx_runtime15.jsx(Divider, {}),
4713
- /* @__PURE__ */ jsx_runtime15.jsx(StatusBar, {
4714
- isProcessing: state.isProcessing
4715
- }),
4716
- /* @__PURE__ */ jsx_runtime15.jsx(InputBar, {
4717
- disabled: state.isProcessing || state.showHelp || state.needsConfig,
4718
- onSubmit: handleInput
4719
- }),
4720
- state.showHelp ? /* @__PURE__ */ jsx_runtime15.jsx(HelpModal, {
4721
- onClose: () => import_store5.setState({ showHelp: false }),
4722
- onCommand: handleHelpCommand
4723
- }) : null,
4724
- showConfig ? /* @__PURE__ */ jsx_runtime15.jsx(globalThis._ApiKeyModal, {}) : null,
4725
- state.needsConfig ? /* @__PURE__ */ jsx_runtime15.jsx(globalThis._ProviderSelector, {}) : null
4893
+ shouldShowSetup ? /* @__PURE__ */ jsx_runtime15.jsx(globalThis._ProviderSelector, {}) : /* @__PURE__ */ jsx_runtime15.jsxs(jsx_runtime15.Fragment, {
4894
+ children: [
4895
+ /* @__PURE__ */ jsx_runtime15.jsx(Header, {}),
4896
+ /* @__PURE__ */ jsx_runtime15.jsx(Divider, {}),
4897
+ /* @__PURE__ */ jsx_runtime15.jsx(ChatArea, {
4898
+ messages: state.messages,
4899
+ streamingContent: state.streamingContent,
4900
+ streamingThinking: state.streamingThinking,
4901
+ isProcessing: state.isProcessing
4902
+ }),
4903
+ /* @__PURE__ */ jsx_runtime15.jsx(Divider, {}),
4904
+ /* @__PURE__ */ jsx_runtime15.jsx(StatusBar, {
4905
+ isProcessing: state.isProcessing
4906
+ }),
4907
+ /* @__PURE__ */ jsx_runtime15.jsx(InputBar, {
4908
+ disabled: state.isProcessing || state.showHelp || shouldShowSetup,
4909
+ onSubmit: handleInput
4910
+ }),
4911
+ state.showHelp ? /* @__PURE__ */ jsx_runtime15.jsx(HelpModal, {
4912
+ onClose: () => import_store5.setState({ showHelp: false }),
4913
+ onCommand: handleHelpCommand
4914
+ }) : null
4915
+ ]
4916
+ })
4726
4917
  ]
4727
4918
  });
4728
4919
  }