clixad 0.0.1-beta.30 → 0.0.1-beta.32
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/README.md +4 -0
- package/dist/clixad.mjs +341 -40
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -15,6 +15,10 @@ cover it, for example `task ≈ 560–3.1k · free` or `task ≈ 7.5k–47k · n
|
|
|
15
15
|
estimate from the size of the conversation and how tasks usually run; what is charged is what the
|
|
16
16
|
model actually used.
|
|
17
17
|
|
|
18
|
+
The same account also works in the browser, at <https://clixad.onrender.com/chat>: sign in with the
|
|
19
|
+
GitHub account you use here. A model, a reasoning setting, your name and instructions set there or on
|
|
20
|
+
its account page hold here too (from 0.0.1-beta.31), and `/model` and `/effort` here hold there.
|
|
21
|
+
|
|
18
22
|
`/effort` sets how long the model thinks before it answers, saved separately for each model.
|
|
19
23
|
Thinking is billed as output tokens like the answer, so a higher setting costs more credits per turn
|
|
20
24
|
and a lower one costs less. `auto` leaves the model at its own default, and a model with nothing to
|
package/dist/clixad.mjs
CHANGED
|
@@ -324,6 +324,13 @@ function usd(v) {
|
|
|
324
324
|
function packsFromClause(cheapestUsd) {
|
|
325
325
|
return typeof cheapestUsd === "number" && cheapestUsd > 0 ? `, from ${usd(cheapestUsd)}` : "";
|
|
326
326
|
}
|
|
327
|
+
function parseSpendable(raw) {
|
|
328
|
+
if (!raw || typeof raw !== "object") return void 0;
|
|
329
|
+
const r = raw;
|
|
330
|
+
const ok = (v) => typeof v === "number" && Number.isFinite(v) && v >= 0;
|
|
331
|
+
if (!ok(r.budget) || !ok(r.premium) || !ok(r.budget_after_reset)) return void 0;
|
|
332
|
+
return { budget: r.budget, premium: r.premium, budgetAfterReset: r.budget_after_reset };
|
|
333
|
+
}
|
|
327
334
|
function usableStreak(streak) {
|
|
328
335
|
if (typeof streak !== "object" || streak === null) return void 0;
|
|
329
336
|
const s = streak;
|
|
@@ -432,6 +439,22 @@ function extraFormatIds(raw) {
|
|
|
432
439
|
}
|
|
433
440
|
return out;
|
|
434
441
|
}
|
|
442
|
+
function parseAccountSettings(body) {
|
|
443
|
+
if (!body || typeof body !== "object") return null;
|
|
444
|
+
const b = body;
|
|
445
|
+
const efforts = {};
|
|
446
|
+
if (b.efforts && typeof b.efforts === "object" && !Array.isArray(b.efforts)) {
|
|
447
|
+
for (const [k, v] of Object.entries(b.efforts)) if (typeof v === "string") efforts[k] = v;
|
|
448
|
+
}
|
|
449
|
+
const str = (v) => typeof v === "string" && v.trim() ? v : null;
|
|
450
|
+
return {
|
|
451
|
+
model: str(b.model),
|
|
452
|
+
efforts,
|
|
453
|
+
name: str(b.name),
|
|
454
|
+
instructions: str(b.instructions),
|
|
455
|
+
sponsorLine: typeof b.sponsor_line === "boolean" ? b.sponsor_line : null
|
|
456
|
+
};
|
|
457
|
+
}
|
|
435
458
|
function imagePart(image) {
|
|
436
459
|
return { type: "image_url", image_url: { url: `data:${image.mimeType};base64,${image.data}` } };
|
|
437
460
|
}
|
|
@@ -465,7 +488,11 @@ function asSignupClosed(status, data) {
|
|
|
465
488
|
const d = data;
|
|
466
489
|
return d && d.error === "signup_closed" && typeof d.message === "string" ? { error: d.error, message: d.message, waitlist_url: d.waitlist_url ?? "https://clixad.io" } : void 0;
|
|
467
490
|
}
|
|
468
|
-
function
|
|
491
|
+
function spendableAt(raw, balance) {
|
|
492
|
+
const figures = parseSpendable(raw);
|
|
493
|
+
return figures && typeof balance === "number" && Number.isFinite(balance) ? { figures, balance } : void 0;
|
|
494
|
+
}
|
|
495
|
+
function premiumLockedError(message, model, body, spendable) {
|
|
469
496
|
const num = (v) => typeof v === "number" && Number.isFinite(v) ? v : 0;
|
|
470
497
|
return new PremiumLockedError(
|
|
471
498
|
message,
|
|
@@ -475,7 +502,8 @@ function premiumLockedError(message, model, body) {
|
|
|
475
502
|
typeof body?.unlock_usd === "number" && body.unlock_usd > 0 ? body.unlock_usd : null,
|
|
476
503
|
num(body?.grant_per_ad),
|
|
477
504
|
freeAlternatives(body?.free_models),
|
|
478
|
-
usableStreak(body?.streak)
|
|
505
|
+
usableStreak(body?.streak),
|
|
506
|
+
spendable
|
|
479
507
|
);
|
|
480
508
|
}
|
|
481
509
|
function shortfallHeadline(err, model) {
|
|
@@ -500,9 +528,23 @@ function freeAlternatives(list) {
|
|
|
500
528
|
}
|
|
501
529
|
async function refusal402(res, model) {
|
|
502
530
|
const body = await res.json();
|
|
503
|
-
if (body.error === "premium_locked")
|
|
531
|
+
if (body.error === "premium_locked") {
|
|
532
|
+
return premiumLockedError(body.message ?? "", model, body.premium, spendableAt(body.spendable, body.balance));
|
|
533
|
+
}
|
|
504
534
|
return paywallError(body);
|
|
505
535
|
}
|
|
536
|
+
async function refusal429(res) {
|
|
537
|
+
const text = await res.text().catch(() => "");
|
|
538
|
+
try {
|
|
539
|
+
const body = JSON.parse(text);
|
|
540
|
+
if (typeof body.error === "string" && LIMIT_REFUSALS.has(body.error) && typeof body.message === "string") {
|
|
541
|
+
return new LimitRefusalError(body.message, body.error, spendableAt(body.spendable, body.balance));
|
|
542
|
+
}
|
|
543
|
+
} catch {
|
|
544
|
+
}
|
|
545
|
+
const detail = safeDetail(text);
|
|
546
|
+
return new Error(detail ? `chat failed: 429 \u2014 ${detail}` : "chat failed: 429");
|
|
547
|
+
}
|
|
506
548
|
function neverConnected(code) {
|
|
507
549
|
return code !== void 0 && NEVER_CONNECTED.has(code);
|
|
508
550
|
}
|
|
@@ -627,7 +669,7 @@ async function* parseSSE(body) {
|
|
|
627
669
|
}
|
|
628
670
|
}
|
|
629
671
|
}
|
|
630
|
-
var CHAT_ENCODED_CONTENT_TYPE, LEDGER_REASONS, SignupClosedError, PaywallError, PremiumLockedError, GatewayUnreachableError, NEVER_CONNECTED, AuthError, GatewayClient;
|
|
672
|
+
var CHAT_ENCODED_CONTENT_TYPE, LEDGER_REASONS, SignupClosedError, PaywallError, PremiumLockedError, LimitRefusalError, LIMIT_REFUSALS, GatewayUnreachableError, NEVER_CONNECTED, AuthError, GatewayClient;
|
|
631
673
|
var init_client = __esm({
|
|
632
674
|
"src/client.ts"() {
|
|
633
675
|
"use strict";
|
|
@@ -680,7 +722,7 @@ var init_client = __esm({
|
|
|
680
722
|
cheapestPackUsd;
|
|
681
723
|
};
|
|
682
724
|
PremiumLockedError = class extends Error {
|
|
683
|
-
constructor(message, model, neededCredits, premiumCredits, unlockUsd, grantPerAd, freeModels, streak) {
|
|
725
|
+
constructor(message, model, neededCredits, premiumCredits, unlockUsd, grantPerAd, freeModels, streak, spendable) {
|
|
684
726
|
super(message);
|
|
685
727
|
this.model = model;
|
|
686
728
|
this.neededCredits = neededCredits;
|
|
@@ -689,6 +731,7 @@ var init_client = __esm({
|
|
|
689
731
|
this.grantPerAd = grantPerAd;
|
|
690
732
|
this.freeModels = freeModels;
|
|
691
733
|
this.streak = streak;
|
|
734
|
+
this.spendable = spendable;
|
|
692
735
|
this.name = "PremiumLockedError";
|
|
693
736
|
}
|
|
694
737
|
model;
|
|
@@ -698,7 +741,19 @@ var init_client = __esm({
|
|
|
698
741
|
grantPerAd;
|
|
699
742
|
freeModels;
|
|
700
743
|
streak;
|
|
744
|
+
spendable;
|
|
701
745
|
};
|
|
746
|
+
LimitRefusalError = class extends Error {
|
|
747
|
+
constructor(message, type, spendable) {
|
|
748
|
+
super(message);
|
|
749
|
+
this.type = type;
|
|
750
|
+
this.spendable = spendable;
|
|
751
|
+
this.name = "LimitRefusalError";
|
|
752
|
+
}
|
|
753
|
+
type;
|
|
754
|
+
spendable;
|
|
755
|
+
};
|
|
756
|
+
LIMIT_REFUSALS = /* @__PURE__ */ new Set(["paid_user_limit_reached", "paid_budget_spent", "paid_budget_unavailable"]);
|
|
702
757
|
GatewayUnreachableError = class extends Error {
|
|
703
758
|
constructor(host, code) {
|
|
704
759
|
const where = `${host}${code ? ` (${code})` : ""}`;
|
|
@@ -840,6 +895,42 @@ var init_client = __esm({
|
|
|
840
895
|
* is "say nothing about a streak". A missed grant is claimed by the next
|
|
841
896
|
* command; a session that died over one would be a real loss.
|
|
842
897
|
*/
|
|
898
|
+
/**
|
|
899
|
+
* The account's preferences (gateway 0039), kept with the account so they
|
|
900
|
+
* hold in the browser's chat and here. Null when they cannot be read, and on
|
|
901
|
+
* a gateway too old to have them (404): a preference is never a reason for
|
|
902
|
+
* the REPL not to open.
|
|
903
|
+
*/
|
|
904
|
+
async getSettings(signal) {
|
|
905
|
+
try {
|
|
906
|
+
const res = await gatewayFetch(`${this.config.gatewayUrl}/v1/account/settings`, {
|
|
907
|
+
headers: this.headers(),
|
|
908
|
+
...signal ? { signal } : {}
|
|
909
|
+
});
|
|
910
|
+
if (!res.ok) return null;
|
|
911
|
+
return parseAccountSettings(await res.json());
|
|
912
|
+
} catch {
|
|
913
|
+
return null;
|
|
914
|
+
}
|
|
915
|
+
}
|
|
916
|
+
/**
|
|
917
|
+
* Change the preferences a request names. Best-effort and never throws: a
|
|
918
|
+
* model picked here is saved locally whatever the gateway says, and a
|
|
919
|
+
* failed sync costs only that the browser's chat does not follow.
|
|
920
|
+
*/
|
|
921
|
+
async saveSettings(change) {
|
|
922
|
+
try {
|
|
923
|
+
const res = await gatewayFetch(`${this.config.gatewayUrl}/v1/account/settings`, {
|
|
924
|
+
method: "PUT",
|
|
925
|
+
headers: { ...this.headers(), "Content-Type": "application/json" },
|
|
926
|
+
body: JSON.stringify(change)
|
|
927
|
+
});
|
|
928
|
+
if (!res.ok) return null;
|
|
929
|
+
return parseAccountSettings(await res.json());
|
|
930
|
+
} catch {
|
|
931
|
+
return null;
|
|
932
|
+
}
|
|
933
|
+
}
|
|
843
934
|
async claimStreak(signal) {
|
|
844
935
|
try {
|
|
845
936
|
const res = await gatewayFetch(`${this.config.gatewayUrl}/v1/streak/claim`, {
|
|
@@ -1361,16 +1452,19 @@ var init_client = __esm({
|
|
|
1361
1452
|
);
|
|
1362
1453
|
if (res.status === 401) throw await authFailure(res, "chat");
|
|
1363
1454
|
if (res.status === 402) throw await refusal402(res, model);
|
|
1455
|
+
if (res.status === 429) throw await refusal429(res);
|
|
1364
1456
|
if (!res.ok) throw new Error(await httpFailure(res, "chat"));
|
|
1365
1457
|
const data = await res.json();
|
|
1366
1458
|
const choice = data.choices[0];
|
|
1367
1459
|
if (!choice) throw new Error("chat: no choices in response");
|
|
1460
|
+
const spendable = parseSpendable(data.clixad.spendable);
|
|
1368
1461
|
return {
|
|
1369
1462
|
message: choice.message,
|
|
1370
1463
|
finishReason: choice.finish_reason,
|
|
1371
1464
|
creditsCharged: data.clixad.credits_charged,
|
|
1372
1465
|
balance: data.clixad.balance,
|
|
1373
|
-
...freeMeta(data.clixad)
|
|
1466
|
+
...freeMeta(data.clixad),
|
|
1467
|
+
...spendable ? { spendable } : {}
|
|
1374
1468
|
};
|
|
1375
1469
|
}
|
|
1376
1470
|
/**
|
|
@@ -1399,6 +1493,8 @@ var init_client = __esm({
|
|
|
1399
1493
|
let free = {};
|
|
1400
1494
|
let balance = 0;
|
|
1401
1495
|
let streamError;
|
|
1496
|
+
let streamErrorType;
|
|
1497
|
+
let spendable;
|
|
1402
1498
|
let premiumRefusal;
|
|
1403
1499
|
const calls = /* @__PURE__ */ new Map();
|
|
1404
1500
|
const reasoning = [];
|
|
@@ -1421,24 +1517,36 @@ var init_client = __esm({
|
|
|
1421
1517
|
}
|
|
1422
1518
|
mergeReasoningDetails(reasoning, choice?.delta?.reasoning_details);
|
|
1423
1519
|
if (choice?.finish_reason) finishReason = choice.finish_reason;
|
|
1424
|
-
if (json.error)
|
|
1520
|
+
if (json.error) {
|
|
1521
|
+
streamError = json.error.message || "the model stream failed";
|
|
1522
|
+
streamErrorType = json.error.type;
|
|
1523
|
+
}
|
|
1425
1524
|
if (json.error?.type === "premium_locked") {
|
|
1426
|
-
premiumRefusal = premiumLockedError(
|
|
1525
|
+
premiumRefusal = premiumLockedError(
|
|
1526
|
+
streamError ?? "",
|
|
1527
|
+
model,
|
|
1528
|
+
json.clixad?.premium,
|
|
1529
|
+
spendableAt(json.clixad?.spendable, json.clixad?.balance)
|
|
1530
|
+
);
|
|
1427
1531
|
}
|
|
1428
1532
|
if (json.clixad) {
|
|
1429
1533
|
creditsCharged = json.clixad.credits_charged;
|
|
1430
1534
|
balance = json.clixad.balance;
|
|
1431
1535
|
free = freeMeta(json.clixad);
|
|
1536
|
+
spendable = parseSpendable(json.clixad.spendable);
|
|
1432
1537
|
}
|
|
1433
1538
|
}
|
|
1434
1539
|
if (premiumRefusal) throw premiumRefusal;
|
|
1540
|
+
if (streamError && streamErrorType && LIMIT_REFUSALS.has(streamErrorType)) {
|
|
1541
|
+
throw new LimitRefusalError(streamError, streamErrorType, spendable ? { figures: spendable, balance } : void 0);
|
|
1542
|
+
}
|
|
1435
1543
|
if (streamError) throw new Error(streamError);
|
|
1436
1544
|
const message = { role: "assistant", content: content || null };
|
|
1437
1545
|
if (calls.size > 0) {
|
|
1438
1546
|
message.tool_calls = [...calls.entries()].sort(([a], [b]) => a - b).map(([, c2]) => c2);
|
|
1439
1547
|
if (reasoning.length) message.reasoning_details = reasoning;
|
|
1440
1548
|
}
|
|
1441
|
-
return { message, finishReason, creditsCharged, balance, ...free };
|
|
1549
|
+
return { message, finishReason, creditsCharged, balance, ...free, ...spendable ? { spendable } : {} };
|
|
1442
1550
|
}
|
|
1443
1551
|
};
|
|
1444
1552
|
}
|
|
@@ -1638,6 +1746,46 @@ var init_config = __esm({
|
|
|
1638
1746
|
}
|
|
1639
1747
|
});
|
|
1640
1748
|
|
|
1749
|
+
// src/preferences.ts
|
|
1750
|
+
async function syncPreferences(client, config, settings) {
|
|
1751
|
+
if (!settings) return { name: null, instructions: null, sponsorLine: null };
|
|
1752
|
+
const upload = {};
|
|
1753
|
+
if (settings.model && settings.model !== config.model) {
|
|
1754
|
+
config.model = settings.model;
|
|
1755
|
+
saveConfig(config);
|
|
1756
|
+
} else if (!settings.model && config.model && config.model !== FALLBACK_MODEL) {
|
|
1757
|
+
upload.model = config.model;
|
|
1758
|
+
}
|
|
1759
|
+
const local = config.effort ?? {};
|
|
1760
|
+
for (const [model, level] of Object.entries(settings.efforts)) {
|
|
1761
|
+
if (local[model] !== level) saveEffort(config, model, level);
|
|
1762
|
+
}
|
|
1763
|
+
const localOnly = {};
|
|
1764
|
+
for (const [model, level] of Object.entries(local)) {
|
|
1765
|
+
if (!(model in settings.efforts)) localOnly[model] = level;
|
|
1766
|
+
}
|
|
1767
|
+
if (Object.keys(localOnly).length) upload.efforts = localOnly;
|
|
1768
|
+
if (Object.keys(upload).length) await client.saveSettings(upload).catch(() => null);
|
|
1769
|
+
return { name: settings.name, instructions: settings.instructions, sponsorLine: settings.sponsorLine };
|
|
1770
|
+
}
|
|
1771
|
+
function preferencesInstruction(prefs) {
|
|
1772
|
+
if (!prefs) return "";
|
|
1773
|
+
const parts = [];
|
|
1774
|
+
if (prefs.name) parts.push(`The person's name is ${prefs.name}.`);
|
|
1775
|
+
if (prefs.instructions) parts.push(`What the person wants you to keep in mind in every answer:
|
|
1776
|
+
${prefs.instructions}`);
|
|
1777
|
+
return parts.join("\n\n");
|
|
1778
|
+
}
|
|
1779
|
+
function sponsorPreference(config, prefs) {
|
|
1780
|
+
return prefs?.sponsorLine === false ? false : config.sponsor;
|
|
1781
|
+
}
|
|
1782
|
+
var init_preferences = __esm({
|
|
1783
|
+
"src/preferences.ts"() {
|
|
1784
|
+
"use strict";
|
|
1785
|
+
init_config();
|
|
1786
|
+
}
|
|
1787
|
+
});
|
|
1788
|
+
|
|
1641
1789
|
// src/browser.ts
|
|
1642
1790
|
import { spawn } from "node:child_process";
|
|
1643
1791
|
function openBrowser(url) {
|
|
@@ -4368,7 +4516,8 @@ ${NO_SEARCH_INSTRUCTION}`,
|
|
|
4368
4516
|
free: true,
|
|
4369
4517
|
creditsWaived: turn.creditsWaived ?? 0,
|
|
4370
4518
|
freeCreditsLeft: turn.freeCreditsLeft ?? 0
|
|
4371
|
-
} : {}
|
|
4519
|
+
} : {},
|
|
4520
|
+
...turn.spendable ? { spendable: turn.spendable } : {}
|
|
4372
4521
|
});
|
|
4373
4522
|
}
|
|
4374
4523
|
let retriedEmpty = false;
|
|
@@ -4940,12 +5089,12 @@ var init_kimi = __esm({
|
|
|
4940
5089
|
CONTEXT_TOKENS = {
|
|
4941
5090
|
"gpt-5-nano": 4e5,
|
|
4942
5091
|
"gemini-2.5-flash-lite": 1048576,
|
|
5092
|
+
"claude-haiku-5.5": 1e6,
|
|
4943
5093
|
"deepseek-v4-flash": 1048576,
|
|
4944
5094
|
"gemini-3.5-flash-lite": 1048576,
|
|
4945
5095
|
"kimi-k2.5": 262144,
|
|
4946
5096
|
"gemini-3.8-flash": 1048576,
|
|
4947
5097
|
"kimi-k2.7-code": 262144,
|
|
4948
|
-
"claude-haiku-4.5": 2e5,
|
|
4949
5098
|
"claude-sonnet-5.5": 1e6,
|
|
4950
5099
|
"gpt-6-sol": 105e4,
|
|
4951
5100
|
"kimi-k3": 1048576,
|
|
@@ -4955,14 +5104,17 @@ var init_kimi = __esm({
|
|
|
4955
5104
|
// packages/pricing), so an install still configured with one compacts
|
|
4956
5105
|
// against the window of the model that actually answers.
|
|
4957
5106
|
"gemini-3.1-flash-lite": 1048576,
|
|
4958
|
-
|
|
5107
|
+
// Served by Claude Haiku 5.5 since 2026-10-12, when Kimi K2.5 left the free set.
|
|
5108
|
+
"kimi-k2": 1e6,
|
|
4959
5109
|
"gemini-3.6-flash": 1048576,
|
|
4960
5110
|
"gpt-5.2": 105e4,
|
|
4961
5111
|
"claude-opus-5": 1e6,
|
|
4962
5112
|
"gpt-5.5": 105e4,
|
|
4963
5113
|
"gpt-5.2-pro": 105e4,
|
|
4964
5114
|
// Retired on 2026-09-29 for Claude Sonnet 5.5, same window.
|
|
4965
|
-
"claude-sonnet-5": 1e6
|
|
5115
|
+
"claude-sonnet-5": 1e6,
|
|
5116
|
+
// Retired on 2026-10-12 for Claude Haiku 5.5.
|
|
5117
|
+
"claude-haiku-4.5": 1e6
|
|
4966
5118
|
};
|
|
4967
5119
|
DEFAULT_CONTEXT_TOKENS = 256e3;
|
|
4968
5120
|
INSTALL_HELP = `Kimi CLI is not installed (or not on PATH).
|
|
@@ -5401,6 +5553,7 @@ ${summary}` },
|
|
|
5401
5553
|
summary,
|
|
5402
5554
|
creditsCharged: turn.creditsCharged,
|
|
5403
5555
|
balance: turn.balance,
|
|
5556
|
+
...turn.spendable ? { spendable: turn.spendable } : {},
|
|
5404
5557
|
before,
|
|
5405
5558
|
after: conversationTokens(compacted)
|
|
5406
5559
|
};
|
|
@@ -6090,12 +6243,22 @@ function headRows(text, cols, max) {
|
|
|
6090
6243
|
if (rows.length <= max) return text;
|
|
6091
6244
|
return rows.slice(0, max).join("\n");
|
|
6092
6245
|
}
|
|
6093
|
-
function errorNotice(message, rows = ERROR_NOTICE_ROWS) {
|
|
6246
|
+
function errorNotice(message, rows = ERROR_NOTICE_ROWS, indent = "") {
|
|
6094
6247
|
const clean = message.replace(/\u001b\][^\u0007]*(?:\u0007|\u001b\\)?/g, " ").replace(/\u001b\[[0-9;?]*[ -\/]*[@-~]/g, " ").replace(/[\u0000-\u0009\u000b-\u001f\u007f-\u009f]/g, " ");
|
|
6095
6248
|
const lines = clean.split("\n");
|
|
6096
|
-
const kept =
|
|
6097
|
-
|
|
6098
|
-
|
|
6249
|
+
const kept = [];
|
|
6250
|
+
let left = ERROR_NOTICE_CHARS;
|
|
6251
|
+
for (const line2 of lines.slice(0, rows)) {
|
|
6252
|
+
if (line2.length > left) {
|
|
6253
|
+
kept.push(`${line2.slice(0, Math.max(0, left - 1))}\u2026`);
|
|
6254
|
+
break;
|
|
6255
|
+
}
|
|
6256
|
+
kept.push(line2);
|
|
6257
|
+
left -= line2.length;
|
|
6258
|
+
}
|
|
6259
|
+
if (lines.length > kept.length) kept.push(`\u2026 +${lines.length - kept.length} more lines`);
|
|
6260
|
+
return kept.join(`
|
|
6261
|
+
${indent}`);
|
|
6099
6262
|
}
|
|
6100
6263
|
function entryHeight(entry, cols) {
|
|
6101
6264
|
const margin = entry.kind === "banner" ? 0 : 1;
|
|
@@ -6143,7 +6306,7 @@ function EntryView({ entry }) {
|
|
|
6143
6306
|
) });
|
|
6144
6307
|
}
|
|
6145
6308
|
}
|
|
6146
|
-
var MANGO3, MANGO_BRIGHT, SLATE, AD_TEXT, AD_MARK, MODE_STYLE, WRAP_LIKE_INK, DIALOG_CHROME_COLS, ERROR_NOTICE_ROWS,
|
|
6309
|
+
var MANGO3, MANGO_BRIGHT, SLATE, AD_TEXT, AD_MARK, MODE_STYLE, WRAP_LIKE_INK, DIALOG_CHROME_COLS, ERROR_NOTICE_ROWS, ERROR_NOTICE_CHARS;
|
|
6147
6310
|
var init_views = __esm({
|
|
6148
6311
|
"src/tui/views.tsx"() {
|
|
6149
6312
|
"use strict";
|
|
@@ -6162,7 +6325,7 @@ var init_views = __esm({
|
|
|
6162
6325
|
WRAP_LIKE_INK = { trim: false, hard: true };
|
|
6163
6326
|
DIALOG_CHROME_COLS = 4;
|
|
6164
6327
|
ERROR_NOTICE_ROWS = 12;
|
|
6165
|
-
|
|
6328
|
+
ERROR_NOTICE_CHARS = 2400;
|
|
6166
6329
|
}
|
|
6167
6330
|
});
|
|
6168
6331
|
|
|
@@ -6187,6 +6350,9 @@ function buyConfirmLines(pack) {
|
|
|
6187
6350
|
`Terms: ${TERMS_URL}`
|
|
6188
6351
|
].join("\n");
|
|
6189
6352
|
}
|
|
6353
|
+
function utcDay(t = Date.now()) {
|
|
6354
|
+
return new Date(t).toISOString().slice(0, 10);
|
|
6355
|
+
}
|
|
6190
6356
|
function useTerminalSize() {
|
|
6191
6357
|
const { stdout } = useStdout();
|
|
6192
6358
|
const snapshot2 = useRef({
|
|
@@ -6224,9 +6390,11 @@ function App({
|
|
|
6224
6390
|
sponsorServe,
|
|
6225
6391
|
sponsorFormats,
|
|
6226
6392
|
sponsorDirect,
|
|
6227
|
-
initHint
|
|
6393
|
+
initHint,
|
|
6394
|
+
preferences
|
|
6228
6395
|
}) {
|
|
6229
6396
|
const { exit } = useApp();
|
|
6397
|
+
const preferencesText = preferencesInstruction(preferences);
|
|
6230
6398
|
const { stdout, write: writeToStdout } = useStdout();
|
|
6231
6399
|
const idRef = useRef(1);
|
|
6232
6400
|
const { rows, cols } = useTerminalSize();
|
|
@@ -6270,7 +6438,8 @@ function App({
|
|
|
6270
6438
|
const switchModel = useCallback((id) => {
|
|
6271
6439
|
modelRef.current = id;
|
|
6272
6440
|
setModel2(id);
|
|
6273
|
-
|
|
6441
|
+
void client.saveSettings?.({ model: id });
|
|
6442
|
+
}, [client]);
|
|
6274
6443
|
const [hist, setHist] = useState(() => loadHistory());
|
|
6275
6444
|
const [histIdx, setHistIdx] = useState(-1);
|
|
6276
6445
|
const [sel, setSel] = useState(0);
|
|
@@ -6307,6 +6476,7 @@ function App({
|
|
|
6307
6476
|
const runStartedAtRef = useRef(Date.now());
|
|
6308
6477
|
const contextRef = useRef(collectContext(root));
|
|
6309
6478
|
const catalogRef = useRef([]);
|
|
6479
|
+
const [, setCatalogArrived] = useState(0);
|
|
6310
6480
|
const searchRef = useRef(false);
|
|
6311
6481
|
const outputsRef = useRef([]);
|
|
6312
6482
|
const expandedRef = useRef(/* @__PURE__ */ new Set());
|
|
@@ -6328,6 +6498,52 @@ function App({
|
|
|
6328
6498
|
const refreshPremium = useCallback(() => {
|
|
6329
6499
|
void client.wallet(void 0, { premium: true }).then((w) => setPremium(w.premium)).catch(() => void 0);
|
|
6330
6500
|
}, [client, setPremium]);
|
|
6501
|
+
const initialSpendable = useMemo(() => {
|
|
6502
|
+
const figures = parseSpendable(wallet?.spendable);
|
|
6503
|
+
return figures && wallet ? { figures, balance: wallet.balance, day: utcDay() } : void 0;
|
|
6504
|
+
}, []);
|
|
6505
|
+
const [spendable, setSpendableState] = useState(initialSpendable);
|
|
6506
|
+
const spendableRef = useRef(initialSpendable);
|
|
6507
|
+
const takeSpendable = useCallback(
|
|
6508
|
+
(figures, balance2) => {
|
|
6509
|
+
const next = { figures, balance: balance2, day: utcDay() };
|
|
6510
|
+
spendableRef.current = next;
|
|
6511
|
+
setSpendableState(next);
|
|
6512
|
+
setBalance(balance2);
|
|
6513
|
+
const p = premiumRef.current;
|
|
6514
|
+
if (p) setPremium({ ...p, credits: figures.premium });
|
|
6515
|
+
},
|
|
6516
|
+
[setPremium]
|
|
6517
|
+
);
|
|
6518
|
+
const balanceSeqRef = useRef(0);
|
|
6519
|
+
const spendableInFlightRef = useRef(false);
|
|
6520
|
+
const spendableAskedRef = useRef(null);
|
|
6521
|
+
const refreshSpendable = useCallback(() => {
|
|
6522
|
+
if (spendableInFlightRef.current) return;
|
|
6523
|
+
spendableInFlightRef.current = true;
|
|
6524
|
+
const seq = balanceSeqRef.current;
|
|
6525
|
+
void client.wallet(void 0, { premium: true }).then((w) => {
|
|
6526
|
+
if (balanceSeqRef.current !== seq) return;
|
|
6527
|
+
const figures = parseSpendable(w.spendable);
|
|
6528
|
+
if (figures) takeSpendable(figures, w.balance);
|
|
6529
|
+
else setBalance(w.balance);
|
|
6530
|
+
if (w.premium) setPremium(w.premium);
|
|
6531
|
+
}).catch(() => void 0).finally(() => {
|
|
6532
|
+
spendableInFlightRef.current = false;
|
|
6533
|
+
});
|
|
6534
|
+
}, [client, setPremium, takeSpendable]);
|
|
6535
|
+
const [dayTick, setDayTick] = useState(0);
|
|
6536
|
+
useEffect(() => {
|
|
6537
|
+
const now = Date.now();
|
|
6538
|
+
const next = Date.UTC(
|
|
6539
|
+
new Date(now).getUTCFullYear(),
|
|
6540
|
+
new Date(now).getUTCMonth(),
|
|
6541
|
+
new Date(now).getUTCDate() + 1
|
|
6542
|
+
);
|
|
6543
|
+
const timer = setTimeout(() => setDayTick((t) => t + 1), next - now + 1e3);
|
|
6544
|
+
timer.unref?.();
|
|
6545
|
+
return () => clearTimeout(timer);
|
|
6546
|
+
}, [dayTick]);
|
|
6331
6547
|
const mcpRef = useRef(McpHub.empty());
|
|
6332
6548
|
const todosRef = useRef(new TodoList());
|
|
6333
6549
|
const lastTurnRef = useRef(null);
|
|
@@ -6482,6 +6698,7 @@ function App({
|
|
|
6482
6698
|
}
|
|
6483
6699
|
client.models().then((m) => {
|
|
6484
6700
|
catalogRef.current = m;
|
|
6701
|
+
setCatalogArrived((n) => n + 1);
|
|
6485
6702
|
searchRef.current = client.capabilities?.web_search === true;
|
|
6486
6703
|
const successor = client.retiredModels[modelRef.current];
|
|
6487
6704
|
if (successor) {
|
|
@@ -6544,7 +6761,7 @@ function App({
|
|
|
6544
6761
|
if (!sponsorEnabled({
|
|
6545
6762
|
isTTY: stdout.isTTY,
|
|
6546
6763
|
env: process.env,
|
|
6547
|
-
configEnabled: config
|
|
6764
|
+
configEnabled: sponsorPreference(config, preferences)
|
|
6548
6765
|
})) {
|
|
6549
6766
|
setSponsor(null);
|
|
6550
6767
|
setAdSlots({});
|
|
@@ -6746,8 +6963,11 @@ function App({
|
|
|
6746
6963
|
push({ kind: "notice", text: ` showing the model: ${event.labels.join(", ")}` });
|
|
6747
6964
|
return;
|
|
6748
6965
|
case "usage":
|
|
6966
|
+
balanceSeqRef.current++;
|
|
6749
6967
|
setBalance(event.balance);
|
|
6750
|
-
if (event.
|
|
6968
|
+
if (event.spendable) {
|
|
6969
|
+
takeSpendable(event.spendable, event.balance);
|
|
6970
|
+
} else if (event.creditsCharged > 0 && premiumRef.current) {
|
|
6751
6971
|
const p = premiumRef.current;
|
|
6752
6972
|
setPremium({
|
|
6753
6973
|
...p,
|
|
@@ -6765,8 +6985,18 @@ function App({
|
|
|
6765
6985
|
return;
|
|
6766
6986
|
}
|
|
6767
6987
|
},
|
|
6768
|
-
[cols, flushDelta, push, setPremium]
|
|
6988
|
+
[cols, flushDelta, push, setPremium, takeSpendable]
|
|
6769
6989
|
);
|
|
6990
|
+
useEffect(() => {
|
|
6991
|
+
if (balance === void 0) return;
|
|
6992
|
+
const s = spendableRef.current;
|
|
6993
|
+
const day = utcDay();
|
|
6994
|
+
if (s && s.balance === balance && s.day === day) return;
|
|
6995
|
+
const key = `${balance}|${day}`;
|
|
6996
|
+
if (spendableAskedRef.current === key) return;
|
|
6997
|
+
spendableAskedRef.current = key;
|
|
6998
|
+
refreshSpendable();
|
|
6999
|
+
}, [balance, spendable, dayTick, refreshSpendable]);
|
|
6770
7000
|
const runTurn2 = useCallback(
|
|
6771
7001
|
async (task, images = []) => {
|
|
6772
7002
|
pendingFreeRef.current = [];
|
|
@@ -6789,7 +7019,9 @@ function App({
|
|
|
6789
7019
|
if (res) {
|
|
6790
7020
|
history = res.messages.slice(1);
|
|
6791
7021
|
setMessages(history);
|
|
7022
|
+
balanceSeqRef.current++;
|
|
6792
7023
|
setBalance(res.balance);
|
|
7024
|
+
if (res.spendable) takeSpendable(res.spendable, res.balance);
|
|
6793
7025
|
setSpent((s) => s + res.creditsCharged);
|
|
6794
7026
|
push({
|
|
6795
7027
|
kind: "notice",
|
|
@@ -6817,7 +7049,11 @@ function App({
|
|
|
6817
7049
|
|
|
6818
7050
|
${modeInstruction(permRef.current.mode)}` + (searchRef.current ? "" : `
|
|
6819
7051
|
|
|
6820
|
-
${NO_SEARCH_INSTRUCTION}`),
|
|
7052
|
+
${NO_SEARCH_INSTRUCTION}`) + // The account's name and instructions (gateway 0039), set in the
|
|
7053
|
+
// browser or on the account page, said on every turn.
|
|
7054
|
+
(preferencesText ? `
|
|
7055
|
+
|
|
7056
|
+
${preferencesText}` : ""),
|
|
6821
7057
|
permit,
|
|
6822
7058
|
approvePlan,
|
|
6823
7059
|
askUser: askQuestion,
|
|
@@ -6907,6 +7143,10 @@ ${NO_SEARCH_INSTRUCTION}`),
|
|
|
6907
7143
|
}
|
|
6908
7144
|
}
|
|
6909
7145
|
} catch (err) {
|
|
7146
|
+
if ((err instanceof LimitRefusalError || err instanceof PremiumLockedError) && err.spendable) {
|
|
7147
|
+
balanceSeqRef.current++;
|
|
7148
|
+
takeSpendable(err.spendable.figures, err.spendable.balance);
|
|
7149
|
+
}
|
|
6910
7150
|
if (err instanceof PremiumLockedError) {
|
|
6911
7151
|
pendingTaskRef.current = { task, images };
|
|
6912
7152
|
pendingPremiumRef.current = err;
|
|
@@ -6956,7 +7196,7 @@ ${NO_SEARCH_INSTRUCTION}`),
|
|
|
6956
7196
|
} else if (err instanceof AuthError) {
|
|
6957
7197
|
push({ kind: "notice", tone: "error", text: NOT_SIGNED_IN });
|
|
6958
7198
|
} else {
|
|
6959
|
-
push({ kind: "notice", tone: "error", text: ` error: ${errorNotice(err.message)}` });
|
|
7199
|
+
push({ kind: "notice", tone: "error", text: ` error: ${errorNotice(err.message, void 0, " ")}` });
|
|
6960
7200
|
}
|
|
6961
7201
|
} finally {
|
|
6962
7202
|
abortRef.current = null;
|
|
@@ -6981,7 +7221,8 @@ ${NO_SEARCH_INSTRUCTION}`),
|
|
|
6981
7221
|
nextSponsor,
|
|
6982
7222
|
permit,
|
|
6983
7223
|
push,
|
|
6984
|
-
root
|
|
7224
|
+
root,
|
|
7225
|
+
takeSpendable
|
|
6985
7226
|
]
|
|
6986
7227
|
);
|
|
6987
7228
|
const stopCurrent = useCallback(() => {
|
|
@@ -7000,7 +7241,7 @@ ${NO_SEARCH_INSTRUCTION}`),
|
|
|
7000
7241
|
} catch (err) {
|
|
7001
7242
|
if (ac.signal.aborted) push({ kind: "notice", tone: "warn", text: " (stopped)" });
|
|
7002
7243
|
else if (err instanceof AuthError) push({ kind: "notice", tone: "error", text: NOT_SIGNED_IN });
|
|
7003
|
-
else push({ kind: "notice", tone: "error", text: ` ${errorNotice(err.message)}` });
|
|
7244
|
+
else push({ kind: "notice", tone: "error", text: ` ${errorNotice(err.message, void 0, " ")}` });
|
|
7004
7245
|
} finally {
|
|
7005
7246
|
busyAbortRef.current = null;
|
|
7006
7247
|
setBusyLabel(WORKING);
|
|
@@ -7442,6 +7683,7 @@ ${NO_SEARCH_INSTRUCTION}`),
|
|
|
7442
7683
|
const defaultWords = fallback ? effortLabel(fallback) : "its default";
|
|
7443
7684
|
const apply2 = (choice, note = "") => {
|
|
7444
7685
|
saveEffort(config, model, choice);
|
|
7686
|
+
void client.saveSettings?.({ efforts: { [model]: choice ?? "auto" } });
|
|
7445
7687
|
setEffortBy({ ...config.effort ?? {} });
|
|
7446
7688
|
const now = choice ? effortLabel(choice) : `auto (${defaultWords})`;
|
|
7447
7689
|
push({ kind: "notice", text: ` ${name}: ${now}${note}` });
|
|
@@ -7492,8 +7734,13 @@ ${NO_SEARCH_INSTRUCTION}`),
|
|
|
7492
7734
|
case "wallet":
|
|
7493
7735
|
await runBusy(LOADING, async (signal) => {
|
|
7494
7736
|
const streakNow = await client.claimStreak(signal);
|
|
7495
|
-
const w = await client.wallet(signal);
|
|
7496
|
-
|
|
7737
|
+
const w = await client.wallet(signal, { premium: true });
|
|
7738
|
+
const figures = parseSpendable(w.spendable);
|
|
7739
|
+
balanceSeqRef.current++;
|
|
7740
|
+
if (figures) takeSpendable(figures, w.balance);
|
|
7741
|
+
else setBalance(w.balance);
|
|
7742
|
+
if (w.premium) setPremium(w.premium);
|
|
7743
|
+
const usableNow = figures ? walletUsableLines(w.balance, figures) : [];
|
|
7497
7744
|
const reversals = w.ledger.filter((e) => e.reason === "ad_reversal");
|
|
7498
7745
|
const reversed = reversals.reduce((sum, e) => sum + Math.abs(e.delta), 0);
|
|
7499
7746
|
const screenouts = w.screenouts_today ?? 0;
|
|
@@ -7501,7 +7748,8 @@ ${NO_SEARCH_INSTRUCTION}`),
|
|
|
7501
7748
|
const streakLine = streakNow && streakLabel(streakNow);
|
|
7502
7749
|
push({
|
|
7503
7750
|
kind: "notice",
|
|
7504
|
-
text: ` balance ${w.balance.toLocaleString("en-US")} credits \xB7 ${w.ads_today} offers today \xB7 $${w.earned_usd_today.toFixed(2)}/$${w.max_reward_usd_per_day.toFixed(2)} earned` + (
|
|
7751
|
+
text: ` balance ${w.balance.toLocaleString("en-US")} credits \xB7 ${w.ads_today} offers today \xB7 $${w.earned_usd_today.toFixed(2)}/$${w.max_reward_usd_per_day.toFixed(2)} earned` + usableNow.map((l) => `
|
|
7752
|
+
${l}`).join("") + (screenouts ? `
|
|
7505
7753
|
plus ${screenoutCredits.toLocaleString("en-US")} credits from ${screenouts} screenout bonus${screenouts === 1 ? "" : "es"} \u2014 attempts that didn't qualify, which CPX pays a little for anyway.` : "") + (streakLine ? `
|
|
7506
7754
|
${streakLine}` : "") + (reversals.length ? `
|
|
7507
7755
|
recently: ${reversed.toLocaleString("en-US")} credits from ${reversals.length} offer${reversals.length === 1 ? "" : "s"} were reversed by the provider. Run \`clixad wallet\` for the full ledger.` : "")
|
|
@@ -8156,14 +8404,25 @@ ${dropped.map((l) => ` \xB7 ${l}`).join("\n")}`
|
|
|
8156
8404
|
const typed = toText(editor);
|
|
8157
8405
|
let estimate;
|
|
8158
8406
|
const info = catalogRef.current.find((m) => m.id === model);
|
|
8407
|
+
const usable = (() => {
|
|
8408
|
+
const s = spendable;
|
|
8409
|
+
if (!s || balance === void 0 || s.balance !== balance || s.day !== utcDay() || !info?.access) return void 0;
|
|
8410
|
+
const credits2 = Math.max(0, Math.min(balance, info.access === "premium" ? s.figures.premium : s.figures.budget));
|
|
8411
|
+
return {
|
|
8412
|
+
kind: info.access,
|
|
8413
|
+
credits: credits2,
|
|
8414
|
+
locked: Math.max(0, balance - credits2),
|
|
8415
|
+
afterReset: Math.max(credits2, Math.min(balance, s.figures.budgetAfterReset))
|
|
8416
|
+
};
|
|
8417
|
+
})();
|
|
8159
8418
|
const effortNow = info ? resolveEffort(info, effortBy[model]) : effortBy[model];
|
|
8160
8419
|
const shape = client.taskShape;
|
|
8161
8420
|
if (!busy && info && shape && balance !== void 0 && !effortRaised(info, resolveEffort(info, effortBy[model])) && typed.trim() !== "" && !/^[/!]/.test(typed.trimStart())) {
|
|
8162
8421
|
const range = estimateTask(info, conversationUsed + schemaTokens + estimateTokens(typed), shape);
|
|
8163
8422
|
const onFree = Boolean(info.free && freeTier?.models.includes(model));
|
|
8164
|
-
const
|
|
8423
|
+
const canSpend = usable ? usable.credits : info.access === "premium" && premium && !premium.unlocked ? Math.min(balance, premium.credits) : balance;
|
|
8165
8424
|
const verdict = taskVerdict(range, {
|
|
8166
|
-
balance:
|
|
8425
|
+
balance: canSpend,
|
|
8167
8426
|
...onFree ? { freeLeft: freeTier.credits_left } : {}
|
|
8168
8427
|
});
|
|
8169
8428
|
estimate = estimateClause(range, verdict, !onFree && (freeTier?.credits_left ?? 0) > 0);
|
|
@@ -8175,6 +8434,7 @@ ${dropped.map((l) => ` \xB7 ${l}`).join("\n")}`
|
|
|
8175
8434
|
model,
|
|
8176
8435
|
...effortNow ? { effort: effortNow } : {},
|
|
8177
8436
|
balance,
|
|
8437
|
+
...usable ? { usable } : {},
|
|
8178
8438
|
spent,
|
|
8179
8439
|
contextPct,
|
|
8180
8440
|
minutes: (Date.now() - runStartedAtRef.current) / 6e4,
|
|
@@ -8360,15 +8620,41 @@ function turnMeta(result) {
|
|
|
8360
8620
|
}
|
|
8361
8621
|
return `[free \xB7 ${n(result.creditsWaived)} credits waived${left}]`;
|
|
8362
8622
|
}
|
|
8623
|
+
function lockedPhrasings(u) {
|
|
8624
|
+
if (!u || u.locked <= 0) return [];
|
|
8625
|
+
const n = (v) => v.toLocaleString("en-US");
|
|
8626
|
+
const locked = `${n(u.locked)} bonus locked`;
|
|
8627
|
+
if (u.kind === "premium") return [`${locked} on premium`, locked];
|
|
8628
|
+
const back = u.afterReset - u.credits;
|
|
8629
|
+
return back >= u.locked ? [`${locked} until 00:00 UTC`, locked] : back > 0 ? [`${locked}, ${n(back)} usable from 00:00 UTC`, locked] : [locked];
|
|
8630
|
+
}
|
|
8631
|
+
function walletUsableLines(balance, f) {
|
|
8632
|
+
const n = (v) => Math.max(0, v).toLocaleString("en-US");
|
|
8633
|
+
const budget = Math.min(balance, f.budget);
|
|
8634
|
+
const premium = Math.min(balance, f.premium);
|
|
8635
|
+
if (budget >= balance && premium >= balance) return [];
|
|
8636
|
+
const lines = [`usable now: ${n(budget)} credits on budget models, ${n(premium)} on premium models`];
|
|
8637
|
+
const locked = balance - budget;
|
|
8638
|
+
if (locked > 0) {
|
|
8639
|
+
const back = Math.min(balance, Math.max(budget, f.budgetAfterReset)) - budget;
|
|
8640
|
+
lines.push(
|
|
8641
|
+
back >= locked ? `${n(locked)} bonus credits are over today's limit and become usable at 00:00 UTC` : back > 0 ? `${n(locked)} bonus credits are over today's limit: ${n(back)} become usable at 00:00 UTC, the rest on later days` : `${n(locked)} bonus credits are over today's limit`
|
|
8642
|
+
);
|
|
8643
|
+
}
|
|
8644
|
+
return lines;
|
|
8645
|
+
}
|
|
8363
8646
|
function statusLine(o, cols) {
|
|
8364
8647
|
const phrasings = o.estimate === void 0 ? [] : typeof o.estimate === "string" ? [o.estimate] : [...o.estimate];
|
|
8648
|
+
const locked = lockedPhrasings(o.usable);
|
|
8365
8649
|
for (const estimate of [...phrasings, void 0]) {
|
|
8366
|
-
const
|
|
8367
|
-
|
|
8650
|
+
for (const held of [...locked, void 0]) {
|
|
8651
|
+
const line2 = statusRow(o, estimate, held, cols);
|
|
8652
|
+
if (visibleLength(line2) <= cols) return line2;
|
|
8653
|
+
}
|
|
8368
8654
|
}
|
|
8369
|
-
return fitRow(statusRow(o, void 0, cols), cols);
|
|
8655
|
+
return fitRow(statusRow(o, void 0, void 0, cols), cols);
|
|
8370
8656
|
}
|
|
8371
|
-
function statusRow(o, estimate, cols) {
|
|
8657
|
+
function statusRow(o, estimate, locked, cols) {
|
|
8372
8658
|
const burn = o.spent > 0 && o.minutes >= 1 ? `${Math.round(o.spent / o.minutes).toLocaleString("en-US")} cr/min` : void 0;
|
|
8373
8659
|
const clauses = [
|
|
8374
8660
|
{ text: o.model, rank: KEEP_ALWAYS },
|
|
@@ -8377,9 +8663,15 @@ function statusRow(o, estimate, cols) {
|
|
|
8377
8663
|
{
|
|
8378
8664
|
// "? cr" rather than "0 cr": unknown is not empty, and an empty-looking
|
|
8379
8665
|
// wallet is the one misreading on this row that frightens somebody.
|
|
8380
|
-
|
|
8666
|
+
// With part of the balance held back, what can be spent takes its place:
|
|
8667
|
+
// "33,396 cr" over a wallet the gateway refused to spend from was how
|
|
8668
|
+
// this row came to be read as a fault (2026-10-12).
|
|
8669
|
+
text: o.balance === void 0 ? "? cr" : o.usable && o.usable.locked > 0 ? `${o.usable.credits.toLocaleString("en-US")} cr usable` : `${o.balance.toLocaleString("en-US")} cr`,
|
|
8381
8670
|
rank: KEEP_ALWAYS
|
|
8382
8671
|
},
|
|
8672
|
+
// Never dropped inside a row: `statusLine` tries its shorter phrasings,
|
|
8673
|
+
// then none, as it does for the preview.
|
|
8674
|
+
{ text: locked, rank: 5 },
|
|
8383
8675
|
// Above the context warning, below the balance. It is the second number
|
|
8384
8676
|
// deciding whether the next turn costs anything, and unlike the spend it is
|
|
8385
8677
|
// *news* — it only exists while a free model is selected and it counts down.
|
|
@@ -8459,6 +8751,7 @@ var init_app = __esm({
|
|
|
8459
8751
|
init_todos();
|
|
8460
8752
|
init_shells();
|
|
8461
8753
|
init_config();
|
|
8754
|
+
init_preferences();
|
|
8462
8755
|
init_effort();
|
|
8463
8756
|
init_login();
|
|
8464
8757
|
init_banner();
|
|
@@ -8568,6 +8861,7 @@ var init_tui = __esm({
|
|
|
8568
8861
|
// src/main.ts
|
|
8569
8862
|
init_client();
|
|
8570
8863
|
init_config();
|
|
8864
|
+
init_preferences();
|
|
8571
8865
|
init_login();
|
|
8572
8866
|
init_free_tier();
|
|
8573
8867
|
init_agent();
|
|
@@ -9076,7 +9370,8 @@ async function repl(client, config, opts = {}) {
|
|
|
9076
9370
|
session = loadSession(opts.resume);
|
|
9077
9371
|
if (!session) return console.log(c.red(`no such session: ${opts.resume}`));
|
|
9078
9372
|
}
|
|
9079
|
-
const { streak, wallet } = await startupReads(client, config.gatewayUrl);
|
|
9373
|
+
const { streak, wallet, settings } = await startupReads(client, config.gatewayUrl);
|
|
9374
|
+
const preferences = await syncPreferences(client, config, settings);
|
|
9080
9375
|
const cwd = process.cwd();
|
|
9081
9376
|
const initHint = shouldSuggestInit(cwd) ? { returning: hasSessionIn(cwd) } : void 0;
|
|
9082
9377
|
clearScreen();
|
|
@@ -9088,6 +9383,7 @@ async function repl(client, config, opts = {}) {
|
|
|
9088
9383
|
streak,
|
|
9089
9384
|
session,
|
|
9090
9385
|
newAccount,
|
|
9386
|
+
preferences,
|
|
9091
9387
|
initialTask: opts.task,
|
|
9092
9388
|
...opts.notices?.length ? { notices: opts.notices } : {},
|
|
9093
9389
|
...initHint ? { initHint } : {}
|
|
@@ -9166,10 +9462,15 @@ async function startupReads(client, gatewayUrl) {
|
|
|
9166
9462
|
)
|
|
9167
9463
|
];
|
|
9168
9464
|
try {
|
|
9169
|
-
const [streak, wallet] = await Promise.all([
|
|
9465
|
+
const [streak, wallet, settings] = await Promise.all([
|
|
9466
|
+
client.claimStreak(),
|
|
9467
|
+
safeWallet(client, { premium: true }),
|
|
9468
|
+
client.getSettings()
|
|
9469
|
+
]);
|
|
9170
9470
|
const balance = streak?.claimed ? streak.balance : wallet?.balance;
|
|
9171
9471
|
return {
|
|
9172
9472
|
streak,
|
|
9473
|
+
settings,
|
|
9173
9474
|
wallet: wallet && balance !== void 0 ? { ...wallet, balance } : wallet
|
|
9174
9475
|
};
|
|
9175
9476
|
} finally {
|