@dexto/tui 1.14.5 → 1.15.0-beta.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 (36) hide show
  1. package/dist/components/Footer.cjs +31 -0
  2. package/dist/components/Footer.d.ts.map +1 -1
  3. package/dist/components/Footer.js +31 -0
  4. package/dist/components/Footer.test.cjs +44 -0
  5. package/dist/components/Footer.test.js +44 -0
  6. package/dist/components/overlays/InsufficientCreditsOverlay.cjs +19 -4
  7. package/dist/components/overlays/InsufficientCreditsOverlay.d.ts +2 -0
  8. package/dist/components/overlays/InsufficientCreditsOverlay.d.ts.map +1 -1
  9. package/dist/components/overlays/InsufficientCreditsOverlay.js +19 -4
  10. package/dist/components/overlays/LoginOverlay.cjs +25 -61
  11. package/dist/components/overlays/LoginOverlay.d.ts +1 -3
  12. package/dist/components/overlays/LoginOverlay.d.ts.map +1 -1
  13. package/dist/components/overlays/LoginOverlay.js +26 -66
  14. package/dist/components/overlays/LogoutOverlay.cjs +8 -17
  15. package/dist/components/overlays/LogoutOverlay.d.ts +1 -0
  16. package/dist/components/overlays/LogoutOverlay.d.ts.map +1 -1
  17. package/dist/components/overlays/LogoutOverlay.js +10 -31
  18. package/dist/components/overlays/custom-model-wizard/provider-config.cjs +4 -4
  19. package/dist/components/overlays/custom-model-wizard/provider-config.js +4 -4
  20. package/dist/containers/OverlayContainer.cjs +30 -40
  21. package/dist/containers/OverlayContainer.d.ts.map +1 -1
  22. package/dist/containers/OverlayContainer.js +30 -40
  23. package/dist/host/index.cjs +22 -49
  24. package/dist/host/index.d.ts +18 -72
  25. package/dist/host/index.d.ts.map +1 -1
  26. package/dist/host/index.js +18 -42
  27. package/dist/host/index.test.cjs +21 -35
  28. package/dist/host/index.test.js +22 -40
  29. package/dist/index.d.cts +13 -52
  30. package/dist/services/processStream.cjs +2 -2
  31. package/dist/services/processStream.js +2 -2
  32. package/dist/services/processStream.test.cjs +1 -1
  33. package/dist/services/processStream.test.js +1 -1
  34. package/dist/utils/llm-provider-display.cjs +1 -1
  35. package/dist/utils/llm-provider-display.js +1 -1
  36. package/package.json +5 -5
@@ -40,6 +40,7 @@ var import_core = require("@dexto/core");
40
40
  var import_llm_provider_display = require("../utils/llm-provider-display.js");
41
41
  var import_chatgpt_rate_limit = require("../utils/chatgpt-rate-limit.js");
42
42
  var import_agent_backend = require("../agent-backend.js");
43
+ var import_host = require("../host/index.js");
43
44
  function Footer({
44
45
  agent,
45
46
  sessionId,
@@ -125,6 +126,31 @@ function Footer({
125
126
  controller.abort();
126
127
  };
127
128
  }, [agent, sessionId]);
129
+ const [creditBalance, setCreditBalance] = (0, import_react.useState)(null);
130
+ const usesDextoCredits = provider === "dexto-nova";
131
+ (0, import_react.useEffect)(() => {
132
+ setCreditBalance(null);
133
+ if (!usesDextoCredits) return;
134
+ let cancelled = false;
135
+ const refresh = () => {
136
+ (0, import_host.dextoCreditBalance)().then((balance) => {
137
+ if (!cancelled) setCreditBalance(balance);
138
+ }).catch(() => void 0);
139
+ };
140
+ refresh();
141
+ const controller = new AbortController();
142
+ agent.on(
143
+ "llm:response",
144
+ (payload) => {
145
+ if (!payload.sessionId || payload.sessionId === sessionId) refresh();
146
+ },
147
+ { signal: controller.signal }
148
+ );
149
+ return () => {
150
+ cancelled = true;
151
+ controller.abort();
152
+ };
153
+ }, [agent, sessionId, usesDextoCredits]);
128
154
  const pathColor = isShellMode ? "yellow" : "blue";
129
155
  return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_ink.Box, { flexDirection: "column", paddingX: 1, children: [
130
156
  /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_ink.Box, { flexDirection: "row", justifyContent: "space-between", children: [
@@ -153,6 +179,11 @@ function Footer({
153
179
  contextLeft.percentLeft,
154
180
  "% context left"
155
181
  ] }) }),
182
+ creditBalance !== null && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_ink.Box, { children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_ink.Text, { color: "gray", children: [
183
+ "$",
184
+ creditBalance.toFixed(2),
185
+ " credits"
186
+ ] }) }),
156
187
  chatGPTRateLimitHint && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_ink.Box, { children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_ink.Text, { color: chatGPTRateLimitColor, children: chatGPTRateLimitHint }) }),
157
188
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_ink.Box, { children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_ink.Text, { color: "gray", children: [
158
189
  "approvals: ",
@@ -1 +1 @@
1
- {"version":3,"file":"Footer.d.ts","sourceRoot":"","sources":["../../src/components/Footer.tsx"],"names":[],"mappings":"AAAA;;;GAGG;AAMH,OAAO,EAAqB,KAAK,sBAAsB,EAAE,MAAM,aAAa,CAAC;AAM7E,OAAO,EAAwB,KAAK,eAAe,EAAE,MAAM,qBAAqB,CAAC;AAEjF,UAAU,WAAW;IACjB,KAAK,EAAE,eAAe,CAAC;IACvB,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,SAAS,EAAE,MAAM,CAAC;IAClB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAC3B,iBAAiB,CAAC,EAAE,OAAO,CAAC;IAC5B,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,kEAAkE;IAClE,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,sBAAsB,CAAC,EAAE,sBAAsB,GAAG,IAAI,CAAC;CAC1D;AAED;;GAEG;AACH,wBAAgB,MAAM,CAAC,EACnB,KAAK,EACL,SAAS,EACT,SAAS,EACT,GAAG,EACH,UAAU,EACV,gBAAgB,EAChB,iBAAiB,EACjB,cAAc,EACd,WAAW,EACX,sBAAsB,GACzB,EAAE,WAAW,2CAkLb"}
1
+ {"version":3,"file":"Footer.d.ts","sourceRoot":"","sources":["../../src/components/Footer.tsx"],"names":[],"mappings":"AAAA;;;GAGG;AAMH,OAAO,EAAqB,KAAK,sBAAsB,EAAE,MAAM,aAAa,CAAC;AAM7E,OAAO,EAAwB,KAAK,eAAe,EAAE,MAAM,qBAAqB,CAAC;AAGjF,UAAU,WAAW;IACjB,KAAK,EAAE,eAAe,CAAC;IACvB,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,SAAS,EAAE,MAAM,CAAC;IAClB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAC3B,iBAAiB,CAAC,EAAE,OAAO,CAAC;IAC5B,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,kEAAkE;IAClE,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,sBAAsB,CAAC,EAAE,sBAAsB,GAAG,IAAI,CAAC;CAC1D;AAED;;GAEG;AACH,wBAAgB,MAAM,CAAC,EACnB,KAAK,EACL,SAAS,EACT,SAAS,EACT,GAAG,EACH,UAAU,EACV,gBAAgB,EAChB,iBAAiB,EACjB,cAAc,EACd,WAAW,EACX,sBAAsB,GACzB,EAAE,WAAW,2CAqNb"}
@@ -10,6 +10,7 @@ import {
10
10
  shouldShowChatGPTRateLimitHint
11
11
  } from "../utils/chatgpt-rate-limit.js";
12
12
  import { supportsContextStats } from "../agent-backend.js";
13
+ import { dextoCreditBalance } from "../host/index.js";
13
14
  function Footer({
14
15
  agent,
15
16
  sessionId,
@@ -95,6 +96,31 @@ function Footer({
95
96
  controller.abort();
96
97
  };
97
98
  }, [agent, sessionId]);
99
+ const [creditBalance, setCreditBalance] = useState(null);
100
+ const usesDextoCredits = provider === "dexto-nova";
101
+ useEffect(() => {
102
+ setCreditBalance(null);
103
+ if (!usesDextoCredits) return;
104
+ let cancelled = false;
105
+ const refresh = () => {
106
+ dextoCreditBalance().then((balance) => {
107
+ if (!cancelled) setCreditBalance(balance);
108
+ }).catch(() => void 0);
109
+ };
110
+ refresh();
111
+ const controller = new AbortController();
112
+ agent.on(
113
+ "llm:response",
114
+ (payload) => {
115
+ if (!payload.sessionId || payload.sessionId === sessionId) refresh();
116
+ },
117
+ { signal: controller.signal }
118
+ );
119
+ return () => {
120
+ cancelled = true;
121
+ controller.abort();
122
+ };
123
+ }, [agent, sessionId, usesDextoCredits]);
98
124
  const pathColor = isShellMode ? "yellow" : "blue";
99
125
  return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", paddingX: 1, children: [
100
126
  /* @__PURE__ */ jsxs(Box, { flexDirection: "row", justifyContent: "space-between", children: [
@@ -123,6 +149,11 @@ function Footer({
123
149
  contextLeft.percentLeft,
124
150
  "% context left"
125
151
  ] }) }),
152
+ creditBalance !== null && /* @__PURE__ */ jsx(Box, { children: /* @__PURE__ */ jsxs(Text, { color: "gray", children: [
153
+ "$",
154
+ creditBalance.toFixed(2),
155
+ " credits"
156
+ ] }) }),
126
157
  chatGPTRateLimitHint && /* @__PURE__ */ jsx(Box, { children: /* @__PURE__ */ jsx(Text, { color: chatGPTRateLimitColor, children: chatGPTRateLimitHint }) }),
127
158
  /* @__PURE__ */ jsx(Box, { children: /* @__PURE__ */ jsxs(Text, { color: "gray", children: [
128
159
  "approvals: ",
@@ -4,6 +4,7 @@ var import_node_stream = require("node:stream");
4
4
  var import_ink = require("ink");
5
5
  var import_vitest = require("vitest");
6
6
  var import_Footer = require("./Footer.js");
7
+ var import_host = require("../host/index.js");
7
8
  (0, import_vitest.describe)("configured approval policy in the TUI footer", () => {
8
9
  import_vitest.it.each(["manual", "auto-approve"])(
9
10
  "shows %s independently of session shortcuts",
@@ -45,3 +46,46 @@ var import_Footer = require("./Footer.js");
45
46
  }
46
47
  );
47
48
  });
49
+ (0, import_vitest.describe)("Dexto credits in the TUI footer", () => {
50
+ async function renderFooter(provider, balance) {
51
+ (0, import_host.setTuiRuntimeServices)({ dextoCreditBalance: async () => balance });
52
+ const stdout = Object.assign(new import_node_stream.PassThrough(), { columns: 120, rows: 40, isTTY: false });
53
+ let output = "";
54
+ stdout.on("data", (chunk) => {
55
+ output += String(chunk);
56
+ });
57
+ const agent = {
58
+ capabilities: { contextStats: false },
59
+ getEffectiveConfig: () => ({ permissions: { mode: "manual" } }),
60
+ getCurrentLLMConfig: () => ({ provider, model: "openai/gpt-5" }),
61
+ on: () => void 0
62
+ };
63
+ const view = (0, import_ink.render)(
64
+ (0, import_react.createElement)(import_Footer.Footer, { agent, sessionId: "session-1", modelName: "openai/gpt-5" }),
65
+ {
66
+ stdout,
67
+ debug: true,
68
+ patchConsole: false,
69
+ exitOnCtrlC: false
70
+ }
71
+ );
72
+ try {
73
+ await import_vitest.vi.waitFor(() => (0, import_vitest.expect)(output).toContain("approvals: manual"));
74
+ await new Promise((resolve) => setTimeout(resolve, 20));
75
+ return output;
76
+ } finally {
77
+ view.unmount();
78
+ stdout.destroy();
79
+ (0, import_host.setTuiRuntimeServices)({});
80
+ }
81
+ }
82
+ (0, import_vitest.it)("shows the balance while a Dexto model runs, under the name Dexto", async () => {
83
+ const output = await renderFooter("dexto-nova", 12.5);
84
+ (0, import_vitest.expect)(output).toContain("$12.50 credits");
85
+ (0, import_vitest.expect)(output).toContain("(Dexto)");
86
+ });
87
+ (0, import_vitest.it)("shows no balance for other providers", async () => {
88
+ const output = await renderFooter("openai", 12.5);
89
+ (0, import_vitest.expect)(output).not.toContain("credits");
90
+ });
91
+ });
@@ -3,6 +3,7 @@ import { PassThrough } from "node:stream";
3
3
  import { render } from "ink";
4
4
  import { describe, expect, it, vi } from "vitest";
5
5
  import { Footer } from "./Footer.js";
6
+ import { setTuiRuntimeServices } from "../host/index.js";
6
7
  describe("configured approval policy in the TUI footer", () => {
7
8
  it.each(["manual", "auto-approve"])(
8
9
  "shows %s independently of session shortcuts",
@@ -44,3 +45,46 @@ describe("configured approval policy in the TUI footer", () => {
44
45
  }
45
46
  );
46
47
  });
48
+ describe("Dexto credits in the TUI footer", () => {
49
+ async function renderFooter(provider, balance) {
50
+ setTuiRuntimeServices({ dextoCreditBalance: async () => balance });
51
+ const stdout = Object.assign(new PassThrough(), { columns: 120, rows: 40, isTTY: false });
52
+ let output = "";
53
+ stdout.on("data", (chunk) => {
54
+ output += String(chunk);
55
+ });
56
+ const agent = {
57
+ capabilities: { contextStats: false },
58
+ getEffectiveConfig: () => ({ permissions: { mode: "manual" } }),
59
+ getCurrentLLMConfig: () => ({ provider, model: "openai/gpt-5" }),
60
+ on: () => void 0
61
+ };
62
+ const view = render(
63
+ createElement(Footer, { agent, sessionId: "session-1", modelName: "openai/gpt-5" }),
64
+ {
65
+ stdout,
66
+ debug: true,
67
+ patchConsole: false,
68
+ exitOnCtrlC: false
69
+ }
70
+ );
71
+ try {
72
+ await vi.waitFor(() => expect(output).toContain("approvals: manual"));
73
+ await new Promise((resolve) => setTimeout(resolve, 20));
74
+ return output;
75
+ } finally {
76
+ view.unmount();
77
+ stdout.destroy();
78
+ setTuiRuntimeServices({});
79
+ }
80
+ }
81
+ it("shows the balance while a Dexto model runs, under the name Dexto", async () => {
82
+ const output = await renderFooter("dexto-nova", 12.5);
83
+ expect(output).toContain("$12.50 credits");
84
+ expect(output).toContain("(Dexto)");
85
+ });
86
+ it("shows no balance for other providers", async () => {
87
+ const output = await renderFooter("openai", 12.5);
88
+ expect(output).not.toContain("credits");
89
+ });
90
+ });
@@ -49,6 +49,11 @@ const TOP_UP_OPTIONS = [
49
49
  id: "open-billing",
50
50
  label: "Open billing page",
51
51
  description: "View full billing dashboard"
52
+ },
53
+ {
54
+ id: "own-key",
55
+ label: "Use your own model key",
56
+ description: "Pick a model from a provider you have a key for"
52
57
  }
53
58
  ];
54
59
  const WAITING_OPTIONS = [
@@ -77,7 +82,7 @@ function formatBalance(balanceUsd) {
77
82
  function getErrorMessage(error) {
78
83
  return error instanceof Error ? error.message : String(error);
79
84
  }
80
- const InsufficientCreditsOverlay = (0, import_react.forwardRef)(function InsufficientCreditsOverlay2({ isVisible, initialBalanceUsd, onResolved, onClose }, ref) {
85
+ const InsufficientCreditsOverlay = (0, import_react.forwardRef)(function InsufficientCreditsOverlay2({ isVisible, initialBalanceUsd, onResolved, onClose, onChooseOwnModel }, ref) {
81
86
  const selectorRef = (0, import_react.useRef)(null);
82
87
  const isActiveRef = (0, import_react.useRef)(false);
83
88
  const [step, setStep] = (0, import_react.useState)("select");
@@ -190,6 +195,9 @@ const InsufficientCreditsOverlay = (0, import_react.forwardRef)(function Insuffi
190
195
  case "open-billing":
191
196
  void openBillingDashboard();
192
197
  return;
198
+ case "own-key":
199
+ onChooseOwnModel();
200
+ return;
193
201
  case "reopen":
194
202
  void reopenBilling();
195
203
  return;
@@ -201,7 +209,14 @@ const InsufficientCreditsOverlay = (0, import_react.forwardRef)(function Insuffi
201
209
  return;
202
210
  }
203
211
  },
204
- [onResolved, openBillingDashboard, openPrefilledBilling, reopenBilling, resetToSelection]
212
+ [
213
+ onChooseOwnModel,
214
+ onResolved,
215
+ openBillingDashboard,
216
+ openPrefilledBilling,
217
+ reopenBilling,
218
+ resetToSelection
219
+ ]
205
220
  );
206
221
  const formatItem = (0, import_react.useCallback)((option, isSelected) => {
207
222
  return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
@@ -228,9 +243,9 @@ const InsufficientCreditsOverlay = (0, import_react.forwardRef)(function Insuffi
228
243
  return null;
229
244
  }
230
245
  return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_ink.Box, { flexDirection: "column", children: [
231
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_ink.Box, { children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_ink.Text, { color: "yellow", bold: true, children: "Out of Dexto Nova credits" }) }),
246
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_ink.Box, { children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_ink.Text, { color: "yellow", bold: true, children: "Out of Dexto credits" }) }),
232
247
  /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_ink.Box, { flexDirection: "column", marginTop: 1, children: [
233
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_ink.Text, { color: "gray", children: "This request stopped because your Dexto Nova balance ran out." }),
248
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_ink.Text, { color: "gray", children: "This request stopped because your Dexto credits ran out." }),
234
249
  formatBalance(balanceUsd) && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_ink.Text, { color: "gray", children: [
235
250
  "Current balance: ",
236
251
  formatBalance(balanceUsd)
@@ -5,6 +5,8 @@ export interface InsufficientCreditsOverlayProps {
5
5
  initialBalanceUsd: number | null;
6
6
  onResolved: () => void;
7
7
  onClose: () => void;
8
+ /** Leaves credits for a model on the person's own key. */
9
+ onChooseOwnModel: () => void;
8
10
  }
9
11
  export interface InsufficientCreditsOverlayHandle {
10
12
  handleInput: (input: string, key: Key) => boolean;
@@ -1 +1 @@
1
- {"version":3,"file":"InsufficientCreditsOverlay.d.ts","sourceRoot":"","sources":["../../../src/components/overlays/InsufficientCreditsOverlay.tsx"],"names":[],"mappings":"AAAA,OAAO,KAQN,MAAM,OAAO,CAAC;AAEf,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,qCAAqC,CAAC;AA6E/D,MAAM,WAAW,+BAA+B;IAC5C,SAAS,EAAE,OAAO,CAAC;IACnB,iBAAiB,EAAE,MAAM,GAAG,IAAI,CAAC;IACjC,UAAU,EAAE,MAAM,IAAI,CAAC;IACvB,OAAO,EAAE,MAAM,IAAI,CAAC;CACvB;AAED,MAAM,WAAW,gCAAgC;IAC7C,WAAW,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,EAAE,GAAG,KAAK,OAAO,CAAC;CACrD;AAED,QAAA,MAAM,0BAA0B,0HAiO9B,CAAC;AAEH,eAAe,0BAA0B,CAAC"}
1
+ {"version":3,"file":"InsufficientCreditsOverlay.d.ts","sourceRoot":"","sources":["../../../src/components/overlays/InsufficientCreditsOverlay.tsx"],"names":[],"mappings":"AAAA,OAAO,KAQN,MAAM,OAAO,CAAC;AAEf,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,qCAAqC,CAAC;AAmF/D,MAAM,WAAW,+BAA+B;IAC5C,SAAS,EAAE,OAAO,CAAC;IACnB,iBAAiB,EAAE,MAAM,GAAG,IAAI,CAAC;IACjC,UAAU,EAAE,MAAM,IAAI,CAAC;IACvB,OAAO,EAAE,MAAM,IAAI,CAAC;IACpB,0DAA0D;IAC1D,gBAAgB,EAAE,MAAM,IAAI,CAAC;CAChC;AAED,MAAM,WAAW,gCAAgC;IAC7C,WAAW,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,EAAE,GAAG,KAAK,OAAO,CAAC;CACrD;AAED,QAAA,MAAM,0BAA0B,0HA4O9B,CAAC;AAEH,eAAe,0BAA0B,CAAC"}
@@ -34,6 +34,11 @@ const TOP_UP_OPTIONS = [
34
34
  id: "open-billing",
35
35
  label: "Open billing page",
36
36
  description: "View full billing dashboard"
37
+ },
38
+ {
39
+ id: "own-key",
40
+ label: "Use your own model key",
41
+ description: "Pick a model from a provider you have a key for"
37
42
  }
38
43
  ];
39
44
  const WAITING_OPTIONS = [
@@ -62,7 +67,7 @@ function formatBalance(balanceUsd) {
62
67
  function getErrorMessage(error) {
63
68
  return error instanceof Error ? error.message : String(error);
64
69
  }
65
- const InsufficientCreditsOverlay = forwardRef(function InsufficientCreditsOverlay2({ isVisible, initialBalanceUsd, onResolved, onClose }, ref) {
70
+ const InsufficientCreditsOverlay = forwardRef(function InsufficientCreditsOverlay2({ isVisible, initialBalanceUsd, onResolved, onClose, onChooseOwnModel }, ref) {
66
71
  const selectorRef = useRef(null);
67
72
  const isActiveRef = useRef(false);
68
73
  const [step, setStep] = useState("select");
@@ -175,6 +180,9 @@ const InsufficientCreditsOverlay = forwardRef(function InsufficientCreditsOverla
175
180
  case "open-billing":
176
181
  void openBillingDashboard();
177
182
  return;
183
+ case "own-key":
184
+ onChooseOwnModel();
185
+ return;
178
186
  case "reopen":
179
187
  void reopenBilling();
180
188
  return;
@@ -186,7 +194,14 @@ const InsufficientCreditsOverlay = forwardRef(function InsufficientCreditsOverla
186
194
  return;
187
195
  }
188
196
  },
189
- [onResolved, openBillingDashboard, openPrefilledBilling, reopenBilling, resetToSelection]
197
+ [
198
+ onChooseOwnModel,
199
+ onResolved,
200
+ openBillingDashboard,
201
+ openPrefilledBilling,
202
+ reopenBilling,
203
+ resetToSelection
204
+ ]
190
205
  );
191
206
  const formatItem = useCallback((option, isSelected) => {
192
207
  return /* @__PURE__ */ jsxs(Fragment, { children: [
@@ -213,9 +228,9 @@ const InsufficientCreditsOverlay = forwardRef(function InsufficientCreditsOverla
213
228
  return null;
214
229
  }
215
230
  return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", children: [
216
- /* @__PURE__ */ jsx(Box, { children: /* @__PURE__ */ jsx(Text, { color: "yellow", bold: true, children: "Out of Dexto Nova credits" }) }),
231
+ /* @__PURE__ */ jsx(Box, { children: /* @__PURE__ */ jsx(Text, { color: "yellow", bold: true, children: "Out of Dexto credits" }) }),
217
232
  /* @__PURE__ */ jsxs(Box, { flexDirection: "column", marginTop: 1, children: [
218
- /* @__PURE__ */ jsx(Text, { color: "gray", children: "This request stopped because your Dexto Nova balance ran out." }),
233
+ /* @__PURE__ */ jsx(Text, { color: "gray", children: "This request stopped because your Dexto credits ran out." }),
219
234
  formatBalance(balanceUsd) && /* @__PURE__ */ jsxs(Text, { color: "gray", children: [
220
235
  "Current balance: ",
221
236
  formatBalance(balanceUsd)
@@ -28,9 +28,8 @@ var import_host = require("../../host/index.js");
28
28
  const LoginOverlay = (0, import_react.forwardRef)(function LoginOverlay2({ isVisible, onDone }, ref) {
29
29
  const [step, setStep] = (0, import_react.useState)("checking");
30
30
  const [existingUser, setExistingUser] = (0, import_react.useState)(null);
31
- const [authUrl, setAuthUrl] = (0, import_react.useState)(null);
32
- const [devicePrompt, setDevicePrompt] = (0, import_react.useState)(null);
33
- const [status, setStatus] = (0, import_react.useState)("Preparing login...");
31
+ const [signInNote, setSignInNote] = (0, import_react.useState)(null);
32
+ const [status, setStatus] = (0, import_react.useState)("Preparing sign-in...");
34
33
  const [error, setError] = (0, import_react.useState)(null);
35
34
  const abortControllerRef = (0, import_react.useRef)(null);
36
35
  const isActiveRef = (0, import_react.useRef)(false);
@@ -42,47 +41,24 @@ const LoginOverlay = (0, import_react.forwardRef)(function LoginOverlay2({ isVis
42
41
  abortControllerRef.current?.abort(new Error("Authentication cancelled"));
43
42
  abortControllerRef.current = null;
44
43
  }, []);
45
- const runDeviceLogin = (0, import_react.useCallback)(
46
- async (abortController) => {
47
- setStep("starting");
48
- safeSetStatus("Starting device code login...");
49
- setAuthUrl(null);
50
- setDevicePrompt(null);
51
- return (0, import_host.performDeviceCodeLogin)({
52
- signal: abortController.signal,
53
- onPrompt: (prompt) => {
54
- if (!isActiveRef.current || abortController.signal.aborted) {
55
- return;
56
- }
57
- setDevicePrompt(prompt);
58
- setAuthUrl(prompt.verificationUrlComplete ?? prompt.verificationUrl);
59
- setStep("waiting");
60
- safeSetStatus("Open the URL below and approve login.");
61
- }
62
- });
63
- },
64
- [safeSetStatus]
65
- );
66
44
  const startLogin = (0, import_react.useCallback)(async () => {
67
45
  cancelInFlight();
68
46
  setError(null);
69
- setAuthUrl(null);
70
- setDevicePrompt(null);
47
+ setSignInNote(null);
48
+ setStep("signing-in");
49
+ safeSetStatus("Signing in...");
71
50
  const abortController = new AbortController();
72
51
  abortControllerRef.current = abortController;
73
52
  try {
74
- const result = await runDeviceLogin(abortController);
75
- if (!result || !isActiveRef.current || abortController.signal.aborted) return;
76
- setStep("finalizing");
77
- safeSetStatus("Finalizing login...");
78
- const persisted = await (0, import_host.persistDeviceApiKeyLoginResult)(result);
79
- if (!isActiveRef.current || abortController.signal.aborted) return;
80
- onDone({
81
- outcome: "success",
82
- email: persisted.email,
83
- keyId: persisted.keyId,
84
- hasDextoApiKey: persisted.hasDextoApiKey
53
+ const summary = await (0, import_host.signIn)({
54
+ signal: abortController.signal,
55
+ onNote: (text) => {
56
+ if (!isActiveRef.current || abortController.signal.aborted) return;
57
+ setSignInNote(text);
58
+ }
85
59
  });
60
+ if (!isActiveRef.current || abortController.signal.aborted) return;
61
+ onDone({ outcome: "success", summary });
86
62
  } catch (err) {
87
63
  if (abortController.signal.aborted) {
88
64
  return;
@@ -90,28 +66,27 @@ const LoginOverlay = (0, import_react.forwardRef)(function LoginOverlay2({ isVis
90
66
  setStep("error");
91
67
  const errorMessage = err instanceof Error ? err.message : String(err);
92
68
  setError(errorMessage);
93
- safeSetStatus("Login failed");
69
+ safeSetStatus("Sign-in failed");
94
70
  } finally {
95
71
  abortControllerRef.current = null;
96
72
  }
97
- }, [cancelInFlight, onDone, runDeviceLogin, safeSetStatus]);
73
+ }, [cancelInFlight, onDone, safeSetStatus]);
98
74
  (0, import_react.useEffect)(() => {
99
75
  if (!isVisible) return;
100
76
  isActiveRef.current = true;
101
77
  setStep("checking");
102
78
  setExistingUser(null);
103
- setAuthUrl(null);
104
- setDevicePrompt(null);
105
- setStatus("Preparing login...");
79
+ setSignInNote(null);
80
+ setStatus("Preparing sign-in...");
106
81
  setError(null);
107
82
  void (async () => {
108
83
  try {
109
- const auth = await (0, import_host.loadAuth)();
84
+ const signedInAs = await (0, import_host.currentSignIn)();
110
85
  if (!isActiveRef.current) return;
111
- if (auth) {
112
- setExistingUser(auth.email || auth.userId || "user");
86
+ if (signedInAs !== null) {
87
+ setExistingUser(signedInAs);
113
88
  setStep("already-authenticated");
114
- setStatus("You are already logged in");
89
+ setStatus("You are already signed in");
115
90
  return;
116
91
  }
117
92
  await startLogin();
@@ -151,7 +126,7 @@ const LoginOverlay = (0, import_react.forwardRef)(function LoginOverlay2({ isVis
151
126
  [cancelInFlight, isVisible, onDone, startLogin, step]
152
127
  );
153
128
  if (!isVisible) return null;
154
- const hint = step === "already-authenticated" ? "Enter to login again \u2022 Esc to close" : step === "error" ? "Enter to retry \u2022 Esc to close" : "Esc to cancel";
129
+ const hint = step === "already-authenticated" ? "Enter to sign in again \u2022 Esc to close" : step === "error" ? "Enter to retry \u2022 Esc to close" : "Esc to cancel";
155
130
  return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
156
131
  import_ink.Box,
157
132
  {
@@ -161,27 +136,16 @@ const LoginOverlay = (0, import_react.forwardRef)(function LoginOverlay2({ isVis
161
136
  paddingX: 1,
162
137
  marginTop: 1,
163
138
  children: [
164
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_ink.Box, { marginBottom: 1, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_ink.Text, { bold: true, color: "cyan", children: "Login to Dexto" }) }),
139
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_ink.Box, { marginBottom: 1, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_ink.Text, { bold: true, color: "cyan", children: "Sign in to Dexto" }) }),
165
140
  existingUser && step === "already-authenticated" && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_ink.Box, { marginBottom: 1, children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_ink.Text, { color: "green", children: [
166
- "\u2705 Already logged in as: ",
141
+ "Signed in as ",
167
142
  existingUser
168
143
  ] }) }),
169
144
  /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_ink.Box, { marginBottom: 1, children: [
170
145
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_ink.Text, { color: "gray", children: "Status: " }),
171
146
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_ink.Text, { children: status })
172
147
  ] }),
173
- devicePrompt && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_ink.Box, { flexDirection: "column", marginBottom: 1, children: [
174
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_ink.Text, { color: "gray", children: "Open this URL on any device:" }),
175
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_ink.Text, { color: "yellowBright", children: devicePrompt.verificationUrlComplete ?? devicePrompt.verificationUrl }),
176
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_ink.Text, { color: "gray", children: [
177
- "Code: ",
178
- devicePrompt.userCode
179
- ] })
180
- ] }),
181
- authUrl && !devicePrompt && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_ink.Box, { flexDirection: "column", marginBottom: 1, children: [
182
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_ink.Text, { color: "gray", children: "If your browser didn't open, use this URL:" }),
183
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_ink.Text, { color: "yellowBright", children: authUrl })
184
- ] }),
148
+ signInNote && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_ink.Box, { flexDirection: "column", marginBottom: 1, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_ink.Text, { children: signInNote }) }),
185
149
  error && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_ink.Box, { flexDirection: "column", marginBottom: 1, children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_ink.Text, { color: "red", children: [
186
150
  "Error: ",
187
151
  error
@@ -2,9 +2,7 @@ import React from 'react';
2
2
  import type { Key } from '../../hooks/useInputOrchestrator.js';
3
3
  export type LoginOverlayOutcome = {
4
4
  outcome: 'success';
5
- email?: string | undefined;
6
- keyId?: string | undefined;
7
- hasDextoApiKey: boolean;
5
+ summary: string;
8
6
  } | {
9
7
  outcome: 'cancelled';
10
8
  } | {
@@ -1 +1 @@
1
- {"version":3,"file":"LoginOverlay.d.ts","sourceRoot":"","sources":["../../../src/components/overlays/LoginOverlay.tsx"],"names":[],"mappings":"AAAA,OAAO,KAON,MAAM,OAAO,CAAC;AAEf,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,qCAAqC,CAAC;AAS/D,MAAM,MAAM,mBAAmB,GACzB;IACI,OAAO,EAAE,SAAS,CAAC;IACnB,KAAK,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC3B,KAAK,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC3B,cAAc,EAAE,OAAO,CAAC;CAC3B,GACD;IAAE,OAAO,EAAE,WAAW,CAAA;CAAE,GACxB;IAAE,OAAO,EAAE,QAAQ,CAAA;CAAE,CAAC;AAE5B,MAAM,WAAW,iBAAiB;IAC9B,SAAS,EAAE,OAAO,CAAC;IACnB,MAAM,EAAE,CAAC,OAAO,EAAE,mBAAmB,KAAK,IAAI,CAAC;CAClD;AAED,MAAM,WAAW,kBAAkB;IAC/B,WAAW,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,EAAE,GAAG,KAAK,OAAO,CAAC;CACrD;AAUD,QAAA,MAAM,YAAY,8FA0NhB,CAAC;AAEH,eAAe,YAAY,CAAC"}
1
+ {"version":3,"file":"LoginOverlay.d.ts","sourceRoot":"","sources":["../../../src/components/overlays/LoginOverlay.tsx"],"names":[],"mappings":"AAAA,OAAO,KAON,MAAM,OAAO,CAAC;AAEf,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,qCAAqC,CAAC;AAG/D,MAAM,MAAM,mBAAmB,GACzB;IAAE,OAAO,EAAE,SAAS,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,GACvC;IAAE,OAAO,EAAE,WAAW,CAAA;CAAE,GACxB;IAAE,OAAO,EAAE,QAAQ,CAAA;CAAE,CAAC;AAE5B,MAAM,WAAW,iBAAiB;IAC9B,SAAS,EAAE,OAAO,CAAC;IACnB,MAAM,EAAE,CAAC,OAAO,EAAE,mBAAmB,KAAK,IAAI,CAAC;CAClD;AAED,MAAM,WAAW,kBAAkB;IAC/B,WAAW,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,EAAE,GAAG,KAAK,OAAO,CAAC;CACrD;AAID,QAAA,MAAM,YAAY,8FAiLhB,CAAC;AAEH,eAAe,YAAY,CAAC"}