clixad 0.0.1-beta.31 → 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/dist/clixad.mjs +226 -34
- package/package.json +1 -1
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;
|
|
@@ -481,7 +488,11 @@ function asSignupClosed(status, data) {
|
|
|
481
488
|
const d = data;
|
|
482
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;
|
|
483
490
|
}
|
|
484
|
-
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) {
|
|
485
496
|
const num = (v) => typeof v === "number" && Number.isFinite(v) ? v : 0;
|
|
486
497
|
return new PremiumLockedError(
|
|
487
498
|
message,
|
|
@@ -491,7 +502,8 @@ function premiumLockedError(message, model, body) {
|
|
|
491
502
|
typeof body?.unlock_usd === "number" && body.unlock_usd > 0 ? body.unlock_usd : null,
|
|
492
503
|
num(body?.grant_per_ad),
|
|
493
504
|
freeAlternatives(body?.free_models),
|
|
494
|
-
usableStreak(body?.streak)
|
|
505
|
+
usableStreak(body?.streak),
|
|
506
|
+
spendable
|
|
495
507
|
);
|
|
496
508
|
}
|
|
497
509
|
function shortfallHeadline(err, model) {
|
|
@@ -516,9 +528,23 @@ function freeAlternatives(list) {
|
|
|
516
528
|
}
|
|
517
529
|
async function refusal402(res, model) {
|
|
518
530
|
const body = await res.json();
|
|
519
|
-
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
|
+
}
|
|
520
534
|
return paywallError(body);
|
|
521
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
|
+
}
|
|
522
548
|
function neverConnected(code) {
|
|
523
549
|
return code !== void 0 && NEVER_CONNECTED.has(code);
|
|
524
550
|
}
|
|
@@ -643,7 +669,7 @@ async function* parseSSE(body) {
|
|
|
643
669
|
}
|
|
644
670
|
}
|
|
645
671
|
}
|
|
646
|
-
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;
|
|
647
673
|
var init_client = __esm({
|
|
648
674
|
"src/client.ts"() {
|
|
649
675
|
"use strict";
|
|
@@ -696,7 +722,7 @@ var init_client = __esm({
|
|
|
696
722
|
cheapestPackUsd;
|
|
697
723
|
};
|
|
698
724
|
PremiumLockedError = class extends Error {
|
|
699
|
-
constructor(message, model, neededCredits, premiumCredits, unlockUsd, grantPerAd, freeModels, streak) {
|
|
725
|
+
constructor(message, model, neededCredits, premiumCredits, unlockUsd, grantPerAd, freeModels, streak, spendable) {
|
|
700
726
|
super(message);
|
|
701
727
|
this.model = model;
|
|
702
728
|
this.neededCredits = neededCredits;
|
|
@@ -705,6 +731,7 @@ var init_client = __esm({
|
|
|
705
731
|
this.grantPerAd = grantPerAd;
|
|
706
732
|
this.freeModels = freeModels;
|
|
707
733
|
this.streak = streak;
|
|
734
|
+
this.spendable = spendable;
|
|
708
735
|
this.name = "PremiumLockedError";
|
|
709
736
|
}
|
|
710
737
|
model;
|
|
@@ -714,7 +741,19 @@ var init_client = __esm({
|
|
|
714
741
|
grantPerAd;
|
|
715
742
|
freeModels;
|
|
716
743
|
streak;
|
|
744
|
+
spendable;
|
|
717
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"]);
|
|
718
757
|
GatewayUnreachableError = class extends Error {
|
|
719
758
|
constructor(host, code) {
|
|
720
759
|
const where = `${host}${code ? ` (${code})` : ""}`;
|
|
@@ -1413,16 +1452,19 @@ var init_client = __esm({
|
|
|
1413
1452
|
);
|
|
1414
1453
|
if (res.status === 401) throw await authFailure(res, "chat");
|
|
1415
1454
|
if (res.status === 402) throw await refusal402(res, model);
|
|
1455
|
+
if (res.status === 429) throw await refusal429(res);
|
|
1416
1456
|
if (!res.ok) throw new Error(await httpFailure(res, "chat"));
|
|
1417
1457
|
const data = await res.json();
|
|
1418
1458
|
const choice = data.choices[0];
|
|
1419
1459
|
if (!choice) throw new Error("chat: no choices in response");
|
|
1460
|
+
const spendable = parseSpendable(data.clixad.spendable);
|
|
1420
1461
|
return {
|
|
1421
1462
|
message: choice.message,
|
|
1422
1463
|
finishReason: choice.finish_reason,
|
|
1423
1464
|
creditsCharged: data.clixad.credits_charged,
|
|
1424
1465
|
balance: data.clixad.balance,
|
|
1425
|
-
...freeMeta(data.clixad)
|
|
1466
|
+
...freeMeta(data.clixad),
|
|
1467
|
+
...spendable ? { spendable } : {}
|
|
1426
1468
|
};
|
|
1427
1469
|
}
|
|
1428
1470
|
/**
|
|
@@ -1451,6 +1493,8 @@ var init_client = __esm({
|
|
|
1451
1493
|
let free = {};
|
|
1452
1494
|
let balance = 0;
|
|
1453
1495
|
let streamError;
|
|
1496
|
+
let streamErrorType;
|
|
1497
|
+
let spendable;
|
|
1454
1498
|
let premiumRefusal;
|
|
1455
1499
|
const calls = /* @__PURE__ */ new Map();
|
|
1456
1500
|
const reasoning = [];
|
|
@@ -1473,24 +1517,36 @@ var init_client = __esm({
|
|
|
1473
1517
|
}
|
|
1474
1518
|
mergeReasoningDetails(reasoning, choice?.delta?.reasoning_details);
|
|
1475
1519
|
if (choice?.finish_reason) finishReason = choice.finish_reason;
|
|
1476
|
-
if (json.error)
|
|
1520
|
+
if (json.error) {
|
|
1521
|
+
streamError = json.error.message || "the model stream failed";
|
|
1522
|
+
streamErrorType = json.error.type;
|
|
1523
|
+
}
|
|
1477
1524
|
if (json.error?.type === "premium_locked") {
|
|
1478
|
-
premiumRefusal = premiumLockedError(
|
|
1525
|
+
premiumRefusal = premiumLockedError(
|
|
1526
|
+
streamError ?? "",
|
|
1527
|
+
model,
|
|
1528
|
+
json.clixad?.premium,
|
|
1529
|
+
spendableAt(json.clixad?.spendable, json.clixad?.balance)
|
|
1530
|
+
);
|
|
1479
1531
|
}
|
|
1480
1532
|
if (json.clixad) {
|
|
1481
1533
|
creditsCharged = json.clixad.credits_charged;
|
|
1482
1534
|
balance = json.clixad.balance;
|
|
1483
1535
|
free = freeMeta(json.clixad);
|
|
1536
|
+
spendable = parseSpendable(json.clixad.spendable);
|
|
1484
1537
|
}
|
|
1485
1538
|
}
|
|
1486
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
|
+
}
|
|
1487
1543
|
if (streamError) throw new Error(streamError);
|
|
1488
1544
|
const message = { role: "assistant", content: content || null };
|
|
1489
1545
|
if (calls.size > 0) {
|
|
1490
1546
|
message.tool_calls = [...calls.entries()].sort(([a], [b]) => a - b).map(([, c2]) => c2);
|
|
1491
1547
|
if (reasoning.length) message.reasoning_details = reasoning;
|
|
1492
1548
|
}
|
|
1493
|
-
return { message, finishReason, creditsCharged, balance, ...free };
|
|
1549
|
+
return { message, finishReason, creditsCharged, balance, ...free, ...spendable ? { spendable } : {} };
|
|
1494
1550
|
}
|
|
1495
1551
|
};
|
|
1496
1552
|
}
|
|
@@ -4460,7 +4516,8 @@ ${NO_SEARCH_INSTRUCTION}`,
|
|
|
4460
4516
|
free: true,
|
|
4461
4517
|
creditsWaived: turn.creditsWaived ?? 0,
|
|
4462
4518
|
freeCreditsLeft: turn.freeCreditsLeft ?? 0
|
|
4463
|
-
} : {}
|
|
4519
|
+
} : {},
|
|
4520
|
+
...turn.spendable ? { spendable: turn.spendable } : {}
|
|
4464
4521
|
});
|
|
4465
4522
|
}
|
|
4466
4523
|
let retriedEmpty = false;
|
|
@@ -5032,12 +5089,12 @@ var init_kimi = __esm({
|
|
|
5032
5089
|
CONTEXT_TOKENS = {
|
|
5033
5090
|
"gpt-5-nano": 4e5,
|
|
5034
5091
|
"gemini-2.5-flash-lite": 1048576,
|
|
5092
|
+
"claude-haiku-5.5": 1e6,
|
|
5035
5093
|
"deepseek-v4-flash": 1048576,
|
|
5036
5094
|
"gemini-3.5-flash-lite": 1048576,
|
|
5037
5095
|
"kimi-k2.5": 262144,
|
|
5038
5096
|
"gemini-3.8-flash": 1048576,
|
|
5039
5097
|
"kimi-k2.7-code": 262144,
|
|
5040
|
-
"claude-haiku-4.5": 2e5,
|
|
5041
5098
|
"claude-sonnet-5.5": 1e6,
|
|
5042
5099
|
"gpt-6-sol": 105e4,
|
|
5043
5100
|
"kimi-k3": 1048576,
|
|
@@ -5047,14 +5104,17 @@ var init_kimi = __esm({
|
|
|
5047
5104
|
// packages/pricing), so an install still configured with one compacts
|
|
5048
5105
|
// against the window of the model that actually answers.
|
|
5049
5106
|
"gemini-3.1-flash-lite": 1048576,
|
|
5050
|
-
|
|
5107
|
+
// Served by Claude Haiku 5.5 since 2026-10-12, when Kimi K2.5 left the free set.
|
|
5108
|
+
"kimi-k2": 1e6,
|
|
5051
5109
|
"gemini-3.6-flash": 1048576,
|
|
5052
5110
|
"gpt-5.2": 105e4,
|
|
5053
5111
|
"claude-opus-5": 1e6,
|
|
5054
5112
|
"gpt-5.5": 105e4,
|
|
5055
5113
|
"gpt-5.2-pro": 105e4,
|
|
5056
5114
|
// Retired on 2026-09-29 for Claude Sonnet 5.5, same window.
|
|
5057
|
-
"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
|
|
5058
5118
|
};
|
|
5059
5119
|
DEFAULT_CONTEXT_TOKENS = 256e3;
|
|
5060
5120
|
INSTALL_HELP = `Kimi CLI is not installed (or not on PATH).
|
|
@@ -5493,6 +5553,7 @@ ${summary}` },
|
|
|
5493
5553
|
summary,
|
|
5494
5554
|
creditsCharged: turn.creditsCharged,
|
|
5495
5555
|
balance: turn.balance,
|
|
5556
|
+
...turn.spendable ? { spendable: turn.spendable } : {},
|
|
5496
5557
|
before,
|
|
5497
5558
|
after: conversationTokens(compacted)
|
|
5498
5559
|
};
|
|
@@ -6182,12 +6243,22 @@ function headRows(text, cols, max) {
|
|
|
6182
6243
|
if (rows.length <= max) return text;
|
|
6183
6244
|
return rows.slice(0, max).join("\n");
|
|
6184
6245
|
}
|
|
6185
|
-
function errorNotice(message, rows = ERROR_NOTICE_ROWS) {
|
|
6246
|
+
function errorNotice(message, rows = ERROR_NOTICE_ROWS, indent = "") {
|
|
6186
6247
|
const clean = message.replace(/\u001b\][^\u0007]*(?:\u0007|\u001b\\)?/g, " ").replace(/\u001b\[[0-9;?]*[ -\/]*[@-~]/g, " ").replace(/[\u0000-\u0009\u000b-\u001f\u007f-\u009f]/g, " ");
|
|
6187
6248
|
const lines = clean.split("\n");
|
|
6188
|
-
const kept =
|
|
6189
|
-
|
|
6190
|
-
|
|
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}`);
|
|
6191
6262
|
}
|
|
6192
6263
|
function entryHeight(entry, cols) {
|
|
6193
6264
|
const margin = entry.kind === "banner" ? 0 : 1;
|
|
@@ -6235,7 +6306,7 @@ function EntryView({ entry }) {
|
|
|
6235
6306
|
) });
|
|
6236
6307
|
}
|
|
6237
6308
|
}
|
|
6238
|
-
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;
|
|
6239
6310
|
var init_views = __esm({
|
|
6240
6311
|
"src/tui/views.tsx"() {
|
|
6241
6312
|
"use strict";
|
|
@@ -6254,7 +6325,7 @@ var init_views = __esm({
|
|
|
6254
6325
|
WRAP_LIKE_INK = { trim: false, hard: true };
|
|
6255
6326
|
DIALOG_CHROME_COLS = 4;
|
|
6256
6327
|
ERROR_NOTICE_ROWS = 12;
|
|
6257
|
-
|
|
6328
|
+
ERROR_NOTICE_CHARS = 2400;
|
|
6258
6329
|
}
|
|
6259
6330
|
});
|
|
6260
6331
|
|
|
@@ -6279,6 +6350,9 @@ function buyConfirmLines(pack) {
|
|
|
6279
6350
|
`Terms: ${TERMS_URL}`
|
|
6280
6351
|
].join("\n");
|
|
6281
6352
|
}
|
|
6353
|
+
function utcDay(t = Date.now()) {
|
|
6354
|
+
return new Date(t).toISOString().slice(0, 10);
|
|
6355
|
+
}
|
|
6282
6356
|
function useTerminalSize() {
|
|
6283
6357
|
const { stdout } = useStdout();
|
|
6284
6358
|
const snapshot2 = useRef({
|
|
@@ -6402,6 +6476,7 @@ function App({
|
|
|
6402
6476
|
const runStartedAtRef = useRef(Date.now());
|
|
6403
6477
|
const contextRef = useRef(collectContext(root));
|
|
6404
6478
|
const catalogRef = useRef([]);
|
|
6479
|
+
const [, setCatalogArrived] = useState(0);
|
|
6405
6480
|
const searchRef = useRef(false);
|
|
6406
6481
|
const outputsRef = useRef([]);
|
|
6407
6482
|
const expandedRef = useRef(/* @__PURE__ */ new Set());
|
|
@@ -6423,6 +6498,52 @@ function App({
|
|
|
6423
6498
|
const refreshPremium = useCallback(() => {
|
|
6424
6499
|
void client.wallet(void 0, { premium: true }).then((w) => setPremium(w.premium)).catch(() => void 0);
|
|
6425
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]);
|
|
6426
6547
|
const mcpRef = useRef(McpHub.empty());
|
|
6427
6548
|
const todosRef = useRef(new TodoList());
|
|
6428
6549
|
const lastTurnRef = useRef(null);
|
|
@@ -6577,6 +6698,7 @@ function App({
|
|
|
6577
6698
|
}
|
|
6578
6699
|
client.models().then((m) => {
|
|
6579
6700
|
catalogRef.current = m;
|
|
6701
|
+
setCatalogArrived((n) => n + 1);
|
|
6580
6702
|
searchRef.current = client.capabilities?.web_search === true;
|
|
6581
6703
|
const successor = client.retiredModels[modelRef.current];
|
|
6582
6704
|
if (successor) {
|
|
@@ -6841,8 +6963,11 @@ function App({
|
|
|
6841
6963
|
push({ kind: "notice", text: ` showing the model: ${event.labels.join(", ")}` });
|
|
6842
6964
|
return;
|
|
6843
6965
|
case "usage":
|
|
6966
|
+
balanceSeqRef.current++;
|
|
6844
6967
|
setBalance(event.balance);
|
|
6845
|
-
if (event.
|
|
6968
|
+
if (event.spendable) {
|
|
6969
|
+
takeSpendable(event.spendable, event.balance);
|
|
6970
|
+
} else if (event.creditsCharged > 0 && premiumRef.current) {
|
|
6846
6971
|
const p = premiumRef.current;
|
|
6847
6972
|
setPremium({
|
|
6848
6973
|
...p,
|
|
@@ -6860,8 +6985,18 @@ function App({
|
|
|
6860
6985
|
return;
|
|
6861
6986
|
}
|
|
6862
6987
|
},
|
|
6863
|
-
[cols, flushDelta, push, setPremium]
|
|
6988
|
+
[cols, flushDelta, push, setPremium, takeSpendable]
|
|
6864
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]);
|
|
6865
7000
|
const runTurn2 = useCallback(
|
|
6866
7001
|
async (task, images = []) => {
|
|
6867
7002
|
pendingFreeRef.current = [];
|
|
@@ -6884,7 +7019,9 @@ function App({
|
|
|
6884
7019
|
if (res) {
|
|
6885
7020
|
history = res.messages.slice(1);
|
|
6886
7021
|
setMessages(history);
|
|
7022
|
+
balanceSeqRef.current++;
|
|
6887
7023
|
setBalance(res.balance);
|
|
7024
|
+
if (res.spendable) takeSpendable(res.spendable, res.balance);
|
|
6888
7025
|
setSpent((s) => s + res.creditsCharged);
|
|
6889
7026
|
push({
|
|
6890
7027
|
kind: "notice",
|
|
@@ -7006,6 +7143,10 @@ ${preferencesText}` : ""),
|
|
|
7006
7143
|
}
|
|
7007
7144
|
}
|
|
7008
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
|
+
}
|
|
7009
7150
|
if (err instanceof PremiumLockedError) {
|
|
7010
7151
|
pendingTaskRef.current = { task, images };
|
|
7011
7152
|
pendingPremiumRef.current = err;
|
|
@@ -7055,7 +7196,7 @@ ${preferencesText}` : ""),
|
|
|
7055
7196
|
} else if (err instanceof AuthError) {
|
|
7056
7197
|
push({ kind: "notice", tone: "error", text: NOT_SIGNED_IN });
|
|
7057
7198
|
} else {
|
|
7058
|
-
push({ kind: "notice", tone: "error", text: ` error: ${errorNotice(err.message)}` });
|
|
7199
|
+
push({ kind: "notice", tone: "error", text: ` error: ${errorNotice(err.message, void 0, " ")}` });
|
|
7059
7200
|
}
|
|
7060
7201
|
} finally {
|
|
7061
7202
|
abortRef.current = null;
|
|
@@ -7080,7 +7221,8 @@ ${preferencesText}` : ""),
|
|
|
7080
7221
|
nextSponsor,
|
|
7081
7222
|
permit,
|
|
7082
7223
|
push,
|
|
7083
|
-
root
|
|
7224
|
+
root,
|
|
7225
|
+
takeSpendable
|
|
7084
7226
|
]
|
|
7085
7227
|
);
|
|
7086
7228
|
const stopCurrent = useCallback(() => {
|
|
@@ -7099,7 +7241,7 @@ ${preferencesText}` : ""),
|
|
|
7099
7241
|
} catch (err) {
|
|
7100
7242
|
if (ac.signal.aborted) push({ kind: "notice", tone: "warn", text: " (stopped)" });
|
|
7101
7243
|
else if (err instanceof AuthError) push({ kind: "notice", tone: "error", text: NOT_SIGNED_IN });
|
|
7102
|
-
else push({ kind: "notice", tone: "error", text: ` ${errorNotice(err.message)}` });
|
|
7244
|
+
else push({ kind: "notice", tone: "error", text: ` ${errorNotice(err.message, void 0, " ")}` });
|
|
7103
7245
|
} finally {
|
|
7104
7246
|
busyAbortRef.current = null;
|
|
7105
7247
|
setBusyLabel(WORKING);
|
|
@@ -7592,8 +7734,13 @@ ${preferencesText}` : ""),
|
|
|
7592
7734
|
case "wallet":
|
|
7593
7735
|
await runBusy(LOADING, async (signal) => {
|
|
7594
7736
|
const streakNow = await client.claimStreak(signal);
|
|
7595
|
-
const w = await client.wallet(signal);
|
|
7596
|
-
|
|
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) : [];
|
|
7597
7744
|
const reversals = w.ledger.filter((e) => e.reason === "ad_reversal");
|
|
7598
7745
|
const reversed = reversals.reduce((sum, e) => sum + Math.abs(e.delta), 0);
|
|
7599
7746
|
const screenouts = w.screenouts_today ?? 0;
|
|
@@ -7601,7 +7748,8 @@ ${preferencesText}` : ""),
|
|
|
7601
7748
|
const streakLine = streakNow && streakLabel(streakNow);
|
|
7602
7749
|
push({
|
|
7603
7750
|
kind: "notice",
|
|
7604
|
-
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 ? `
|
|
7605
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 ? `
|
|
7606
7754
|
${streakLine}` : "") + (reversals.length ? `
|
|
7607
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.` : "")
|
|
@@ -8256,14 +8404,25 @@ ${dropped.map((l) => ` \xB7 ${l}`).join("\n")}`
|
|
|
8256
8404
|
const typed = toText(editor);
|
|
8257
8405
|
let estimate;
|
|
8258
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
|
+
})();
|
|
8259
8418
|
const effortNow = info ? resolveEffort(info, effortBy[model]) : effortBy[model];
|
|
8260
8419
|
const shape = client.taskShape;
|
|
8261
8420
|
if (!busy && info && shape && balance !== void 0 && !effortRaised(info, resolveEffort(info, effortBy[model])) && typed.trim() !== "" && !/^[/!]/.test(typed.trimStart())) {
|
|
8262
8421
|
const range = estimateTask(info, conversationUsed + schemaTokens + estimateTokens(typed), shape);
|
|
8263
8422
|
const onFree = Boolean(info.free && freeTier?.models.includes(model));
|
|
8264
|
-
const
|
|
8423
|
+
const canSpend = usable ? usable.credits : info.access === "premium" && premium && !premium.unlocked ? Math.min(balance, premium.credits) : balance;
|
|
8265
8424
|
const verdict = taskVerdict(range, {
|
|
8266
|
-
balance:
|
|
8425
|
+
balance: canSpend,
|
|
8267
8426
|
...onFree ? { freeLeft: freeTier.credits_left } : {}
|
|
8268
8427
|
});
|
|
8269
8428
|
estimate = estimateClause(range, verdict, !onFree && (freeTier?.credits_left ?? 0) > 0);
|
|
@@ -8275,6 +8434,7 @@ ${dropped.map((l) => ` \xB7 ${l}`).join("\n")}`
|
|
|
8275
8434
|
model,
|
|
8276
8435
|
...effortNow ? { effort: effortNow } : {},
|
|
8277
8436
|
balance,
|
|
8437
|
+
...usable ? { usable } : {},
|
|
8278
8438
|
spent,
|
|
8279
8439
|
contextPct,
|
|
8280
8440
|
minutes: (Date.now() - runStartedAtRef.current) / 6e4,
|
|
@@ -8460,15 +8620,41 @@ function turnMeta(result) {
|
|
|
8460
8620
|
}
|
|
8461
8621
|
return `[free \xB7 ${n(result.creditsWaived)} credits waived${left}]`;
|
|
8462
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
|
+
}
|
|
8463
8646
|
function statusLine(o, cols) {
|
|
8464
8647
|
const phrasings = o.estimate === void 0 ? [] : typeof o.estimate === "string" ? [o.estimate] : [...o.estimate];
|
|
8648
|
+
const locked = lockedPhrasings(o.usable);
|
|
8465
8649
|
for (const estimate of [...phrasings, void 0]) {
|
|
8466
|
-
const
|
|
8467
|
-
|
|
8650
|
+
for (const held of [...locked, void 0]) {
|
|
8651
|
+
const line2 = statusRow(o, estimate, held, cols);
|
|
8652
|
+
if (visibleLength(line2) <= cols) return line2;
|
|
8653
|
+
}
|
|
8468
8654
|
}
|
|
8469
|
-
return fitRow(statusRow(o, void 0, cols), cols);
|
|
8655
|
+
return fitRow(statusRow(o, void 0, void 0, cols), cols);
|
|
8470
8656
|
}
|
|
8471
|
-
function statusRow(o, estimate, cols) {
|
|
8657
|
+
function statusRow(o, estimate, locked, cols) {
|
|
8472
8658
|
const burn = o.spent > 0 && o.minutes >= 1 ? `${Math.round(o.spent / o.minutes).toLocaleString("en-US")} cr/min` : void 0;
|
|
8473
8659
|
const clauses = [
|
|
8474
8660
|
{ text: o.model, rank: KEEP_ALWAYS },
|
|
@@ -8477,9 +8663,15 @@ function statusRow(o, estimate, cols) {
|
|
|
8477
8663
|
{
|
|
8478
8664
|
// "? cr" rather than "0 cr": unknown is not empty, and an empty-looking
|
|
8479
8665
|
// wallet is the one misreading on this row that frightens somebody.
|
|
8480
|
-
|
|
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`,
|
|
8481
8670
|
rank: KEEP_ALWAYS
|
|
8482
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 },
|
|
8483
8675
|
// Above the context warning, below the balance. It is the second number
|
|
8484
8676
|
// deciding whether the next turn costs anything, and unlike the spend it is
|
|
8485
8677
|
// *news* — it only exists while a free model is selected and it counts down.
|