@tiens.nguyen/gu-cli 1.0.886 → 1.0.888
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.
- package/budget-view.mjs +22 -8
- package/gu-repl.mjs +8 -1
- package/package.json +1 -1
package/budget-view.mjs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* gu `/budget` (#205): the SHORT terminal version of web-v2's /providers budgets.
|
|
3
3
|
*
|
|
4
|
-
* Data: GET {apiBase}/api/worker/providers-budget with the
|
|
4
|
+
* Data: GET {hosted}/api/billing/cli-token/providers-budget with gu's token (else {apiBase}/api/worker/providers-budget with the worker key). The server decides
|
|
5
5
|
* Pro / email / "has items" (api/src/providers/terminalBudget.ts); this module only turns its answer
|
|
6
6
|
* into plain lines. Pure (fetch is injected) so every message is testable without a network.
|
|
7
7
|
*/
|
|
@@ -11,15 +11,11 @@ export const BUDGET_TIMEOUT_MS = 15_000;
|
|
|
11
11
|
/** A web URL for a page under `base` — a URL, so never path.join (that eats the "//"; #205). */
|
|
12
12
|
const pageUrl = (base, path) => new URL(path, `${base}/`).href;
|
|
13
13
|
|
|
14
|
-
/** -> { kind: "
|
|
15
|
-
|
|
16
|
-
if (!String(workerKey ?? "").trim() || !String(apiBase ?? "").trim()) return { kind: "not_logged_in" };
|
|
14
|
+
/** One GET -> { kind: "relogin" | "http" | "offline" | "ok", status?, body? } */
|
|
15
|
+
async function getBudget(url, headers, fetchImpl, timeoutMs) {
|
|
17
16
|
let r;
|
|
18
17
|
try {
|
|
19
|
-
r = await fetchImpl(
|
|
20
|
-
headers: { "X-Worker-Key": String(workerKey).trim() },
|
|
21
|
-
signal: AbortSignal.timeout(timeoutMs),
|
|
22
|
-
});
|
|
18
|
+
r = await fetchImpl(url, { headers, signal: AbortSignal.timeout(timeoutMs) });
|
|
23
19
|
} catch (e) {
|
|
24
20
|
return { kind: "offline", error: e instanceof Error ? e.message : String(e) };
|
|
25
21
|
}
|
|
@@ -30,6 +26,24 @@ export async function fetchProvidersBudget({ apiBase, workerKey, fetchImpl = fet
|
|
|
30
26
|
return { kind: "ok", status: r.status, body };
|
|
31
27
|
}
|
|
32
28
|
|
|
29
|
+
/**
|
|
30
|
+
* -> { kind: "not_logged_in" | "relogin" | "http" | "offline" | "ok", status?, body? }
|
|
31
|
+
*
|
|
32
|
+
* The account (Pro, email, provider items) lives on the HOSTED API. A computer in Local mode has its
|
|
33
|
+
* own API with none of that, so when gu has a token it asks the hosted API with the token
|
|
34
|
+
* (/api/billing/cli-token/providers-budget). Without a token, or if that server predates the route
|
|
35
|
+
* (404), it falls back to this machine's own API with the worker key.
|
|
36
|
+
*/
|
|
37
|
+
export async function fetchProvidersBudget({ apiBase, workerKey, hostedBase, token, fetchImpl = fetch, timeoutMs = BUDGET_TIMEOUT_MS }) {
|
|
38
|
+
const hosted = String(hostedBase ?? "").trim().replace(/\/+$/, "");
|
|
39
|
+
if (String(token ?? "").trim() && hosted) {
|
|
40
|
+
const r = await getBudget(`${hosted}/api/billing/cli-token/providers-budget`, { authorization: `Bearer ${String(token).trim()}` }, fetchImpl, timeoutMs);
|
|
41
|
+
if (!(r.kind === "http" && r.status === 404)) return r;
|
|
42
|
+
}
|
|
43
|
+
if (!String(workerKey ?? "").trim() || !String(apiBase ?? "").trim()) return { kind: "not_logged_in" };
|
|
44
|
+
return getBudget(`${String(apiBase).replace(/\/+$/, "")}/api/worker/providers-budget`, { "X-Worker-Key": String(workerKey).trim() }, fetchImpl, timeoutMs);
|
|
45
|
+
}
|
|
46
|
+
|
|
33
47
|
const money = (n, cur) => {
|
|
34
48
|
if (typeof n !== "number" || !Number.isFinite(n)) return "—";
|
|
35
49
|
const c = String(cur || "USD").toUpperCase();
|
package/gu-repl.mjs
CHANGED
|
@@ -15315,7 +15315,14 @@ async function main() {
|
|
|
15315
15315
|
try {
|
|
15316
15316
|
const bv = await import("./budget-view.mjs");
|
|
15317
15317
|
const ct = await import("./cli-token.mjs");
|
|
15318
|
-
const
|
|
15318
|
+
const { DEFAULT_API_BASE } = await import("./device-login.mjs");
|
|
15319
|
+
const state = await ct.readState(join(homedir(), ".gonext"));
|
|
15320
|
+
const result = await bv.fetchProvidersBudget({
|
|
15321
|
+
apiBase,
|
|
15322
|
+
workerKey,
|
|
15323
|
+
hostedBase: String(process.env.GU_TOKEN_API_BASE ?? "").trim() || DEFAULT_API_BASE,
|
|
15324
|
+
token: state.token,
|
|
15325
|
+
});
|
|
15319
15326
|
const out = bv.budgetLines(result, { site: ct.siteUrl() });
|
|
15320
15327
|
for (const l of out.lines) console.log(GUTTER + (out.tone === "ok" ? l : dim(l)));
|
|
15321
15328
|
console.log("");
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tiens.nguyen/gu-cli",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.888",
|
|
4
4
|
"description": "gu: the coding agent in your terminal. It reads your repo, makes the change and runs the tests, with your own models (Ollama, MLX or any OpenAI-compatible provider). macOS, Linux and Windows.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"ai",
|