@astra-cli/cli 1.2.8 → 1.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/astra.js +283 -41
  2. package/package.json +6 -2
package/dist/astra.js CHANGED
@@ -204,6 +204,7 @@ var ConfigSchema = z2.object({
204
204
  }).optional()
205
205
  }),
206
206
  apiBase: z2.string().default("https://agents.astranova.live"),
207
+ savedKeys: z2.record(z2.string()).optional(),
207
208
  preferences: z2.object({
208
209
  theme: z2.enum(["dark", "light"]).default("dark")
209
210
  }).default({}),
@@ -1356,9 +1357,14 @@ function pluginTagline(pluginName, tagline) {
1356
1357
  const capitalized = pluginName.charAt(0).toUpperCase() + pluginName.slice(1);
1357
1358
  return `${GREEN}Welcome to ${capitalized} \xB7 ${tagline}${RESET}`;
1358
1359
  }
1359
- var __dirname = dirname(fileURLToPath(import.meta.url));
1360
- var pkg = JSON.parse(readFileSync(resolve(__dirname, "..", "package.json"), "utf-8"));
1361
- var VERSION = `${GREEN}v${pkg.version}${RESET}`;
1360
+ var version = "0.0.0";
1361
+ try {
1362
+ const __dirname = dirname(fileURLToPath(import.meta.url));
1363
+ const pkg = JSON.parse(readFileSync(resolve(__dirname, "..", "package.json"), "utf-8"));
1364
+ version = pkg.version;
1365
+ } catch {
1366
+ }
1367
+ var VERSION = `${GREEN}v${version}${RESET}`;
1362
1368
 
1363
1369
  // src/onboarding/index.ts
1364
1370
  async function runOnboarding() {
@@ -4245,7 +4251,7 @@ function extractNarrativeContent(skillMd) {
4245
4251
  function buildManifestFromMeta(meta) {
4246
4252
  const rawName = typeof meta.name === "string" ? meta.name.trim() : void 0;
4247
4253
  const name = rawName && /^[a-z0-9_-]+$/.test(rawName) ? rawName : void 0;
4248
- const version = typeof meta.version === "string" ? meta.version.trim() : "0.0.0";
4254
+ const version2 = typeof meta.version === "string" ? meta.version.trim() : "0.0.0";
4249
4255
  const description = typeof meta.description === "string" ? meta.description.trim() : name ?? "Unknown";
4250
4256
  const apiBase = typeof meta.apiBase === "string" ? meta.apiBase.trim().replace(/\/$/, "") : void 0;
4251
4257
  let allowedPaths;
@@ -4254,7 +4260,7 @@ function buildManifestFromMeta(meta) {
4254
4260
  } else if (typeof meta.allowedPaths === "string") {
4255
4261
  allowedPaths = [meta.allowedPaths];
4256
4262
  }
4257
- return { name, version, description, apiBase, allowedPaths };
4263
+ return { name, version: version2, description, apiBase, allowedPaths };
4258
4264
  }
4259
4265
  async function fetchWithLimit(url, maxBytes, timeoutMs) {
4260
4266
  const controller = new AbortController();
@@ -5125,7 +5131,7 @@ import { useState as useState3, useEffect as useEffect2 } from "react";
5125
5131
  import { Text as Text9 } from "ink";
5126
5132
  import { jsxs as jsxs9 } from "react/jsx-runtime";
5127
5133
  var FRAMES = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
5128
- var INTERVAL_MS = 120;
5134
+ var INTERVAL_MS = 80;
5129
5135
  function Spinner({ label }) {
5130
5136
  const [frame, setFrame] = useState3(0);
5131
5137
  useEffect2(() => {
@@ -5179,27 +5185,9 @@ function App({
5179
5185
  isLoadingRef.current = isLoading;
5180
5186
  }, [isLoading]);
5181
5187
  const [toolName, setToolName] = useState4(void 0);
5182
- const streamBufferRef = useRef2("");
5183
- const streamFlushTimerRef = useRef2(null);
5184
- const flushStreamBuffer = useCallback2(() => {
5185
- streamFlushTimerRef.current = null;
5186
- setStreamingText((prev) => (prev ?? "") + streamBufferRef.current);
5187
- streamBufferRef.current = "";
5188
- }, []);
5189
- const appendStreamChunk = useCallback2((chunk) => {
5190
- streamBufferRef.current += chunk;
5191
- if (!streamFlushTimerRef.current) {
5192
- streamFlushTimerRef.current = setTimeout(flushStreamBuffer, 50);
5193
- }
5194
- }, [flushStreamBuffer]);
5195
- const clearStream = useCallback2(() => {
5196
- if (streamFlushTimerRef.current) {
5197
- clearTimeout(streamFlushTimerRef.current);
5198
- streamFlushTimerRef.current = null;
5199
- }
5200
- streamBufferRef.current = "";
5201
- clearStream();
5202
- }, []);
5188
+ const [awaitingApiKey, setAwaitingApiKey] = useState4(null);
5189
+ const [validatingKey, setValidatingKey] = useState4(false);
5190
+ const oauthVerifierRef = useRef2(null);
5203
5191
  const [autopilotMode, setAutopilotMode] = useState4(initialAutopilotConfig?.mode ?? "off");
5204
5192
  const [autopilotIntervalMs, setAutopilotIntervalMs] = useState4(initialAutopilotConfig?.intervalMs ?? 3e5);
5205
5193
  const epochCallCountRef = useRef2(0);
@@ -5269,7 +5257,7 @@ function App({
5269
5257
  { ...profile, autopilotMode },
5270
5258
  {
5271
5259
  onTextChunk: displayMode === "chat" ? (chunk) => {
5272
- appendStreamChunk(chunk);
5260
+ setStreamingText((prev) => (prev ?? "") + chunk);
5273
5261
  } : () => {
5274
5262
  },
5275
5263
  onToolCallStart: displayMode === "chat" ? (name) => {
@@ -5289,7 +5277,7 @@ function App({
5289
5277
  setCoreMessages(updatedCore);
5290
5278
  const responseText = result.text.trim();
5291
5279
  if (displayMode === "chat") {
5292
- clearStream();
5280
+ setStreamingText(void 0);
5293
5281
  setChatMessages((prev) => [
5294
5282
  ...prev,
5295
5283
  { role: "assistant", content: responseText || "Market checked \u2014 holding." }
@@ -5310,7 +5298,7 @@ function App({
5310
5298
  } catch (error) {
5311
5299
  const message = error instanceof Error ? error.message : "Unknown error";
5312
5300
  if (displayMode === "chat") {
5313
- clearStream();
5301
+ setStreamingText(void 0);
5314
5302
  setChatMessages((prev) => [...prev, { role: "assistant", content: `Autopilot error: ${message}` }]);
5315
5303
  } else {
5316
5304
  addLogEntry("error", message);
@@ -5320,7 +5308,7 @@ function App({
5320
5308
  if (displayMode === "chat") setToolName(void 0);
5321
5309
  }
5322
5310
  },
5323
- [skillContext, tradingContext, walletContext, rewardsContext, onboardingContext, apiContext, profile, autopilotMode, agentName, sessionId, memoryContent, addLogEntry, appendStreamChunk, clearStream, pluginMap]
5311
+ [skillContext, tradingContext, walletContext, rewardsContext, onboardingContext, apiContext, profile, autopilotMode, agentName, sessionId, memoryContent, addLogEntry, pluginMap]
5324
5312
  );
5325
5313
  useEffect3(() => {
5326
5314
  if (!hasAutopilot || autopilotMode === "off") return;
@@ -5346,6 +5334,103 @@ function App({
5346
5334
  const sendMessage = useCallback2(
5347
5335
  async (userText, displayRole = "user") => {
5348
5336
  let skipChatDisplay = false;
5337
+ if (awaitingApiKey) {
5338
+ if (userText.startsWith("/model")) {
5339
+ setAwaitingApiKey(null);
5340
+ oauthVerifierRef.current = null;
5341
+ } else if (awaitingApiKey.startsWith("oauth-paste:")) {
5342
+ const expectedState = awaitingApiKey.slice("oauth-paste:".length);
5343
+ const trimmed = userText.trim();
5344
+ if (!trimmed) return;
5345
+ const parsed = parseCallbackUrl(trimmed, expectedState);
5346
+ if ("error" in parsed) {
5347
+ setChatMessages((prev) => [
5348
+ ...prev,
5349
+ { role: "user", content: trimmed.slice(0, 40) + "..." },
5350
+ { role: "assistant", content: `${parsed.error}
5351
+
5352
+ Paste the full redirect URL, or type \`/model\` to cancel.` }
5353
+ ]);
5354
+ return;
5355
+ }
5356
+ const verifier = oauthVerifierRef.current;
5357
+ if (!verifier) {
5358
+ setAwaitingApiKey(null);
5359
+ setChatMessages((prev) => [...prev, { role: "assistant", content: "OAuth session expired. Try `/model codex` again." }]);
5360
+ return;
5361
+ }
5362
+ setValidatingKey(true);
5363
+ setChatMessages((prev) => [...prev, { role: "user", content: trimmed.slice(0, 40) + "..." }]);
5364
+ try {
5365
+ const tokens = await exchangeCodeForTokens({ code: parsed.code, codeVerifier: verifier });
5366
+ oauthVerifierRef.current = null;
5367
+ setValidatingKey(false);
5368
+ const cfg = loadConfig();
5369
+ if (cfg.auth.type === "api-key" && cfg.auth.apiKey) {
5370
+ cfg.savedKeys = { ...cfg.savedKeys, [cfg.provider]: cfg.auth.apiKey };
5371
+ }
5372
+ cfg.provider = "openai-oauth";
5373
+ cfg.model = DEFAULT_MODELS["openai-oauth"] ?? "gpt-5.3-codex";
5374
+ cfg.auth = {
5375
+ type: "oauth",
5376
+ oauth: { accessToken: tokens.accessToken, refreshToken: tokens.refreshToken, expiresAt: tokens.expiresAt, clientId: tokens.clientId }
5377
+ };
5378
+ saveConfig(cfg);
5379
+ providerRef.current = "openai-oauth";
5380
+ setAwaitingApiKey(null);
5381
+ setChatMessages((prev) => [
5382
+ ...prev,
5383
+ { role: "assistant", content: `Switched to **Codex** (${DEFAULT_MODELS["openai-oauth"]})` }
5384
+ ]);
5385
+ } catch (error) {
5386
+ setValidatingKey(false);
5387
+ const msg = error instanceof Error ? error.message : "Unknown error";
5388
+ setChatMessages((prev) => [
5389
+ ...prev,
5390
+ { role: "assistant", content: `OAuth token exchange failed: ${msg}
5391
+
5392
+ Try \`/model codex\` again.` }
5393
+ ]);
5394
+ setAwaitingApiKey(null);
5395
+ oauthVerifierRef.current = null;
5396
+ }
5397
+ return;
5398
+ } else {
5399
+ const trimmedKey = userText.trim();
5400
+ if (!trimmedKey) return;
5401
+ setValidatingKey(true);
5402
+ setChatMessages((prev) => [...prev, { role: "user", content: "\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022" }]);
5403
+ const result = await validateApiKey(awaitingApiKey, trimmedKey);
5404
+ setValidatingKey(false);
5405
+ if (!result.ok) {
5406
+ setChatMessages((prev) => [
5407
+ ...prev,
5408
+ { role: "assistant", content: `Key validation failed: ${result.error}
5409
+
5410
+ Paste a valid key to retry, or type \`/model\` to cancel.` }
5411
+ ]);
5412
+ return;
5413
+ }
5414
+ const targetProvider = awaitingApiKey;
5415
+ const config = loadConfig();
5416
+ if (config.auth.type === "api-key" && config.auth.apiKey) {
5417
+ config.savedKeys = { ...config.savedKeys, [config.provider]: config.auth.apiKey };
5418
+ }
5419
+ config.provider = targetProvider;
5420
+ config.model = DEFAULT_MODELS[targetProvider] ?? "";
5421
+ config.auth = { type: "api-key", apiKey: trimmedKey };
5422
+ config.savedKeys = { ...config.savedKeys, [targetProvider]: trimmedKey };
5423
+ saveConfig(config);
5424
+ providerRef.current = targetProvider;
5425
+ setAwaitingApiKey(null);
5426
+ const modelName = DEFAULT_MODELS[targetProvider] ?? targetProvider;
5427
+ setChatMessages((prev) => [
5428
+ ...prev,
5429
+ { role: "assistant", content: `Switched to **${providerLabel(targetProvider)}** (${modelName})` }
5430
+ ]);
5431
+ return;
5432
+ }
5433
+ }
5349
5434
  if (userText.startsWith("/")) {
5350
5435
  const parts = userText.trim().split(/\s+/);
5351
5436
  const cmd = parts[0].toLowerCase();
@@ -5375,6 +5460,140 @@ function App({
5375
5460
  setTimeout(() => exit(), 800);
5376
5461
  return;
5377
5462
  }
5463
+ if (cmd === "/model") {
5464
+ const arg = parts[1]?.toLowerCase();
5465
+ const config = loadConfig();
5466
+ const currentProvider = config.provider;
5467
+ const currentModel = config.model;
5468
+ if (!arg) {
5469
+ const available = ["claude", "openai", "gemini", "codex"].filter(
5470
+ (p) => p !== providerAlias(currentProvider)
5471
+ );
5472
+ setChatMessages((prev) => [
5473
+ ...prev,
5474
+ { role: "user", content: userText },
5475
+ {
5476
+ role: "assistant",
5477
+ content: `**Current provider:** ${providerLabel(currentProvider)} (${currentModel})
5478
+
5479
+ **Switch to:** ${available.map((p) => `\`/model ${p}\``).join(" \xB7 ")}`
5480
+ }
5481
+ ]);
5482
+ return;
5483
+ }
5484
+ const providerAliases = {
5485
+ claude: "claude",
5486
+ anthropic: "claude",
5487
+ openai: "openai",
5488
+ gpt: "openai",
5489
+ gemini: "google",
5490
+ google: "google",
5491
+ codex: "openai-oauth",
5492
+ chatgpt: "openai-oauth"
5493
+ };
5494
+ const targetProvider = providerAliases[arg];
5495
+ if (!targetProvider) {
5496
+ setChatMessages((prev) => [
5497
+ ...prev,
5498
+ { role: "user", content: userText },
5499
+ { role: "assistant", content: `Unknown provider: \`${arg}\`. Available: \`claude\` \xB7 \`openai\` \xB7 \`gemini\` \xB7 \`codex\`` }
5500
+ ]);
5501
+ return;
5502
+ }
5503
+ if (targetProvider === currentProvider) {
5504
+ setChatMessages((prev) => [
5505
+ ...prev,
5506
+ { role: "user", content: userText },
5507
+ { role: "assistant", content: `Already using **${providerLabel(targetProvider)}** (${currentModel})` }
5508
+ ]);
5509
+ return;
5510
+ }
5511
+ if (targetProvider === "openai-oauth") {
5512
+ const { verifier, challenge } = generatePkce();
5513
+ const oauthState = generateState();
5514
+ const authorizeUrl = buildAuthorizeUrl({ state: oauthState, challenge });
5515
+ oauthVerifierRef.current = verifier;
5516
+ setChatMessages((prev) => [
5517
+ ...prev,
5518
+ { role: "user", content: userText },
5519
+ { role: "assistant", content: "Opening browser for ChatGPT login..." }
5520
+ ]);
5521
+ setValidatingKey(true);
5522
+ openBrowser(authorizeUrl);
5523
+ try {
5524
+ const result = await waitForCallback({
5525
+ redirectUri: REDIRECT_URI,
5526
+ expectedState: oauthState
5527
+ });
5528
+ const tokens = await exchangeCodeForTokens({ code: result.code, codeVerifier: verifier });
5529
+ oauthVerifierRef.current = null;
5530
+ setValidatingKey(false);
5531
+ const cfg = loadConfig();
5532
+ if (cfg.auth.type === "api-key" && cfg.auth.apiKey) {
5533
+ cfg.savedKeys = { ...cfg.savedKeys, [cfg.provider]: cfg.auth.apiKey };
5534
+ }
5535
+ cfg.provider = "openai-oauth";
5536
+ cfg.model = DEFAULT_MODELS["openai-oauth"] ?? "gpt-5.3-codex";
5537
+ cfg.auth = {
5538
+ type: "oauth",
5539
+ oauth: {
5540
+ accessToken: tokens.accessToken,
5541
+ refreshToken: tokens.refreshToken,
5542
+ expiresAt: tokens.expiresAt,
5543
+ clientId: tokens.clientId
5544
+ }
5545
+ };
5546
+ saveConfig(cfg);
5547
+ providerRef.current = "openai-oauth";
5548
+ setChatMessages((prev) => [
5549
+ ...prev,
5550
+ { role: "assistant", content: `Switched to **Codex** (${DEFAULT_MODELS["openai-oauth"]})` }
5551
+ ]);
5552
+ } catch {
5553
+ setValidatingKey(false);
5554
+ setAwaitingApiKey("oauth-paste:" + oauthState);
5555
+ setChatMessages((prev) => [
5556
+ ...prev,
5557
+ { role: "assistant", content: `Couldn't detect the callback automatically.
5558
+
5559
+ Paste the redirect URL from your browser here, or type \`/model\` to cancel.
5560
+
5561
+ ${authorizeUrl}` }
5562
+ ]);
5563
+ }
5564
+ return;
5565
+ }
5566
+ const savedKey = config.savedKeys?.[targetProvider];
5567
+ if (savedKey) {
5568
+ if (config.auth.type === "api-key" && config.auth.apiKey) {
5569
+ config.savedKeys = { ...config.savedKeys, [config.provider]: config.auth.apiKey };
5570
+ }
5571
+ config.provider = targetProvider;
5572
+ config.model = DEFAULT_MODELS[targetProvider] ?? "";
5573
+ config.auth = { type: "api-key", apiKey: savedKey };
5574
+ saveConfig(config);
5575
+ providerRef.current = targetProvider;
5576
+ const modelName = DEFAULT_MODELS[targetProvider] ?? targetProvider;
5577
+ setChatMessages((prev) => [
5578
+ ...prev,
5579
+ { role: "user", content: userText },
5580
+ { role: "assistant", content: `Switched to **${providerLabel(targetProvider)}** (${modelName})` }
5581
+ ]);
5582
+ return;
5583
+ }
5584
+ const keyLabels = {
5585
+ claude: "Anthropic API key",
5586
+ openai: "OpenAI API key",
5587
+ google: "Google AI API key"
5588
+ };
5589
+ setAwaitingApiKey(targetProvider);
5590
+ setChatMessages((prev) => [
5591
+ ...prev,
5592
+ { role: "user", content: userText },
5593
+ { role: "assistant", content: `Enter your ${keyLabels[targetProvider] ?? "API key"}. Your key is stored locally and never shared with the AI model.` }
5594
+ ]);
5595
+ return;
5596
+ }
5378
5597
  if (hasAutopilot && cmd === "/auto") {
5379
5598
  const sub = parts[1]?.toLowerCase();
5380
5599
  if (!sub || sub === "status") {
@@ -5559,6 +5778,7 @@ Let's go through it and improve or replace it.` : "I want to create a trading st
5559
5778
  helpLines.push(
5560
5779
  "**System**",
5561
5780
  "",
5781
+ " `/model` \u2014 Show or switch LLM provider",
5562
5782
  " `/plugins` \u2014 Browse and switch plugins",
5563
5783
  " `/help` \u2014 Show this help",
5564
5784
  " `/exit` \u2014 Exit (also `/quit`, `/q`)",
@@ -5618,7 +5838,7 @@ Let's go through it and improve or replace it.` : "I want to create a trading st
5618
5838
  { ...profile, autopilotMode },
5619
5839
  {
5620
5840
  onTextChunk: (chunk) => {
5621
- appendStreamChunk(chunk);
5841
+ setStreamingText((prev) => (prev ?? "") + chunk);
5622
5842
  },
5623
5843
  onToolCallStart: (name) => {
5624
5844
  setToolName(name);
@@ -5651,7 +5871,7 @@ Let's go through it and improve or replace it.` : "I want to create a trading st
5651
5871
  }
5652
5872
  setChatMessages(updatedChat);
5653
5873
  setCoreMessages(updatedCore);
5654
- clearStream();
5874
+ setStreamingText(void 0);
5655
5875
  saveSession({
5656
5876
  agentName,
5657
5877
  provider: providerRef.current,
@@ -5677,13 +5897,13 @@ ${stack}
5677
5897
  { role: "assistant", content: `Error: ${message}${debugInfo}` }
5678
5898
  ]);
5679
5899
  setCoreMessages(newCoreMessages);
5680
- clearStream();
5900
+ setStreamingText(void 0);
5681
5901
  } finally {
5682
5902
  setIsLoading(false);
5683
5903
  setToolName(void 0);
5684
5904
  }
5685
5905
  },
5686
- [coreMessages, chatMessages, skillContext, tradingContext, walletContext, rewardsContext, onboardingContext, apiContext, profile, autopilotMode, agentName, sessionId, memoryContent, addLogEntry, pluginMap, hasAutopilot, hasJourneyStages, exit, debug, appendStreamChunk, clearStream, runAutopilotTurn]
5906
+ [coreMessages, chatMessages, skillContext, tradingContext, walletContext, rewardsContext, onboardingContext, apiContext, profile, autopilotMode, agentName, sessionId, memoryContent, addLogEntry, pluginMap, hasAutopilot, hasJourneyStages, exit, debug, runAutopilotTurn, awaitingApiKey]
5687
5907
  );
5688
5908
  useEffect3(() => {
5689
5909
  sendMessageRef.current = sendMessage;
@@ -5695,10 +5915,13 @@ ${stack}
5695
5915
  [sendMessage]
5696
5916
  );
5697
5917
  return /* @__PURE__ */ jsxs10(Box9, { flexDirection: "column", width: "100%", height: "100%", children: [
5698
- /* @__PURE__ */ jsx9(Box9, { flexDirection: "column", flexGrow: 1, flexShrink: 1, children: /* @__PURE__ */ jsx9(ChatView, { messages: chatMessages, streamingText }) }),
5699
- isLoading && toolName && /* @__PURE__ */ jsx9(Spinner, { label: `Calling ${toolName}...` }),
5700
- isLoading && !toolName && /* @__PURE__ */ jsx9(Spinner, { label: streamingText ? "Thinking..." : void 0 }),
5701
- /* @__PURE__ */ jsx9(Box9, { flexShrink: 0, width: "100%", children: /* @__PURE__ */ jsx9(Input, { isActive: !isLoading, onSubmit: handleSubmit }) }),
5918
+ /* @__PURE__ */ jsxs10(Box9, { flexDirection: "column", flexGrow: 1, flexShrink: 1, children: [
5919
+ /* @__PURE__ */ jsx9(ChatView, { messages: chatMessages, streamingText }),
5920
+ validatingKey && /* @__PURE__ */ jsx9(Spinner, { label: "Validating API key..." }),
5921
+ isLoading && toolName && /* @__PURE__ */ jsx9(Spinner, { label: `Calling ${toolName}...` }),
5922
+ isLoading && !toolName && /* @__PURE__ */ jsx9(Spinner, { label: streamingText ? "Thinking..." : void 0 })
5923
+ ] }),
5924
+ /* @__PURE__ */ jsx9(Box9, { flexShrink: 0, width: "100%", children: /* @__PURE__ */ jsx9(Input, { isActive: !isLoading && !validatingKey, onSubmit: handleSubmit }) }),
5702
5925
  /* @__PURE__ */ jsx9(Box9, { flexShrink: 0, width: "100%", children: /* @__PURE__ */ jsx9(
5703
5926
  StatusBar_default,
5704
5927
  {
@@ -5712,8 +5935,8 @@ ${stack}
5712
5935
  pluginMap
5713
5936
  }
5714
5937
  ) }),
5715
- /* @__PURE__ */ jsx9(Box9, { flexShrink: 0, width: "100%", paddingX: 2, marginTop: 1, justifyContent: "space-between", children: hasJourneyStages ? /* @__PURE__ */ jsxs10(Fragment3, { children: [
5716
- /* @__PURE__ */ jsx9(Text10, { dimColor: true, children: "/help \xB7 /portfolio \xB7 /market \xB7 /strategy \xB7 /exit" }),
5938
+ /* @__PURE__ */ jsx9(Box9, { flexShrink: 0, width: "100%", paddingX: 2, marginTop: 1, marginBottom: 1, justifyContent: "space-between", children: hasJourneyStages ? /* @__PURE__ */ jsxs10(Fragment3, { children: [
5939
+ /* @__PURE__ */ jsx9(Text10, { dimColor: true, children: "/help \xB7 /portfolio \xB7 /market \xB7 /model \xB7 /exit" }),
5717
5940
  /* @__PURE__ */ jsx9(Text10, { dimColor: true, children: "/auto on\xB7off\xB7set \xB7 Ctrl+C quit" })
5718
5941
  ] }) : /* @__PURE__ */ jsxs10(Fragment3, { children: [
5719
5942
  /* @__PURE__ */ jsx9(Text10, { dimColor: true, children: (pluginMap?.commands?.map((c) => c.command).join(" \xB7 ") ?? "/help") + " \xB7 /exit" }),
@@ -5724,6 +5947,25 @@ ${stack}
5724
5947
  function formatTime(d) {
5725
5948
  return `${String(d.getHours()).padStart(2, "0")}:${String(d.getMinutes()).padStart(2, "0")}`;
5726
5949
  }
5950
+ function providerLabel(provider) {
5951
+ const labels = {
5952
+ claude: "Claude",
5953
+ openai: "OpenAI GPT",
5954
+ google: "Gemini",
5955
+ "openai-oauth": "Codex",
5956
+ ollama: "Ollama"
5957
+ };
5958
+ return labels[provider] ?? provider;
5959
+ }
5960
+ function providerAlias(provider) {
5961
+ const aliases = {
5962
+ claude: "claude",
5963
+ openai: "openai",
5964
+ google: "gemini",
5965
+ "openai-oauth": "codex"
5966
+ };
5967
+ return aliases[provider] ?? provider;
5968
+ }
5727
5969
 
5728
5970
  // src/bin/astra.ts
5729
5971
  function detectJourneyStage(params) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@astra-cli/cli",
3
- "version": "1.2.8",
3
+ "version": "1.3.0",
4
4
  "description": "The terminal for autonomous agents. Powered by AstraNova.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -33,7 +33,11 @@
33
33
  "test:e2e": "vitest run src/__tests__/e2e/",
34
34
  "test:all": "vitest run",
35
35
  "test:coverage": "vitest run --exclude 'src/__tests__/integration/**' --exclude 'src/__tests__/e2e/**' --coverage",
36
- "prepublishOnly": "pnpm build"
36
+ "prepublishOnly": "pnpm build",
37
+ "desktop:dev": "pnpm build && pnpm --filter @astra-cli/desktop dev",
38
+ "desktop:bundle": "npx tsup --config tsup.desktop.ts",
39
+ "desktop:build": "pnpm desktop:bundle && pnpm --filter @astra-cli/desktop build",
40
+ "desktop:package": "pnpm desktop:bundle && pnpm --filter @astra-cli/desktop package"
37
41
  },
38
42
  "author": "fermartz",
39
43
  "repository": {