clixad 0.0.1-beta.26 → 0.0.1-beta.28
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 +303 -34
- package/package.json +1 -1
package/dist/clixad.mjs
CHANGED
|
@@ -302,6 +302,52 @@ import { gzipSync } from "node:zlib";
|
|
|
302
302
|
function encodeChatBody(json) {
|
|
303
303
|
return gzipSync(Buffer.from(json, "utf8"));
|
|
304
304
|
}
|
|
305
|
+
function accessColumn(m, premium) {
|
|
306
|
+
if (m.access === void 0) return m.tier.padEnd(8);
|
|
307
|
+
if (m.access === "budget") return "budget ";
|
|
308
|
+
return premiumLocked(premium) ? "locked " : "premium ";
|
|
309
|
+
}
|
|
310
|
+
function premiumLocked(premium) {
|
|
311
|
+
return premium !== void 0 && !premium.unlocked && premium.credits <= 0;
|
|
312
|
+
}
|
|
313
|
+
function premiumUnlockHint(unlockUsd, streak) {
|
|
314
|
+
const base = typeof unlockUsd === "number" && unlockUsd > 0 ? `premium: a survey or ${usd(unlockUsd)} unlocks it` : "premium: a survey unlocks it";
|
|
315
|
+
const s = usableStreak(streak);
|
|
316
|
+
return s && s.day < s.min_day ? `${base}, or a ${s.min_day}-day streak` : base;
|
|
317
|
+
}
|
|
318
|
+
function premiumHeadline(unlockUsd) {
|
|
319
|
+
return typeof unlockUsd === "number" && unlockUsd > 0 ? `Premium: complete a survey or unlock from ${usd(unlockUsd)}.` : "Premium: complete a survey to unlock it.";
|
|
320
|
+
}
|
|
321
|
+
function usd(v) {
|
|
322
|
+
return Number.isInteger(v) ? `$${v}` : `$${v.toFixed(2)}`;
|
|
323
|
+
}
|
|
324
|
+
function usableStreak(streak) {
|
|
325
|
+
if (typeof streak !== "object" || streak === null) return void 0;
|
|
326
|
+
const s = streak;
|
|
327
|
+
const num = (v) => typeof v === "number" && Number.isFinite(v) ? v : void 0;
|
|
328
|
+
const day = num(s.day);
|
|
329
|
+
const minDay = num(s.min_day);
|
|
330
|
+
const daily = num(s.daily_credits);
|
|
331
|
+
const left = num(s.credits_left);
|
|
332
|
+
if (day === void 0 || minDay === void 0 || daily === void 0 || left === void 0 || daily <= 0) {
|
|
333
|
+
return void 0;
|
|
334
|
+
}
|
|
335
|
+
return { day, min_day: minDay, daily_credits: daily, credits_left: left };
|
|
336
|
+
}
|
|
337
|
+
function premiumStreakLine(streak) {
|
|
338
|
+
const s = usableStreak(streak);
|
|
339
|
+
if (!s) return null;
|
|
340
|
+
const n = (x) => x.toLocaleString("en-US");
|
|
341
|
+
if (s.day < s.min_day) {
|
|
342
|
+
const wait = s.min_day - s.day;
|
|
343
|
+
const when = wait === 1 ? "tomorrow" : wait === 2 ? "the day after tomorrow" : `in ${wait} days`;
|
|
344
|
+
return `From day ${s.min_day} of your login streak, ${n(s.daily_credits)} bonus credits a day work on premium models too. You are on day ${s.day}, so that starts ${when} if you open Clixad every day.`;
|
|
345
|
+
}
|
|
346
|
+
if (s.credits_left <= 0) {
|
|
347
|
+
return `Today's ${n(s.daily_credits)} bonus credits for premium models are used up. They come back at 00:00 UTC while your login streak holds.`;
|
|
348
|
+
}
|
|
349
|
+
return `Your login streak lets ${n(s.credits_left)} more bonus credits go to premium models today.`;
|
|
350
|
+
}
|
|
305
351
|
function adsPerTaskLabel(estAdsPerTask) {
|
|
306
352
|
const effort = estAdsPerTask >= 1 ? `~${estAdsPerTask} ads/task` : "<1 ad/task";
|
|
307
353
|
return `${effort} on completion (median)`;
|
|
@@ -319,7 +365,18 @@ function streakLabel(streak) {
|
|
|
319
365
|
if (!streak.claimed) return `daily streak: ${where} \xB7 today's already counted`;
|
|
320
366
|
const tomorrow = streak.schedule[Math.min(streak.day, streak.length - 1)];
|
|
321
367
|
const next = tomorrow === void 0 ? "" : ` \xB7 tomorrow ${n(tomorrow)}`;
|
|
322
|
-
return `daily streak: ${where} \xB7 +${n(streak.credits)} credits${next}`;
|
|
368
|
+
return `daily streak: ${where} \xB7 +${n(streak.credits)} credits${next}${streakPremiumClause(streak)}`;
|
|
369
|
+
}
|
|
370
|
+
function streakPremiumClause(streak) {
|
|
371
|
+
const p = streak.premium;
|
|
372
|
+
if (!p) return "";
|
|
373
|
+
return streak.day < p.minDay ? ` \xB7 bonus credits on premium from day ${p.minDay}` : ` \xB7 ${p.dailyCredits.toLocaleString("en-US")} bonus credits a day on premium`;
|
|
374
|
+
}
|
|
375
|
+
function streakPremium(raw) {
|
|
376
|
+
if (typeof raw !== "object" || raw === null) return {};
|
|
377
|
+
const p = raw;
|
|
378
|
+
if (typeof p.min_day !== "number" || typeof p.daily_credits !== "number" || p.daily_credits <= 0) return {};
|
|
379
|
+
return { premium: { minDay: p.min_day, dailyCredits: p.daily_credits } };
|
|
323
380
|
}
|
|
324
381
|
function referLines(info) {
|
|
325
382
|
const n = (x) => x.toLocaleString("en-US");
|
|
@@ -405,16 +462,36 @@ function asSignupClosed(status, data) {
|
|
|
405
462
|
const d = data;
|
|
406
463
|
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;
|
|
407
464
|
}
|
|
465
|
+
function premiumLockedError(message, model, body) {
|
|
466
|
+
const num = (v) => typeof v === "number" && Number.isFinite(v) ? v : 0;
|
|
467
|
+
return new PremiumLockedError(
|
|
468
|
+
message,
|
|
469
|
+
typeof body?.model === "string" ? body.model : model,
|
|
470
|
+
num(body?.needed_credits),
|
|
471
|
+
num(body?.premium_credits),
|
|
472
|
+
typeof body?.unlock_usd === "number" && body.unlock_usd > 0 ? body.unlock_usd : null,
|
|
473
|
+
num(body?.grant_per_ad),
|
|
474
|
+
freeAlternatives(body?.free_models),
|
|
475
|
+
usableStreak(body?.streak)
|
|
476
|
+
);
|
|
477
|
+
}
|
|
408
478
|
function shortfallHeadline(err, model) {
|
|
409
479
|
const n = (v) => Math.max(0, Math.round(v)).toLocaleString("en-US");
|
|
410
480
|
if (err.balance <= 0) return `Out of credits (this needs about ${n(err.estimatedCost)}).`;
|
|
411
481
|
return `Not enough credits for this turn${model ? ` on ${model}` : ""}: it needs about ${n(err.estimatedCost)}, and the balance is ${n(err.balance)}.`;
|
|
412
482
|
}
|
|
413
483
|
function paywallError(body) {
|
|
414
|
-
|
|
484
|
+
return new PaywallError(body.balance, body.estimated_cost, body.grant_per_ad, freeAlternatives(body.free_models));
|
|
485
|
+
}
|
|
486
|
+
function freeAlternatives(list) {
|
|
487
|
+
return Array.isArray(list) ? list.filter(
|
|
415
488
|
(m) => !!m && typeof m.id === "string" && m.id.length > 0 && Number.isFinite(m.turns_left)
|
|
416
489
|
).map((m) => ({ id: m.id, turnsLeft: Math.max(0, Math.floor(m.turns_left)) })).filter((m) => m.turnsLeft > 0) : [];
|
|
417
|
-
|
|
490
|
+
}
|
|
491
|
+
async function refusal402(res, model) {
|
|
492
|
+
const body = await res.json();
|
|
493
|
+
if (body.error === "premium_locked") return premiumLockedError(body.message ?? "", model, body.premium);
|
|
494
|
+
return paywallError(body);
|
|
418
495
|
}
|
|
419
496
|
function neverConnected(code) {
|
|
420
497
|
return code !== void 0 && NEVER_CONNECTED.has(code);
|
|
@@ -540,7 +617,7 @@ async function* parseSSE(body) {
|
|
|
540
617
|
}
|
|
541
618
|
}
|
|
542
619
|
}
|
|
543
|
-
var CHAT_ENCODED_CONTENT_TYPE, LEDGER_REASONS, SignupClosedError, PaywallError, GatewayUnreachableError, NEVER_CONNECTED, AuthError, GatewayClient;
|
|
620
|
+
var CHAT_ENCODED_CONTENT_TYPE, LEDGER_REASONS, SignupClosedError, PaywallError, PremiumLockedError, GatewayUnreachableError, NEVER_CONNECTED, AuthError, GatewayClient;
|
|
544
621
|
var init_client = __esm({
|
|
545
622
|
"src/client.ts"() {
|
|
546
623
|
"use strict";
|
|
@@ -590,6 +667,26 @@ var init_client = __esm({
|
|
|
590
667
|
grantPerAd;
|
|
591
668
|
freeModels;
|
|
592
669
|
};
|
|
670
|
+
PremiumLockedError = class extends Error {
|
|
671
|
+
constructor(message, model, neededCredits, premiumCredits, unlockUsd, grantPerAd, freeModels, streak) {
|
|
672
|
+
super(message);
|
|
673
|
+
this.model = model;
|
|
674
|
+
this.neededCredits = neededCredits;
|
|
675
|
+
this.premiumCredits = premiumCredits;
|
|
676
|
+
this.unlockUsd = unlockUsd;
|
|
677
|
+
this.grantPerAd = grantPerAd;
|
|
678
|
+
this.freeModels = freeModels;
|
|
679
|
+
this.streak = streak;
|
|
680
|
+
this.name = "PremiumLockedError";
|
|
681
|
+
}
|
|
682
|
+
model;
|
|
683
|
+
neededCredits;
|
|
684
|
+
premiumCredits;
|
|
685
|
+
unlockUsd;
|
|
686
|
+
grantPerAd;
|
|
687
|
+
freeModels;
|
|
688
|
+
streak;
|
|
689
|
+
};
|
|
593
690
|
GatewayUnreachableError = class extends Error {
|
|
594
691
|
constructor(host, code) {
|
|
595
692
|
const where = `${host}${code ? ` (${code})` : ""}`;
|
|
@@ -747,7 +844,8 @@ var init_client = __esm({
|
|
|
747
844
|
claimed: body.claimed === true,
|
|
748
845
|
balance: typeof body.balance === "number" ? body.balance : 0,
|
|
749
846
|
schedule: Array.isArray(body.schedule) ? body.schedule : [],
|
|
750
|
-
length: typeof body.length === "number" ? body.length : 0
|
|
847
|
+
length: typeof body.length === "number" ? body.length : 0,
|
|
848
|
+
...streakPremium(body.premium)
|
|
751
849
|
};
|
|
752
850
|
} catch {
|
|
753
851
|
return null;
|
|
@@ -886,6 +984,7 @@ var init_client = __esm({
|
|
|
886
984
|
if (!res.ok) throw new Error(`models failed: ${res.status}`);
|
|
887
985
|
const data = await res.json();
|
|
888
986
|
this.seen = { web_search: data.capabilities?.web_search === true };
|
|
987
|
+
this.unlockUsd = typeof data.premium_unlock_usd === "number" && data.premium_unlock_usd > 0 ? data.premium_unlock_usd : null;
|
|
889
988
|
this.shape = parseTaskShape(data.task_shape);
|
|
890
989
|
this.latest = typeof data.cli_latest === "string" ? data.cli_latest : void 0;
|
|
891
990
|
const live = new Set(data.data.map((m) => m.id));
|
|
@@ -936,8 +1035,23 @@ var init_client = __esm({
|
|
|
936
1035
|
return this.seen;
|
|
937
1036
|
}
|
|
938
1037
|
seen = { web_search: false };
|
|
939
|
-
|
|
940
|
-
|
|
1038
|
+
/**
|
|
1039
|
+
* What unlocks premium for every credit on the account (the cheapest pack,
|
|
1040
|
+
* USD), as of the last catalog fetch. Null where the gateway sells none or
|
|
1041
|
+
* did not say, which the hints read as "a survey unlocks it".
|
|
1042
|
+
*/
|
|
1043
|
+
get premiumUnlockUsd() {
|
|
1044
|
+
return this.unlockUsd;
|
|
1045
|
+
}
|
|
1046
|
+
unlockUsd = null;
|
|
1047
|
+
/**
|
|
1048
|
+
* `premium: true` asks for premium access too, which costs the gateway a read
|
|
1049
|
+
* of this account's whole ledger. Asked for where it is shown (`/model`,
|
|
1050
|
+
* `clixad models`), never by the polls `/earn` and a purchase run.
|
|
1051
|
+
*/
|
|
1052
|
+
async wallet(signal, opts = {}) {
|
|
1053
|
+
const url = `${this.config.gatewayUrl}/v1/wallet${opts.premium ? "?premium=1" : ""}`;
|
|
1054
|
+
const res = await gatewayFetch(url, { headers: this.headers(), signal });
|
|
941
1055
|
if (res.status === 401) throw await authFailure(res, "wallet");
|
|
942
1056
|
if (!res.ok) throw new Error(`wallet failed: ${res.status}`);
|
|
943
1057
|
return res.json();
|
|
@@ -1234,9 +1348,7 @@ var init_client = __esm({
|
|
|
1234
1348
|
signal
|
|
1235
1349
|
);
|
|
1236
1350
|
if (res.status === 401) throw await authFailure(res, "chat");
|
|
1237
|
-
if (res.status === 402)
|
|
1238
|
-
throw paywallError(await res.json());
|
|
1239
|
-
}
|
|
1351
|
+
if (res.status === 402) throw await refusal402(res, model);
|
|
1240
1352
|
if (!res.ok) throw new Error(await httpFailure(res, "chat"));
|
|
1241
1353
|
const data = await res.json();
|
|
1242
1354
|
const choice = data.choices[0];
|
|
@@ -1267,9 +1379,7 @@ var init_client = __esm({
|
|
|
1267
1379
|
opts.signal
|
|
1268
1380
|
);
|
|
1269
1381
|
if (res.status === 401) throw await authFailure(res, "chat");
|
|
1270
|
-
if (res.status === 402)
|
|
1271
|
-
throw paywallError(await res.json());
|
|
1272
|
-
}
|
|
1382
|
+
if (res.status === 402) throw await refusal402(res, model);
|
|
1273
1383
|
if (!res.ok || !res.body) throw new Error(await httpFailure(res, "chat"));
|
|
1274
1384
|
let content = "";
|
|
1275
1385
|
let finishReason = "stop";
|
|
@@ -1277,6 +1387,7 @@ var init_client = __esm({
|
|
|
1277
1387
|
let free = {};
|
|
1278
1388
|
let balance = 0;
|
|
1279
1389
|
let streamError;
|
|
1390
|
+
let premiumRefusal;
|
|
1280
1391
|
const calls = /* @__PURE__ */ new Map();
|
|
1281
1392
|
const reasoning = [];
|
|
1282
1393
|
for await (const evt of parseSSE(res.body)) {
|
|
@@ -1299,12 +1410,16 @@ var init_client = __esm({
|
|
|
1299
1410
|
mergeReasoningDetails(reasoning, choice?.delta?.reasoning_details);
|
|
1300
1411
|
if (choice?.finish_reason) finishReason = choice.finish_reason;
|
|
1301
1412
|
if (json.error) streamError = json.error.message || "the model stream failed";
|
|
1413
|
+
if (json.error?.type === "premium_locked") {
|
|
1414
|
+
premiumRefusal = premiumLockedError(streamError ?? "", model, json.clixad?.premium);
|
|
1415
|
+
}
|
|
1302
1416
|
if (json.clixad) {
|
|
1303
1417
|
creditsCharged = json.clixad.credits_charged;
|
|
1304
1418
|
balance = json.clixad.balance;
|
|
1305
1419
|
free = freeMeta(json.clixad);
|
|
1306
1420
|
}
|
|
1307
1421
|
}
|
|
1422
|
+
if (premiumRefusal) throw premiumRefusal;
|
|
1308
1423
|
if (streamError) throw new Error(streamError);
|
|
1309
1424
|
const message = { role: "assistant", content: content || null };
|
|
1310
1425
|
if (calls.size > 0) {
|
|
@@ -6150,6 +6265,16 @@ function App({
|
|
|
6150
6265
|
const pendingFreeRef = useRef([]);
|
|
6151
6266
|
const pendingGrantRef = useRef(0);
|
|
6152
6267
|
const pendingBalanceRef = useRef(void 0);
|
|
6268
|
+
const pendingPremiumRef = useRef(null);
|
|
6269
|
+
const [premium, setPremiumState] = useState(wallet?.premium);
|
|
6270
|
+
const premiumRef = useRef(wallet?.premium);
|
|
6271
|
+
const setPremium = useCallback((next) => {
|
|
6272
|
+
premiumRef.current = next;
|
|
6273
|
+
setPremiumState(next);
|
|
6274
|
+
}, []);
|
|
6275
|
+
const refreshPremium = useCallback(() => {
|
|
6276
|
+
void client.wallet(void 0, { premium: true }).then((w) => setPremium(w.premium)).catch(() => void 0);
|
|
6277
|
+
}, [client, setPremium]);
|
|
6153
6278
|
const mcpRef = useRef(McpHub.empty());
|
|
6154
6279
|
const todosRef = useRef(new TodoList());
|
|
6155
6280
|
const lastTurnRef = useRef(null);
|
|
@@ -6259,9 +6384,10 @@ function App({
|
|
|
6259
6384
|
useEffect(() => {
|
|
6260
6385
|
if (wallet) return;
|
|
6261
6386
|
const ctl = new AbortController();
|
|
6262
|
-
void client.wallet(ctl.signal).then((w) => {
|
|
6387
|
+
void client.wallet(ctl.signal, { premium: true }).then((w) => {
|
|
6263
6388
|
setBalance((current) => current ?? w.balance);
|
|
6264
6389
|
setFreeTier((current) => current ?? w.free_tier);
|
|
6390
|
+
if (premiumRef.current === void 0) setPremium(w.premium);
|
|
6265
6391
|
}).catch(() => void 0);
|
|
6266
6392
|
return () => ctl.abort();
|
|
6267
6393
|
}, []);
|
|
@@ -6556,6 +6682,13 @@ function App({
|
|
|
6556
6682
|
return;
|
|
6557
6683
|
case "usage":
|
|
6558
6684
|
setBalance(event.balance);
|
|
6685
|
+
if (event.creditsCharged > 0 && premiumRef.current) {
|
|
6686
|
+
const p = premiumRef.current;
|
|
6687
|
+
setPremium({
|
|
6688
|
+
...p,
|
|
6689
|
+
credits: Math.max(0, p.unlocked ? event.balance : Math.min(event.balance, p.credits - event.creditsCharged))
|
|
6690
|
+
});
|
|
6691
|
+
}
|
|
6559
6692
|
setSpent((s) => s + event.creditsCharged);
|
|
6560
6693
|
if (event.free) {
|
|
6561
6694
|
setFreeTier(
|
|
@@ -6567,11 +6700,12 @@ function App({
|
|
|
6567
6700
|
return;
|
|
6568
6701
|
}
|
|
6569
6702
|
},
|
|
6570
|
-
[cols, flushDelta, push]
|
|
6703
|
+
[cols, flushDelta, push, setPremium]
|
|
6571
6704
|
);
|
|
6572
6705
|
const runTurn2 = useCallback(
|
|
6573
6706
|
async (task, images = []) => {
|
|
6574
6707
|
pendingFreeRef.current = [];
|
|
6708
|
+
pendingPremiumRef.current = null;
|
|
6575
6709
|
const model2 = modelRef.current;
|
|
6576
6710
|
const ac = new AbortController();
|
|
6577
6711
|
abortRef.current = ac;
|
|
@@ -6703,7 +6837,22 @@ ${NO_SEARCH_INSTRUCTION}`),
|
|
|
6703
6837
|
}
|
|
6704
6838
|
}
|
|
6705
6839
|
} catch (err) {
|
|
6706
|
-
if (err instanceof
|
|
6840
|
+
if (err instanceof PremiumLockedError) {
|
|
6841
|
+
pendingTaskRef.current = { task, images };
|
|
6842
|
+
pendingPremiumRef.current = err;
|
|
6843
|
+
setPremium({ unlocked: false, credits: err.premiumCredits, ...err.streak ? { streak: err.streak } : {} });
|
|
6844
|
+
const short2 = err.premiumCredits > 0 ? ` This turn needs about ${err.neededCredits.toLocaleString("en-US")} of those credits, and you have ${err.premiumCredits.toLocaleString("en-US")}.
|
|
6845
|
+
` : "";
|
|
6846
|
+
const streakLine = premiumStreakLine(err.streak);
|
|
6847
|
+
push({
|
|
6848
|
+
kind: "notice",
|
|
6849
|
+
tone: "warn",
|
|
6850
|
+
text: ` ${premiumHeadline(err.unlockUsd)}
|
|
6851
|
+
${err.model} is a premium model. It runs on credits you earned on the offer walls or bought.
|
|
6852
|
+
` + short2 + (streakLine ? ` ${streakLine}
|
|
6853
|
+
` : "") + " Your bonus credits (the signup bonus, our own surveys) still work on budget models.\n Press enter to unlock it or to switch to a budget model. Your request is kept."
|
|
6854
|
+
});
|
|
6855
|
+
} else if (err instanceof PaywallError) {
|
|
6707
6856
|
pendingTaskRef.current = { task, images };
|
|
6708
6857
|
pendingFreeRef.current = err.freeModels;
|
|
6709
6858
|
pendingGrantRef.current = err.grantPerAd;
|
|
@@ -6898,8 +7047,9 @@ ${NO_SEARCH_INSTRUCTION}`),
|
|
|
6898
7047
|
text: " no credit yet \u2014 offers take a while to confirm, and a screenout pays nothing.\n That is normal: start another with /earn, or check /wallet later."
|
|
6899
7048
|
});
|
|
6900
7049
|
push({ kind: "notice", tone: "good", text: " credits added \u2014 continuing" });
|
|
7050
|
+
refreshPremium();
|
|
6901
7051
|
if (pending) await runTurn2(pending.task, pending.images);
|
|
6902
|
-
}, [balance, client, config.dashboardUrl, push, runBusy, runTurn2]);
|
|
7052
|
+
}, [balance, client, config.dashboardUrl, push, refreshPremium, runBusy, runTurn2]);
|
|
6903
7053
|
const runPaywallChoice = useCallback(() => {
|
|
6904
7054
|
const free = pendingFreeRef.current.filter((m) => m.id !== EARN_ROW);
|
|
6905
7055
|
if (free.length === 0) return void runAdWall();
|
|
@@ -6969,6 +7119,47 @@ ${NO_SEARCH_INSTRUCTION}`),
|
|
|
6969
7119
|
});
|
|
6970
7120
|
});
|
|
6971
7121
|
}, [client, push, runBusy]);
|
|
7122
|
+
const runPremiumChoice = useCallback(() => {
|
|
7123
|
+
const refusal = pendingPremiumRef.current;
|
|
7124
|
+
if (!refusal) return void runPaywallChoice();
|
|
7125
|
+
const items = [
|
|
7126
|
+
{
|
|
7127
|
+
value: EARN_ROW,
|
|
7128
|
+
label: "complete a survey",
|
|
7129
|
+
hint: `opens the offer walls, offers pay ${grantLabel(refusal.grantPerAd)}`
|
|
7130
|
+
},
|
|
7131
|
+
...refusal.unlockUsd ? [
|
|
7132
|
+
{
|
|
7133
|
+
value: BUY_ROW,
|
|
7134
|
+
label: `unlock from ${usd(refusal.unlockUsd)}`,
|
|
7135
|
+
hint: "any pack, and premium then takes all your credits"
|
|
7136
|
+
}
|
|
7137
|
+
] : [],
|
|
7138
|
+
...refusal.freeModels.filter((m) => m.id !== EARN_ROW && m.id !== BUY_ROW).map((m) => ({
|
|
7139
|
+
value: m.id,
|
|
7140
|
+
label: m.id,
|
|
7141
|
+
hint: `budget, free: ~${m.turnsLeft} turn${m.turnsLeft === 1 ? "" : "s"} left today`
|
|
7142
|
+
}))
|
|
7143
|
+
];
|
|
7144
|
+
setPickerSel(0);
|
|
7145
|
+
setPicker({
|
|
7146
|
+
title: premiumHeadline(refusal.unlockUsd),
|
|
7147
|
+
subtitle: "Unlock premium, or carry on with a budget model.",
|
|
7148
|
+
items,
|
|
7149
|
+
onPick: (choice) => {
|
|
7150
|
+
if (choice.value === EARN_ROW) return void runAdWall();
|
|
7151
|
+
if (choice.value === BUY_ROW) return void runBuy();
|
|
7152
|
+
const pending = pendingTaskRef.current;
|
|
7153
|
+
pendingTaskRef.current = null;
|
|
7154
|
+
pendingPremiumRef.current = null;
|
|
7155
|
+
config.model = choice.value;
|
|
7156
|
+
saveConfig(config);
|
|
7157
|
+
switchModel(choice.value);
|
|
7158
|
+
push({ kind: "notice", tone: "good", text: ` model \u2192 ${choice.value} (budget) \xB7 continuing where you left off` });
|
|
7159
|
+
if (pending) void runTurn2(pending.task, pending.images);
|
|
7160
|
+
}
|
|
7161
|
+
});
|
|
7162
|
+
}, [config, push, runAdWall, runBuy, runPaywallChoice, runTurn2, switchModel]);
|
|
6972
7163
|
const runCheckout = useCallback(
|
|
6973
7164
|
async (pack) => {
|
|
6974
7165
|
let before = balance ?? (await client.wallet().catch(() => void 0))?.balance;
|
|
@@ -7006,11 +7197,20 @@ ${NO_SEARCH_INSTRUCTION}`),
|
|
|
7006
7197
|
stopped = signal.aborted;
|
|
7007
7198
|
});
|
|
7008
7199
|
if (credited) {
|
|
7009
|
-
|
|
7200
|
+
push({
|
|
7010
7201
|
kind: "notice",
|
|
7011
7202
|
tone: "good",
|
|
7012
7203
|
text: " credits added \u2014 an invoice is on its way to the address on the payment."
|
|
7013
7204
|
});
|
|
7205
|
+
refreshPremium();
|
|
7206
|
+
const pending = pendingPremiumRef.current ? pendingTaskRef.current : null;
|
|
7207
|
+
if (pending) {
|
|
7208
|
+
pendingTaskRef.current = null;
|
|
7209
|
+
pendingPremiumRef.current = null;
|
|
7210
|
+
push({ kind: "notice", tone: "good", text: " premium is unlocked \xB7 continuing where you left off" });
|
|
7211
|
+
await runTurn2(pending.task, pending.images);
|
|
7212
|
+
}
|
|
7213
|
+
return;
|
|
7014
7214
|
}
|
|
7015
7215
|
push({
|
|
7016
7216
|
kind: "notice",
|
|
@@ -7018,7 +7218,7 @@ ${NO_SEARCH_INSTRUCTION}`),
|
|
|
7018
7218
|
text: stopped ? " stopped waiting. Credits still land when the payment clears \u2014 /wallet checks the balance." : " no credits yet \u2014 the payment may still be clearing. /wallet checks the balance."
|
|
7019
7219
|
});
|
|
7020
7220
|
},
|
|
7021
|
-
[balance, client, push, runBusy]
|
|
7221
|
+
[balance, client, push, refreshPremium, runBusy, runTurn2]
|
|
7022
7222
|
);
|
|
7023
7223
|
const runCommand = useCallback(
|
|
7024
7224
|
async (line2) => {
|
|
@@ -7099,15 +7299,21 @@ ${NO_SEARCH_INSTRUCTION}`),
|
|
|
7099
7299
|
if (arg && !models.some((m) => m.id === arg)) {
|
|
7100
7300
|
push({ kind: "notice", tone: "warn", text: ` unknown model: ${arg}` });
|
|
7101
7301
|
}
|
|
7102
|
-
const free = await client.wallet(signal).then((w) => {
|
|
7302
|
+
const free = await client.wallet(signal, { premium: true }).then((w) => {
|
|
7103
7303
|
setBalance(w.balance);
|
|
7104
7304
|
setFreeTier(w.free_tier);
|
|
7305
|
+
setPremium(w.premium);
|
|
7105
7306
|
return w.free_tier;
|
|
7106
|
-
}).catch(() =>
|
|
7307
|
+
}).catch(() => {
|
|
7308
|
+
setPremium(void 0);
|
|
7309
|
+
return freeTier;
|
|
7310
|
+
});
|
|
7311
|
+
const premium2 = premiumRef.current;
|
|
7312
|
+
const unlockUsd = client.premiumUnlockUsd;
|
|
7107
7313
|
const items = models.map((m) => ({
|
|
7108
7314
|
value: m.id,
|
|
7109
7315
|
label: m.id,
|
|
7110
|
-
hint:
|
|
7316
|
+
hint: accessColumn(m, premium2) + (m.access === "premium" && premiumLocked(premium2) ? premiumUnlockHint(unlockUsd, premium2?.streak) : modelHint(m, free)),
|
|
7111
7317
|
current: m.id === model
|
|
7112
7318
|
}));
|
|
7113
7319
|
const preselect = items.findIndex((i) => i.value === arg);
|
|
@@ -7121,6 +7327,18 @@ ${NO_SEARCH_INSTRUCTION}`),
|
|
|
7121
7327
|
saveConfig(config);
|
|
7122
7328
|
switchModel(choice.value);
|
|
7123
7329
|
push({ kind: "notice", text: ` model \u2192 ${choice.value}` });
|
|
7330
|
+
const picked = models.find((m) => m.id === choice.value);
|
|
7331
|
+
if (picked?.access === "premium" && premiumLocked(premium2)) {
|
|
7332
|
+
const streakLine = premiumStreakLine(premium2?.streak);
|
|
7333
|
+
push({
|
|
7334
|
+
kind: "notice",
|
|
7335
|
+
tone: "warn",
|
|
7336
|
+
text: ` ${premiumHeadline(unlockUsd)}
|
|
7337
|
+
${choice.value} runs on credits you earned on the offer walls or bought.
|
|
7338
|
+
` + (streakLine ? ` ${streakLine}
|
|
7339
|
+
` : "") + " /earn opens the offer walls" + (unlockUsd ? `, /buy unlocks premium from ${usd(unlockUsd)}.` : ".") + " Your bonus credits work on budget models."
|
|
7340
|
+
});
|
|
7341
|
+
}
|
|
7124
7342
|
}
|
|
7125
7343
|
});
|
|
7126
7344
|
});
|
|
@@ -7189,7 +7407,7 @@ ${NO_SEARCH_INSTRUCTION}`),
|
|
|
7189
7407
|
catalogRef.current = ms;
|
|
7190
7408
|
push({
|
|
7191
7409
|
kind: "notice",
|
|
7192
|
-
text: ms.map((m) => ` ${m.id.padEnd(24)} ${modelHint(m, freeTier)}`).join("\n")
|
|
7410
|
+
text: ms.map((m) => ` ${m.id.padEnd(24)} ${accessColumn(m, premium)}${modelHint(m, freeTier)}`).join("\n")
|
|
7193
7411
|
});
|
|
7194
7412
|
});
|
|
7195
7413
|
return;
|
|
@@ -7351,7 +7569,7 @@ ${NO_SEARCH_INSTRUCTION}`),
|
|
|
7351
7569
|
push({ kind: "notice", tone: "warn", text: ` unknown command: /${cmd} \u2014 try /help` });
|
|
7352
7570
|
}
|
|
7353
7571
|
},
|
|
7354
|
-
[client, cols, config, cycleMode, exit, model, push, root, runAdWall, runBusy, runBuy, runLogin, runTurn2, writeToStdout]
|
|
7572
|
+
[client, cols, config, cycleMode, exit, model, premium, push, root, runAdWall, runBusy, runBuy, runLogin, runTurn2, setPremium, writeToStdout]
|
|
7355
7573
|
);
|
|
7356
7574
|
const runShell = useCallback(
|
|
7357
7575
|
async (command) => {
|
|
@@ -7681,7 +7899,9 @@ ${dropped.map((l) => ` \xB7 ${l}`).join("\n")}`
|
|
|
7681
7899
|
if (key.return) {
|
|
7682
7900
|
if (key.meta || key.shift) return setEditor(newline(editor));
|
|
7683
7901
|
if (endsWithContinuation(editor)) return setEditor(continueLine(editor));
|
|
7684
|
-
if (!busy && pendingTaskRef.current && isEmpty(editor))
|
|
7902
|
+
if (!busy && pendingTaskRef.current && isEmpty(editor)) {
|
|
7903
|
+
return void (pendingPremiumRef.current ? runPremiumChoice() : runPaywallChoice());
|
|
7904
|
+
}
|
|
7685
7905
|
return void submit(toText(editor));
|
|
7686
7906
|
}
|
|
7687
7907
|
if (key.ctrl && ch === "j") return setEditor(newline(editor));
|
|
@@ -7862,7 +8082,11 @@ ${dropped.map((l) => ` \xB7 ${l}`).join("\n")}`
|
|
|
7862
8082
|
if (!busy && info && shape && balance !== void 0 && !effortRaised(info, resolveEffort(info, effortBy[model])) && typed.trim() !== "" && !/^[/!]/.test(typed.trimStart())) {
|
|
7863
8083
|
const range = estimateTask(info, conversationUsed + schemaTokens + estimateTokens(typed), shape);
|
|
7864
8084
|
const onFree = Boolean(info.free && freeTier?.models.includes(model));
|
|
7865
|
-
const
|
|
8085
|
+
const spendable = info.access === "premium" && premium && !premium.unlocked ? Math.min(balance, premium.credits) : balance;
|
|
8086
|
+
const verdict = taskVerdict(range, {
|
|
8087
|
+
balance: spendable,
|
|
8088
|
+
...onFree ? { freeLeft: freeTier.credits_left } : {}
|
|
8089
|
+
});
|
|
7866
8090
|
estimate = estimateClause(range, verdict, !onFree && (freeTier?.credits_left ?? 0) > 0);
|
|
7867
8091
|
}
|
|
7868
8092
|
const modeText = ` ${MODE_STYLE[mode].glyph} ${MODE_LABEL[mode]}`;
|
|
@@ -8147,7 +8371,7 @@ function tailLines(text, max) {
|
|
|
8147
8371
|
const lines = text.split("\n");
|
|
8148
8372
|
return lines.length <= max ? text : lines.slice(-max).join("\n");
|
|
8149
8373
|
}
|
|
8150
|
-
var WORKING, WAITING_FOR_REWARD, LOADING, COMPACTING, SIGNING_IN, RUNNING, SENDING_FEEDBACK, WAITING_FOR_PAYMENT, FEEDBACK_TURN_INTERVAL, NOT_SIGNED_IN, LIVE_OUTPUT_LINES, COMMITTED_OUTPUT_LINES, DELTA_FLUSH_MS, MAX_INPUT_ROWS, MAX_PICKER_ROWS, MAX_QUEUE_ROWS, QUEUE_MARKER, PICKER_CURRENT, MIN_PICKER_LABEL_COLS, PICKER_CHROME, PICKER_CHROME_COMPACT, ASK_CHROME, PLAN_CHROME, BUY_CHROME, BUY_CHROME_COMPACT, ASK_HINT, PLAN_HINT, BUY_HINT, BUY_CONFIRM_WORD, BUY_TYPED_MAX, BUY_TOO_SHORT, EXPAND_MAX_LINES, FALLBACK_ROWS, FALLBACK_COLS, INIT_HINT_AFTER_TURNS, EARN_ROW, CONTEXT_NOTICE_AT, KEEP_ALWAYS;
|
|
8374
|
+
var WORKING, WAITING_FOR_REWARD, LOADING, COMPACTING, SIGNING_IN, RUNNING, SENDING_FEEDBACK, WAITING_FOR_PAYMENT, FEEDBACK_TURN_INTERVAL, NOT_SIGNED_IN, LIVE_OUTPUT_LINES, COMMITTED_OUTPUT_LINES, DELTA_FLUSH_MS, MAX_INPUT_ROWS, MAX_PICKER_ROWS, MAX_QUEUE_ROWS, QUEUE_MARKER, PICKER_CURRENT, MIN_PICKER_LABEL_COLS, PICKER_CHROME, PICKER_CHROME_COMPACT, ASK_CHROME, PLAN_CHROME, BUY_CHROME, BUY_CHROME_COMPACT, ASK_HINT, PLAN_HINT, BUY_HINT, BUY_CONFIRM_WORD, BUY_TYPED_MAX, BUY_TOO_SHORT, EXPAND_MAX_LINES, FALLBACK_ROWS, FALLBACK_COLS, INIT_HINT_AFTER_TURNS, EARN_ROW, BUY_ROW, CONTEXT_NOTICE_AT, KEEP_ALWAYS;
|
|
8151
8375
|
var init_app = __esm({
|
|
8152
8376
|
"src/tui/app.tsx"() {
|
|
8153
8377
|
"use strict";
|
|
@@ -8231,6 +8455,7 @@ var init_app = __esm({
|
|
|
8231
8455
|
FALLBACK_COLS = 80;
|
|
8232
8456
|
INIT_HINT_AFTER_TURNS = 3;
|
|
8233
8457
|
EARN_ROW = "\0earn";
|
|
8458
|
+
BUY_ROW = "\0buy";
|
|
8234
8459
|
CONTEXT_NOTICE_AT = 0.6;
|
|
8235
8460
|
KEEP_ALWAYS = Number.POSITIVE_INFINITY;
|
|
8236
8461
|
}
|
|
@@ -8469,17 +8694,42 @@ function earnedToday(w) {
|
|
|
8469
8694
|
}
|
|
8470
8695
|
async function listModels(client) {
|
|
8471
8696
|
const models = await client.models();
|
|
8472
|
-
const
|
|
8697
|
+
const wallet = await client.wallet(void 0, { premium: true }).catch(() => void 0);
|
|
8698
|
+
const free = wallet?.free_tier;
|
|
8699
|
+
const premium = wallet?.premium;
|
|
8473
8700
|
console.log(c.bold("Models (credit prices per 1M tokens)"));
|
|
8474
8701
|
for (const m of models) {
|
|
8475
8702
|
const tag = m.ad_fundable ? "" : c.dim(" [paid/BYOK]");
|
|
8476
8703
|
const turns2 = free?.models.includes(m.id) ? free.turns_left[m.id] ?? 0 : void 0;
|
|
8477
8704
|
const freeTag = !m.free ? "" : turns2 === void 0 ? c.green(" [free]") : turns2 > 0 ? c.green(` [free: ~${turns2} turn${turns2 === 1 ? "" : "s"} left today]`) : c.dim(" [free: none left today]");
|
|
8705
|
+
const lockTag = m.access === "premium" && premiumLocked(premium) ? c.yellow(` [${premiumHeadline(client.premiumUnlockUsd).replace(/\.$/, "")}]`) : "";
|
|
8478
8706
|
console.log(
|
|
8479
|
-
` ${c.cyan(m.id.padEnd(22))} ${m
|
|
8707
|
+
` ${c.cyan(m.id.padEnd(22))} ${accessColumn(m, premium)} in ${String(m.credits_per_mtoken_input).padStart(9)} out ${String(m.credits_per_mtoken_output).padStart(9)} ${adsPerTaskLabel(m.est_ads_per_task)}${tag}${freeTag}${lockTag}`
|
|
8480
8708
|
);
|
|
8481
8709
|
}
|
|
8482
8710
|
}
|
|
8711
|
+
function printPremiumRefusal(err) {
|
|
8712
|
+
console.log(c.yellow(premiumHeadline(err.unlockUsd)));
|
|
8713
|
+
console.log(
|
|
8714
|
+
c.yellow(`${err.model} is a premium model. It runs on credits you earned on the offer walls or bought.`)
|
|
8715
|
+
);
|
|
8716
|
+
if (err.premiumCredits > 0) {
|
|
8717
|
+
console.log(
|
|
8718
|
+
c.yellow(
|
|
8719
|
+
`This turn needs about ${err.neededCredits.toLocaleString("en-US")} of those credits, and you have ${err.premiumCredits.toLocaleString("en-US")}.`
|
|
8720
|
+
)
|
|
8721
|
+
);
|
|
8722
|
+
}
|
|
8723
|
+
const streakLine = premiumStreakLine(err.streak);
|
|
8724
|
+
if (streakLine) console.log(c.yellow(streakLine));
|
|
8725
|
+
console.log(c.yellow(`Run \`clixad earn\` for a survey on the offer walls. Offers pay ${grantLabel(err.grantPerAd)}.`));
|
|
8726
|
+
if (err.unlockUsd) {
|
|
8727
|
+
console.log(c.yellow(`Or unlock premium from ${usd(err.unlockUsd)}: start \`clixad\` and type /buy.`));
|
|
8728
|
+
}
|
|
8729
|
+
const freeLine = freeAlternativeLine(err.freeModels);
|
|
8730
|
+
console.log(c.green(freeLine ?? "Your bonus credits still work on budget models: `clixad models` lists them."));
|
|
8731
|
+
console.log(c.dim(" Nothing was charged for this request."));
|
|
8732
|
+
}
|
|
8483
8733
|
async function setModel(client, config, model) {
|
|
8484
8734
|
if (!model) return console.log(`current model: ${c.cyan(config.model)}`);
|
|
8485
8735
|
const known = await client.models().catch(() => void 0);
|
|
@@ -8493,6 +8743,20 @@ async function setModel(client, config, model) {
|
|
|
8493
8743
|
config.model = model;
|
|
8494
8744
|
saveConfig(config);
|
|
8495
8745
|
console.log(c.green(`\u2713 model set to ${model}`));
|
|
8746
|
+
const picked = known?.find((m) => m.id === model);
|
|
8747
|
+
if (picked?.access === "premium") {
|
|
8748
|
+
const w = await client.wallet(void 0, { premium: true }).catch(() => void 0);
|
|
8749
|
+
if (premiumLocked(w?.premium)) {
|
|
8750
|
+
console.log(c.yellow(premiumHeadline(client.premiumUnlockUsd)));
|
|
8751
|
+
console.log(
|
|
8752
|
+
c.yellow(
|
|
8753
|
+
`${model} runs on credits you earned on the offer walls or bought. Run \`clixad earn\` for a survey` + (client.premiumUnlockUsd ? `, or start \`clixad\` and type /buy.` : ".")
|
|
8754
|
+
)
|
|
8755
|
+
);
|
|
8756
|
+
const streakLine = premiumStreakLine(w?.premium?.streak);
|
|
8757
|
+
if (streakLine) console.log(c.yellow(streakLine));
|
|
8758
|
+
}
|
|
8759
|
+
}
|
|
8496
8760
|
}
|
|
8497
8761
|
async function showWallet(client) {
|
|
8498
8762
|
const streak = await client.claimStreak();
|
|
@@ -8626,7 +8890,10 @@ ${NO_SEARCH_INSTRUCTION}`),
|
|
|
8626
8890
|
[${cost} \xB7 balance ${res.balance} \xB7 ${res.steps} step(s)]`)
|
|
8627
8891
|
);
|
|
8628
8892
|
} catch (err) {
|
|
8629
|
-
if (err instanceof
|
|
8893
|
+
if (err instanceof PremiumLockedError) {
|
|
8894
|
+
console.log("");
|
|
8895
|
+
printPremiumRefusal(err);
|
|
8896
|
+
} else if (err instanceof PaywallError) {
|
|
8630
8897
|
console.log(c.yellow(`
|
|
8631
8898
|
${shortfallHeadline(err, config.model)}`));
|
|
8632
8899
|
const freeLine = freeAlternativeLine(err.freeModels);
|
|
@@ -8761,7 +9028,9 @@ async function runTurn(client, config, messages) {
|
|
|
8761
9028
|
return result;
|
|
8762
9029
|
} catch (err) {
|
|
8763
9030
|
if (started) process.stdout.write("\n");
|
|
8764
|
-
if (err instanceof
|
|
9031
|
+
if (err instanceof PremiumLockedError) {
|
|
9032
|
+
printPremiumRefusal(err);
|
|
9033
|
+
} else if (err instanceof PaywallError) {
|
|
8765
9034
|
console.log(c.yellow(shortfallHeadline(err, config.model)));
|
|
8766
9035
|
const freeLine = freeAlternativeLine(err.freeModels);
|
|
8767
9036
|
if (freeLine) console.log(c.green(freeLine));
|
|
@@ -8785,9 +9054,9 @@ async function showStreak(client) {
|
|
|
8785
9054
|
console.log(streak.claimed ? c.green(` ${line2}`) : c.dim(` ${line2}`));
|
|
8786
9055
|
return streak;
|
|
8787
9056
|
}
|
|
8788
|
-
async function safeWallet(client) {
|
|
9057
|
+
async function safeWallet(client, opts = {}) {
|
|
8789
9058
|
try {
|
|
8790
|
-
return await client.wallet();
|
|
9059
|
+
return await client.wallet(void 0, opts);
|
|
8791
9060
|
} catch {
|
|
8792
9061
|
return void 0;
|
|
8793
9062
|
}
|
|
@@ -8813,7 +9082,7 @@ async function startupReads(client, gatewayUrl) {
|
|
|
8813
9082
|
)
|
|
8814
9083
|
];
|
|
8815
9084
|
try {
|
|
8816
|
-
const [streak, wallet] = await Promise.all([client.claimStreak(), safeWallet(client)]);
|
|
9085
|
+
const [streak, wallet] = await Promise.all([client.claimStreak(), safeWallet(client, { premium: true })]);
|
|
8817
9086
|
const balance = streak?.claimed ? streak.balance : wallet?.balance;
|
|
8818
9087
|
return {
|
|
8819
9088
|
streak,
|