clixad 0.0.1-beta.20 → 0.0.1-beta.21
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 +212 -120
- package/package.json +1 -1
package/dist/clixad.mjs
CHANGED
|
@@ -168,13 +168,13 @@ function encodeChatBody(json) {
|
|
|
168
168
|
}
|
|
169
169
|
function adsPerTaskLabel(estAdsPerTask) {
|
|
170
170
|
const effort = estAdsPerTask >= 1 ? `~${estAdsPerTask} ads/task` : "<1 ad/task";
|
|
171
|
-
return `${effort} on completion`;
|
|
171
|
+
return `${effort} on completion (median)`;
|
|
172
172
|
}
|
|
173
|
-
function
|
|
174
|
-
if (!
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
return
|
|
173
|
+
function grantLabel(grantPerAd) {
|
|
174
|
+
if (typeof grantPerAd !== "number" || !Number.isFinite(grantPerAd) || grantPerAd <= 0) {
|
|
175
|
+
return "credits on completion";
|
|
176
|
+
}
|
|
177
|
+
return `~${grantPerAd.toLocaleString("en-US")} credits on completion (median)`;
|
|
178
178
|
}
|
|
179
179
|
function streakLabel(streak) {
|
|
180
180
|
if (streak.length <= 0 || streak.day <= 0) return null;
|
|
@@ -252,12 +252,7 @@ function asSignupClosed(status, data) {
|
|
|
252
252
|
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;
|
|
253
253
|
}
|
|
254
254
|
function paywallError(body) {
|
|
255
|
-
return new PaywallError(
|
|
256
|
-
body.balance,
|
|
257
|
-
body.estimated_cost,
|
|
258
|
-
body.grant_per_ad,
|
|
259
|
-
body.grant_per_ad_range
|
|
260
|
-
);
|
|
255
|
+
return new PaywallError(body.balance, body.estimated_cost, body.grant_per_ad);
|
|
261
256
|
}
|
|
262
257
|
async function authFailure(res, what) {
|
|
263
258
|
const body = await res.text().catch(() => "");
|
|
@@ -366,18 +361,16 @@ var init_client = __esm({
|
|
|
366
361
|
waitlistUrl;
|
|
367
362
|
};
|
|
368
363
|
PaywallError = class extends Error {
|
|
369
|
-
constructor(balance, estimatedCost, grantPerAd
|
|
364
|
+
constructor(balance, estimatedCost, grantPerAd) {
|
|
370
365
|
super("Out of credits");
|
|
371
366
|
this.balance = balance;
|
|
372
367
|
this.estimatedCost = estimatedCost;
|
|
373
368
|
this.grantPerAd = grantPerAd;
|
|
374
|
-
this.grantRange = grantRange;
|
|
375
369
|
this.name = "PaywallError";
|
|
376
370
|
}
|
|
377
371
|
balance;
|
|
378
372
|
estimatedCost;
|
|
379
373
|
grantPerAd;
|
|
380
|
-
grantRange;
|
|
381
374
|
};
|
|
382
375
|
AuthError = class extends Error {
|
|
383
376
|
constructor() {
|
|
@@ -610,8 +603,13 @@ var init_client = __esm({
|
|
|
610
603
|
balance: typeof body.balance === "number" ? body.balance : 0
|
|
611
604
|
};
|
|
612
605
|
}
|
|
613
|
-
|
|
614
|
-
|
|
606
|
+
/**
|
|
607
|
+
* Both billing calls take a signal, for the reason `models` does: the only
|
|
608
|
+
* surface that buys credits is now `/buy` at the REPL prompt, which awaits
|
|
609
|
+
* them with the input box locked and "esc to stop" on screen.
|
|
610
|
+
*/
|
|
611
|
+
async creditPacks(signal) {
|
|
612
|
+
const res = await fetch(`${this.config.gatewayUrl}/v1/billing/packs`, { signal });
|
|
615
613
|
if (!res.ok) throw new Error(`packs failed: ${res.status}`);
|
|
616
614
|
return res.json();
|
|
617
615
|
}
|
|
@@ -621,10 +619,11 @@ var init_client = __esm({
|
|
|
621
619
|
* the gateway refuses a checkout without it, because starting a purchase we
|
|
622
620
|
* could not deliver instantly is worse than not starting one.
|
|
623
621
|
*/
|
|
624
|
-
async checkout(pack, consent) {
|
|
622
|
+
async checkout(pack, consent, signal) {
|
|
625
623
|
const res = await fetch(`${this.config.gatewayUrl}/v1/billing/checkout`, {
|
|
626
624
|
method: "POST",
|
|
627
625
|
headers: this.headers(),
|
|
626
|
+
...signal ? { signal } : {},
|
|
628
627
|
body: JSON.stringify({ pack, consent })
|
|
629
628
|
});
|
|
630
629
|
if (res.status === 401) throw await authFailure(res, "checkout");
|
|
@@ -632,7 +631,7 @@ var init_client = __esm({
|
|
|
632
631
|
if (!res.ok) throw new Error(await httpFailure(res, "checkout"));
|
|
633
632
|
return res.json();
|
|
634
633
|
}
|
|
635
|
-
//
|
|
634
|
+
// These take an optional signal for the REPL's sake: they are awaited while the
|
|
636
635
|
// input box is locked, and a gateway that is cold-starting (Render's free tier,
|
|
637
636
|
// the better part of a minute) would otherwise be indistinguishable from a
|
|
638
637
|
// hung terminal. Nothing else passes one — a one-shot CLI command has Ctrl+C.
|
|
@@ -4730,7 +4729,7 @@ var init_lines = __esm({
|
|
|
4730
4729
|
{ id: "wallet", text: "/wallet shows your balance and what you've earned today" },
|
|
4731
4730
|
{ id: "cheapest-first", text: "/model lists cheapest-turn-first" },
|
|
4732
4731
|
{ id: "model-cost", text: "current model: {ads_per_task}", needs: "ads_per_task" },
|
|
4733
|
-
{ id: "earn", text: "/earn: offers pay {
|
|
4732
|
+
{ id: "earn", text: "/earn: offers pay {grant}", needs: "grant" },
|
|
4734
4733
|
{ id: "screenout", text: "a screenout pays nothing and is normal \u2014 just start another" }
|
|
4735
4734
|
];
|
|
4736
4735
|
}
|
|
@@ -4739,7 +4738,7 @@ var init_lines = __esm({
|
|
|
4739
4738
|
// src/sponsor/provider.ts
|
|
4740
4739
|
function fill(line2, facts) {
|
|
4741
4740
|
if (!line2.needs) return line2.text;
|
|
4742
|
-
const value = line2.needs === "
|
|
4741
|
+
const value = line2.needs === "grant" ? facts.grant : facts.adsPerTask;
|
|
4743
4742
|
if (!value) return null;
|
|
4744
4743
|
return line2.text.replace(`{${line2.needs}}`, value);
|
|
4745
4744
|
}
|
|
@@ -4934,6 +4933,12 @@ var init_commands = __esm({
|
|
|
4934
4933
|
// completing to "/redeem " is the helpful outcome rather than the surprising
|
|
4935
4934
|
// one.
|
|
4936
4935
|
{ name: "redeem", args: "<code>", desc: "spend a code you were given" },
|
|
4936
|
+
// The only purchase surface in the CLI. It used to be `clixad buy`, a
|
|
4937
|
+
// subcommand you had to quit the REPL to reach — and one nobody found, since
|
|
4938
|
+
// the REPL is the default entry point and never mentioned it. Deliberately
|
|
4939
|
+
// *no* `args` hint, for `/model`'s reason: an arg hint makes enter complete
|
|
4940
|
+
// the command instead of running it, and the pack picker is the point.
|
|
4941
|
+
{ name: "buy", desc: "buy a credit pack (optional \u2014 offers are free)" },
|
|
4937
4942
|
// Reachable from inside the REPL because that is where the 401 is printed.
|
|
4938
4943
|
// "Run `clixad login` first" is not a runnable instruction at this prompt —
|
|
4939
4944
|
// anything without a leading slash is a prompt, so it went to the model and
|
|
@@ -5383,6 +5388,23 @@ var init_views = __esm({
|
|
|
5383
5388
|
import { useCallback, useEffect, useMemo, useRef, useState, useSyncExternalStore } from "react";
|
|
5384
5389
|
import { Box as Box2, Static, Text as Text2, useApp, useInput, usePaste, useStdout } from "ink";
|
|
5385
5390
|
import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
5391
|
+
function buyDeclarationLines(pack) {
|
|
5392
|
+
return [
|
|
5393
|
+
`${pack.label} \u2014 ${pack.credits.toLocaleString("en-US")} credits for $${pack.price_usd.toFixed(2)}`,
|
|
5394
|
+
"",
|
|
5395
|
+
"Before you pay, please confirm:",
|
|
5396
|
+
" \xB7 You request that we deliver the credits immediately, before the 14-day withdrawal period has expired.",
|
|
5397
|
+
" \xB7 You acknowledge that your right of withdrawal lapses once they have been delivered."
|
|
5398
|
+
].join("\n");
|
|
5399
|
+
}
|
|
5400
|
+
function buyConfirmLines(pack) {
|
|
5401
|
+
return [
|
|
5402
|
+
buyDeclarationLines(pack),
|
|
5403
|
+
"",
|
|
5404
|
+
"One purchase. No subscription, nothing to cancel.",
|
|
5405
|
+
`Terms: ${TERMS_URL}`
|
|
5406
|
+
].join("\n");
|
|
5407
|
+
}
|
|
5386
5408
|
function useTerminalSize() {
|
|
5387
5409
|
const { stdout } = useStdout();
|
|
5388
5410
|
const snapshot2 = useRef({
|
|
@@ -5453,6 +5475,7 @@ function App({ client, config, wallet, streak, session, initialTask, notices: no
|
|
|
5453
5475
|
const [plan, setPlan] = useState(null);
|
|
5454
5476
|
const [picker, setPicker] = useState(null);
|
|
5455
5477
|
const [pickerSel, setPickerSel] = useState(0);
|
|
5478
|
+
const [buy, setBuy] = useState(null);
|
|
5456
5479
|
const [tick, setTick] = useState(0);
|
|
5457
5480
|
const [startedAt, setStartedAt] = useState(0);
|
|
5458
5481
|
const [quitHint, setQuitHint] = useState(false);
|
|
@@ -5479,6 +5502,7 @@ function App({ client, config, wallet, streak, session, initialTask, notices: no
|
|
|
5479
5502
|
const outputsRef = useRef([]);
|
|
5480
5503
|
const expandedRef = useRef(/* @__PURE__ */ new Set());
|
|
5481
5504
|
const pickerWindowRef = useRef({ from: 0, count: 0 });
|
|
5505
|
+
const buyFitsRef = useRef(false);
|
|
5482
5506
|
const spinnerRef = useRef(spinnerFrames());
|
|
5483
5507
|
const runningToolRef = useRef(null);
|
|
5484
5508
|
const pendingTaskRef = useRef(null);
|
|
@@ -5625,7 +5649,7 @@ function App({ client, config, wallet, streak, session, initialTask, notices: no
|
|
|
5625
5649
|
}
|
|
5626
5650
|
const est = catalogRef.current.find((m) => m.id === modelId)?.est_ads_per_task;
|
|
5627
5651
|
const facts = {
|
|
5628
|
-
|
|
5652
|
+
grant: wallet?.grant_per_ad ? grantLabel(wallet.grant_per_ad) : void 0,
|
|
5629
5653
|
adsPerTask: est === void 0 ? void 0 : adsPerTaskLabel(est)
|
|
5630
5654
|
};
|
|
5631
5655
|
const picked = sponsorRef.current.pick(sponsorIdxRef.current++, sponsorPrevRef.current, facts);
|
|
@@ -5890,9 +5914,10 @@ ${NO_SEARCH_INSTRUCTION}`),
|
|
|
5890
5914
|
kind: "notice",
|
|
5891
5915
|
tone: "warn",
|
|
5892
5916
|
text: ` Out of credits (balance ${err.balance}, need ~${err.estimatedCost}).
|
|
5893
|
-
Press enter to open the ad wall \u2014 offers pay ${
|
|
5917
|
+
Press enter to open the ad wall \u2014 offers pay ${grantLabel(err.grantPerAd)}.
|
|
5894
5918
|
I'll continue automatically. Offers you are screened out of pay nothing;
|
|
5895
|
-
that is normal, just start another
|
|
5919
|
+
that is normal, just start another.
|
|
5920
|
+
/buy is there too, if you would rather pay than do an offer.`
|
|
5896
5921
|
});
|
|
5897
5922
|
} else if (err instanceof AuthError) {
|
|
5898
5923
|
push({ kind: "notice", tone: "error", text: NOT_SIGNED_IN });
|
|
@@ -6048,6 +6073,83 @@ ${NO_SEARCH_INSTRUCTION}`),
|
|
|
6048
6073
|
push({ kind: "notice", tone: "good", text: " credits added \u2014 continuing" });
|
|
6049
6074
|
if (pending) await runTurn2(pending.task, pending.images);
|
|
6050
6075
|
}, [balance, client, config.dashboardUrl, push, runBusy, runTurn2]);
|
|
6076
|
+
const runBuy = useCallback(async () => {
|
|
6077
|
+
await runBusy(LOADING, async (signal) => {
|
|
6078
|
+
const { enabled, packs } = await client.creditPacks(signal);
|
|
6079
|
+
if (!enabled || !packs.length) {
|
|
6080
|
+
return void push({
|
|
6081
|
+
kind: "notice",
|
|
6082
|
+
tone: "warn",
|
|
6083
|
+
text: " buying credits isn't enabled on this gateway \u2014 /earn tops up the wallet for free."
|
|
6084
|
+
});
|
|
6085
|
+
}
|
|
6086
|
+
const items = packs.map((pack) => ({
|
|
6087
|
+
value: pack.id,
|
|
6088
|
+
label: pack.label,
|
|
6089
|
+
hint: `${pack.credits.toLocaleString("en-US")} credits $${pack.price_usd.toFixed(2)}`
|
|
6090
|
+
}));
|
|
6091
|
+
setPickerSel(0);
|
|
6092
|
+
setPicker({
|
|
6093
|
+
title: "Buy credits",
|
|
6094
|
+
subtitle: "Optional \u2014 everything here can be paid for with credits you earn.",
|
|
6095
|
+
items,
|
|
6096
|
+
// No `onCancel`: nothing is awaiting this. Closing it is the end of it,
|
|
6097
|
+
// exactly as with `/model`.
|
|
6098
|
+
onPick: (choice) => {
|
|
6099
|
+
const pack = packs.find((x) => x.id === choice.value);
|
|
6100
|
+
if (pack) setBuy({ pack, typed: "" });
|
|
6101
|
+
}
|
|
6102
|
+
});
|
|
6103
|
+
});
|
|
6104
|
+
}, [client, push, runBusy]);
|
|
6105
|
+
const runCheckout = useCallback(
|
|
6106
|
+
async (pack) => {
|
|
6107
|
+
const before = balance;
|
|
6108
|
+
let credited = false;
|
|
6109
|
+
let stopped = false;
|
|
6110
|
+
await runBusy(WAITING_FOR_PAYMENT, async (signal) => {
|
|
6111
|
+
const checkout = await client.checkout(
|
|
6112
|
+
pack.id,
|
|
6113
|
+
{
|
|
6114
|
+
requestImmediatePerformance: true,
|
|
6115
|
+
acknowledgeLossOfWithdrawal: true,
|
|
6116
|
+
at: (/* @__PURE__ */ new Date()).toISOString()
|
|
6117
|
+
},
|
|
6118
|
+
signal
|
|
6119
|
+
);
|
|
6120
|
+
openBrowser(checkout.url);
|
|
6121
|
+
push({
|
|
6122
|
+
kind: "notice",
|
|
6123
|
+
text: ` opened Stripe Checkout \u2014 ${checkout.credits.toLocaleString("en-US")} credits for $${checkout.price_usd.toFixed(2)}
|
|
6124
|
+
${checkout.url}`
|
|
6125
|
+
});
|
|
6126
|
+
const deadline = Date.now() + 5 * 6e4;
|
|
6127
|
+
while (Date.now() < deadline && !credited && !signal.aborted) {
|
|
6128
|
+
await sleep4(3e3, signal);
|
|
6129
|
+
if (signal.aborted) break;
|
|
6130
|
+
const w = await client.wallet(signal).catch(() => void 0);
|
|
6131
|
+
if (w && w.balance > before) {
|
|
6132
|
+
setBalance(w.balance);
|
|
6133
|
+
credited = true;
|
|
6134
|
+
}
|
|
6135
|
+
}
|
|
6136
|
+
stopped = signal.aborted;
|
|
6137
|
+
});
|
|
6138
|
+
if (credited) {
|
|
6139
|
+
return push({
|
|
6140
|
+
kind: "notice",
|
|
6141
|
+
tone: "good",
|
|
6142
|
+
text: " credits added \u2014 an invoice is on its way to the address on the payment."
|
|
6143
|
+
});
|
|
6144
|
+
}
|
|
6145
|
+
push({
|
|
6146
|
+
kind: "notice",
|
|
6147
|
+
tone: "warn",
|
|
6148
|
+
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."
|
|
6149
|
+
});
|
|
6150
|
+
},
|
|
6151
|
+
[balance, client, push, runBusy]
|
|
6152
|
+
);
|
|
6051
6153
|
const runCommand = useCallback(
|
|
6052
6154
|
async (line2) => {
|
|
6053
6155
|
const [cmd, ...rest] = line2.slice(1).split(" ");
|
|
@@ -6293,6 +6395,9 @@ ${NO_SEARCH_INSTRUCTION}`),
|
|
|
6293
6395
|
push({ kind: "notice", text: referLines(info).map((line3) => ` ${line3}`).join("\n") });
|
|
6294
6396
|
});
|
|
6295
6397
|
return;
|
|
6398
|
+
case "buy":
|
|
6399
|
+
await runBuy();
|
|
6400
|
+
return;
|
|
6296
6401
|
case "redeem": {
|
|
6297
6402
|
if (!arg) {
|
|
6298
6403
|
push({ kind: "notice", tone: "warn", text: " usage: /redeem <code>" });
|
|
@@ -6317,7 +6422,7 @@ ${NO_SEARCH_INSTRUCTION}`),
|
|
|
6317
6422
|
push({ kind: "notice", tone: "warn", text: ` unknown command: /${cmd} \u2014 try /help` });
|
|
6318
6423
|
}
|
|
6319
6424
|
},
|
|
6320
|
-
[client, cols, config, cycleMode, exit, model, push, root, runAdWall, runBusy, runLogin, runTurn2, writeToStdout]
|
|
6425
|
+
[client, cols, config, cycleMode, exit, model, push, root, runAdWall, runBusy, runBuy, runLogin, runTurn2, writeToStdout]
|
|
6321
6426
|
);
|
|
6322
6427
|
const runShell = useCallback(
|
|
6323
6428
|
async (command) => {
|
|
@@ -6460,7 +6565,7 @@ ${output}` }
|
|
|
6460
6565
|
}) : [],
|
|
6461
6566
|
[query, root]
|
|
6462
6567
|
);
|
|
6463
|
-
const menu = menuOff || ask2 || picker || plan ? [] : matches;
|
|
6568
|
+
const menu = menuOff || ask2 || picker || plan || buy ? [] : matches;
|
|
6464
6569
|
useEffect(() => {
|
|
6465
6570
|
setSel(0);
|
|
6466
6571
|
setMenuOff(false);
|
|
@@ -6474,7 +6579,7 @@ ${output}` }
|
|
|
6474
6579
|
[cursor, query, text]
|
|
6475
6580
|
);
|
|
6476
6581
|
usePaste((text2) => {
|
|
6477
|
-
if (ask2 || picker || plan) return;
|
|
6582
|
+
if (ask2 || picker || plan || buy) return;
|
|
6478
6583
|
setEditor((state) => insert(state, text2));
|
|
6479
6584
|
setHistIdx(-1);
|
|
6480
6585
|
});
|
|
@@ -6492,7 +6597,8 @@ ${output}` }
|
|
|
6492
6597
|
setPicker(null);
|
|
6493
6598
|
cancel?.();
|
|
6494
6599
|
}
|
|
6495
|
-
|
|
6600
|
+
if (buy) setBuy(null);
|
|
6601
|
+
}, [ask2, buy, picker, plan]);
|
|
6496
6602
|
useInput((ch, key) => {
|
|
6497
6603
|
if (key.ctrl && ch === "c") {
|
|
6498
6604
|
if (busy) {
|
|
@@ -6568,6 +6674,37 @@ ${output}` }
|
|
|
6568
6674
|
}
|
|
6569
6675
|
return;
|
|
6570
6676
|
}
|
|
6677
|
+
if (buy) {
|
|
6678
|
+
const cancelled = () => push({ kind: "notice", tone: "warn", text: " cancelled \u2014 you have not been charged." });
|
|
6679
|
+
if (key.escape) {
|
|
6680
|
+
setBuy(null);
|
|
6681
|
+
cancelled();
|
|
6682
|
+
return;
|
|
6683
|
+
}
|
|
6684
|
+
if (key.return) {
|
|
6685
|
+
const confirmed = buy.typed.trim().toLowerCase() === BUY_CONFIRM_WORD;
|
|
6686
|
+
if (confirmed && !buyFitsRef.current) {
|
|
6687
|
+
return void push({
|
|
6688
|
+
kind: "notice",
|
|
6689
|
+
tone: "warn",
|
|
6690
|
+
text: " the confirmation does not fit on this terminal \u2014 make it taller, then answer."
|
|
6691
|
+
});
|
|
6692
|
+
}
|
|
6693
|
+
const { pack } = buy;
|
|
6694
|
+
setBuy(null);
|
|
6695
|
+
if (!confirmed) return void cancelled();
|
|
6696
|
+
void runCheckout(pack);
|
|
6697
|
+
return;
|
|
6698
|
+
}
|
|
6699
|
+
if (key.backspace || key.delete) {
|
|
6700
|
+
setBuy((b) => b ? { ...b, typed: b.typed.slice(0, -1) } : b);
|
|
6701
|
+
return;
|
|
6702
|
+
}
|
|
6703
|
+
if (ch && !key.ctrl && !key.meta && /^[\x20-\x7e]+$/.test(ch)) {
|
|
6704
|
+
setBuy((b) => b ? { ...b, typed: (b.typed + ch).slice(0, BUY_TYPED_MAX) } : b);
|
|
6705
|
+
}
|
|
6706
|
+
return;
|
|
6707
|
+
}
|
|
6571
6708
|
if (busy && key.escape) {
|
|
6572
6709
|
const dropped = queueRef.current;
|
|
6573
6710
|
if (dropped.length) {
|
|
@@ -6648,7 +6785,8 @@ ${dropped.map((l) => ` \xB7 ${l}`).join("\n")}`
|
|
|
6648
6785
|
...queueView.length > MAX_QUEUE_ROWS ? [`+${queueView.length - MAX_QUEUE_ROWS} more queued`] : []
|
|
6649
6786
|
].map((line2) => fitRow(` ${QUEUE_MARKER} ${line2}`, cols));
|
|
6650
6787
|
const inner = dialogInner(cols);
|
|
6651
|
-
const
|
|
6788
|
+
const buyEssential = buy ? lineCount(buyDeclarationLines(buy.pack), inner) : 0;
|
|
6789
|
+
const modalFloor = picker ? PICKER_CHROME_COMPACT + 1 : ask2 ? ASK_CHROME + 1 : plan ? PLAN_CHROME + 1 : buy ? BUY_CHROME + buyEssential : 0;
|
|
6652
6790
|
let slack = Math.max(0, viewport - inputRows - modalFloor - menu.length);
|
|
6653
6791
|
const grant = (want) => {
|
|
6654
6792
|
if (want <= 0 || want > slack) return 0;
|
|
@@ -6714,6 +6852,14 @@ ${dropped.map((l) => ` \xB7 ${l}`).join("\n")}`
|
|
|
6714
6852
|
const planBlock = plan ? headRows(renderMarkdown(plan.plan), inner, Math.max(1, budget - PLAN_CHROME)) : "";
|
|
6715
6853
|
const planHeight = plan ? lineCount(planBlock, inner) + PLAN_CHROME : 0;
|
|
6716
6854
|
budget -= planHeight;
|
|
6855
|
+
const buyCompact = Boolean(buy) && budget < BUY_CHROME + 1;
|
|
6856
|
+
const buyChrome = buyCompact ? BUY_CHROME_COMPACT : BUY_CHROME;
|
|
6857
|
+
const buyRoom = Math.max(1, budget - buyChrome);
|
|
6858
|
+
const buyFits = Boolean(buy) && buyRoom >= buyEssential;
|
|
6859
|
+
const buyBlock = buy ? headRows(buyFits ? buyConfirmLines(buy.pack) : BUY_TOO_SHORT, inner, buyRoom) : "";
|
|
6860
|
+
const buyHeight = buy ? lineCount(buyBlock, inner) + buyChrome : 0;
|
|
6861
|
+
budget -= buyHeight;
|
|
6862
|
+
buyFitsRef.current = buyFits;
|
|
6717
6863
|
const liveRaw = [
|
|
6718
6864
|
// Trailing blank lines are rows the clamp would spend on nothing, and a
|
|
6719
6865
|
// streamed answer ends on one more often than not.
|
|
@@ -6735,7 +6881,7 @@ ${dropped.map((l) => ` \xB7 ${l}`).join("\n")}`
|
|
|
6735
6881
|
return n + h;
|
|
6736
6882
|
}, 0);
|
|
6737
6883
|
}, [entries, cols]);
|
|
6738
|
-
const used = chromeHeight + menu.length + pickerHeight + askHeight + planHeight + busyHeight + liveHeight;
|
|
6884
|
+
const used = chromeHeight + menu.length + pickerHeight + askHeight + planHeight + buyHeight + busyHeight + liveHeight;
|
|
6739
6885
|
const spacer = Math.max(0, viewport - printed - used);
|
|
6740
6886
|
const labelW = Math.min(
|
|
6741
6887
|
Math.max(1, Math.floor(cols * 0.5)),
|
|
@@ -6800,6 +6946,16 @@ ${dropped.map((l) => ` \xB7 ${l}`).join("\n")}`
|
|
|
6800
6946
|
/* @__PURE__ */ jsx2(Text2, { children: planBlock }),
|
|
6801
6947
|
/* @__PURE__ */ jsx2(Text2, { dimColor: true, wrap: "truncate-end", children: pickHint(PLAN_HINT, inner) })
|
|
6802
6948
|
] }) : null,
|
|
6949
|
+
buy ? /* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", marginTop: 1, borderStyle: "round", borderColor: MANGO3, paddingX: 1, children: [
|
|
6950
|
+
buyCompact ? null : /* @__PURE__ */ jsx2(Text2, { bold: true, color: MANGO_BRIGHT, wrap: "truncate-end", children: fitRow("Buy credits", inner) }),
|
|
6951
|
+
buyCompact ? null : /* @__PURE__ */ jsx2(Box2, { height: 1 }),
|
|
6952
|
+
/* @__PURE__ */ jsx2(Text2, { children: buyBlock }),
|
|
6953
|
+
/* @__PURE__ */ jsxs2(Box2, { children: [
|
|
6954
|
+
/* @__PURE__ */ jsx2(Text2, { color: MANGO_BRIGHT, children: "\u276F " }),
|
|
6955
|
+
/* @__PURE__ */ jsx2(Text2, { bold: true, wrap: "truncate-end", children: fitRow(buy.typed, Math.max(1, inner - 2)) })
|
|
6956
|
+
] }),
|
|
6957
|
+
/* @__PURE__ */ jsx2(Text2, { dimColor: true, wrap: "truncate-end", children: pickHint(BUY_HINT, inner) })
|
|
6958
|
+
] }) : null,
|
|
6803
6959
|
picker ? /* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", marginTop: 1, borderStyle: "round", borderColor: MANGO3, paddingX: 1, children: [
|
|
6804
6960
|
/* @__PURE__ */ jsx2(Text2, { bold: true, color: MANGO_BRIGHT, children: pickerTitle }),
|
|
6805
6961
|
pickerCompact ? null : /* @__PURE__ */ jsx2(Text2, { dimColor: true, children: pickerSubtitle }),
|
|
@@ -6962,7 +7118,7 @@ function tailLines(text, max) {
|
|
|
6962
7118
|
const lines = text.split("\n");
|
|
6963
7119
|
return lines.length <= max ? text : lines.slice(-max).join("\n");
|
|
6964
7120
|
}
|
|
6965
|
-
var WORKING, WAITING_FOR_REWARD, LOADING, COMPACTING, SIGNING_IN, RUNNING, SENDING_FEEDBACK, 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, ASK_HINT, PLAN_HINT, EXPAND_MAX_LINES, FALLBACK_ROWS, FALLBACK_COLS, CONTEXT_NOTICE_AT, KEEP_ALWAYS;
|
|
7121
|
+
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, CONTEXT_NOTICE_AT, KEEP_ALWAYS;
|
|
6966
7122
|
var init_app = __esm({
|
|
6967
7123
|
"src/tui/app.tsx"() {
|
|
6968
7124
|
"use strict";
|
|
@@ -7000,6 +7156,7 @@ var init_app = __esm({
|
|
|
7000
7156
|
SIGNING_IN = "signing in\u2026";
|
|
7001
7157
|
RUNNING = "running\u2026";
|
|
7002
7158
|
SENDING_FEEDBACK = "sending\u2026";
|
|
7159
|
+
WAITING_FOR_PAYMENT = "waiting for the payment\u2026";
|
|
7003
7160
|
FEEDBACK_TURN_INTERVAL = 50;
|
|
7004
7161
|
NOT_SIGNED_IN = " Not signed in, or the stored token isn't valid for this gateway.\n Run /login to sign in.";
|
|
7005
7162
|
LIVE_OUTPUT_LINES = 5;
|
|
@@ -7015,6 +7172,8 @@ var init_app = __esm({
|
|
|
7015
7172
|
PICKER_CHROME_COMPACT = 5;
|
|
7016
7173
|
ASK_CHROME = 4;
|
|
7017
7174
|
PLAN_CHROME = 6;
|
|
7175
|
+
BUY_CHROME = 7;
|
|
7176
|
+
BUY_CHROME_COMPACT = 5;
|
|
7018
7177
|
ASK_HINT = [
|
|
7019
7178
|
"[y/\u23CE] once \xB7 [a] always \xB7 [n] no \xB7 esc stops the turn",
|
|
7020
7179
|
"y once \xB7 a always \xB7 n no \xB7 esc stops",
|
|
@@ -7025,6 +7184,14 @@ var init_app = __esm({
|
|
|
7025
7184
|
"y ask first \xB7 a auto-accept \xB7 n keep planning \xB7 esc stops",
|
|
7026
7185
|
"y/a/n \xB7 esc"
|
|
7027
7186
|
];
|
|
7187
|
+
BUY_HINT = [
|
|
7188
|
+
"type yes and press \u23CE to open Stripe Checkout \xB7 esc cancels \u2014 nothing is charged either way",
|
|
7189
|
+
"type yes + \u23CE to continue \xB7 esc cancels \u2014 nothing is charged",
|
|
7190
|
+
"yes + \u23CE \xB7 esc"
|
|
7191
|
+
];
|
|
7192
|
+
BUY_CONFIRM_WORD = "yes";
|
|
7193
|
+
BUY_TYPED_MAX = 8;
|
|
7194
|
+
BUY_TOO_SHORT = "Make this terminal taller \u2014 the confirmation has to be readable before you can agree to it.";
|
|
7028
7195
|
EXPAND_MAX_LINES = 400;
|
|
7029
7196
|
FALLBACK_ROWS = 24;
|
|
7030
7197
|
FALLBACK_COLS = 80;
|
|
@@ -7072,7 +7239,6 @@ init_kimi();
|
|
|
7072
7239
|
init_banner();
|
|
7073
7240
|
init_browser();
|
|
7074
7241
|
init_color();
|
|
7075
|
-
import { createInterface } from "node:readline/promises";
|
|
7076
7242
|
|
|
7077
7243
|
// src/title.ts
|
|
7078
7244
|
var APP_TITLE = "Clixad";
|
|
@@ -7130,8 +7296,16 @@ async function main() {
|
|
|
7130
7296
|
return refer(client, config);
|
|
7131
7297
|
case "redeem":
|
|
7132
7298
|
return redeem(client, config, rest[0]);
|
|
7299
|
+
// `clixad buy` is gone, and it is the one removed subcommand that gets a
|
|
7300
|
+
// signpost rather than the ordinary "anything that is not a command is a
|
|
7301
|
+
// task" fallback. A typo reaching the model is the better failure; this is
|
|
7302
|
+
// not a typo — it is what the help output, the terms and every older CLI
|
|
7303
|
+
// have been telling people to run — so left alone it would send "buy
|
|
7304
|
+
// starter" to a model, spend credits and do nothing.
|
|
7133
7305
|
case "buy":
|
|
7134
|
-
|
|
7306
|
+
console.log(c.yellow("Buying credits moved into the REPL."));
|
|
7307
|
+
console.log(c.dim(" Run `clixad` and type /buy \u2014 it shows the packs and asks you to confirm."));
|
|
7308
|
+
return;
|
|
7135
7309
|
case "ask":
|
|
7136
7310
|
return ask(client, config, rest.join(" "));
|
|
7137
7311
|
case "agent":
|
|
@@ -7280,7 +7454,7 @@ async function showWallet(client) {
|
|
|
7280
7454
|
const streak = await client.claimStreak();
|
|
7281
7455
|
const w = await client.wallet();
|
|
7282
7456
|
console.log(`balance: ${c.bold(w.balance.toLocaleString("en-US"))} credits`);
|
|
7283
|
-
console.log(`${earnedToday(w)} \xB7 offers pay ${
|
|
7457
|
+
console.log(`${earnedToday(w)} \xB7 offers pay ${grantLabel(w.grant_per_ad)}`);
|
|
7284
7458
|
const free = w.free_tier;
|
|
7285
7459
|
if (free && free.daily_credits > 0) {
|
|
7286
7460
|
const best = free.models.map((id) => ({ id, turns: free.turns_left[id] ?? 0 })).filter((m) => m.turns > 0).sort((a, b) => b.turns - a.turns)[0];
|
|
@@ -7372,89 +7546,6 @@ async function redeem(client, config, code) {
|
|
|
7372
7546
|
console.log(c.green(`
|
|
7373
7547
|
\u2713 ${text}`) + ` \xB7 balance ${c.bold(credits(result.balance))}`);
|
|
7374
7548
|
}
|
|
7375
|
-
async function buyCmd(client, config, pack) {
|
|
7376
|
-
if (!config.token) return console.log(c.yellow("Not logged in. Run `clixad login` first."));
|
|
7377
|
-
const { enabled, packs } = await client.creditPacks();
|
|
7378
|
-
if (!enabled) return console.log(c.yellow("Buying credits isn't available (Stripe not configured on the gateway)."));
|
|
7379
|
-
if (!pack) {
|
|
7380
|
-
console.log(c.bold("Credit packs:"));
|
|
7381
|
-
for (const p of packs) {
|
|
7382
|
-
console.log(
|
|
7383
|
-
` ${c.cyan(p.id.padEnd(9))} ${p.credits.toLocaleString("en-US").padStart(11)} credits ${c.bold("$" + p.price_usd.toFixed(2))}`
|
|
7384
|
-
);
|
|
7385
|
-
}
|
|
7386
|
-
console.log(c.dim("\n buy one with: clixad buy <id>"));
|
|
7387
|
-
return;
|
|
7388
|
-
}
|
|
7389
|
-
const chosen = packs.find((x) => x.id === pack);
|
|
7390
|
-
if (!chosen) {
|
|
7391
|
-
console.log(c.red(` unknown pack: ${pack}`));
|
|
7392
|
-
return console.log(c.dim(` try one of: ${packs.map((x) => x.id).join(", ")}`));
|
|
7393
|
-
}
|
|
7394
|
-
const consented = await confirmWithdrawalWaiver(chosen);
|
|
7395
|
-
if (!consented) return;
|
|
7396
|
-
let checkout;
|
|
7397
|
-
try {
|
|
7398
|
-
checkout = await client.checkout(pack, {
|
|
7399
|
-
requestImmediatePerformance: true,
|
|
7400
|
-
acknowledgeLossOfWithdrawal: true,
|
|
7401
|
-
at: (/* @__PURE__ */ new Date()).toISOString()
|
|
7402
|
-
});
|
|
7403
|
-
} catch (err) {
|
|
7404
|
-
return console.log(c.red(err.message));
|
|
7405
|
-
}
|
|
7406
|
-
const before = (await safeWallet(client))?.balance ?? 0;
|
|
7407
|
-
console.log(`
|
|
7408
|
-
Stripe Checkout: ${c.bold(checkout.pack)} \u2014 ${checkout.credits.toLocaleString("en-US")} credits for ${c.bold("$" + checkout.price_usd.toFixed(2))}`);
|
|
7409
|
-
console.log(c.dim(` ${checkout.url}`));
|
|
7410
|
-
openBrowser(checkout.url);
|
|
7411
|
-
if (c.enabled) process.stdout.write(c.dim(" waiting for payment to clear"));
|
|
7412
|
-
const deadline = Date.now() + 5 * 60 * 1e3;
|
|
7413
|
-
while (Date.now() < deadline) {
|
|
7414
|
-
await sleep5(3e3);
|
|
7415
|
-
if (c.enabled) process.stdout.write(c.dim("."));
|
|
7416
|
-
const balance = (await safeWallet(client))?.balance ?? before;
|
|
7417
|
-
if (balance > before) {
|
|
7418
|
-
console.log(c.green(`
|
|
7419
|
-
\u2713 +${credits(balance - before)} credits`) + ` \xB7 balance ${c.bold(credits(balance))}`);
|
|
7420
|
-
return;
|
|
7421
|
-
}
|
|
7422
|
-
}
|
|
7423
|
-
console.log(c.yellow("\n No credits yet \u2014 the webhook may be delayed. Check `clixad wallet`."));
|
|
7424
|
-
}
|
|
7425
|
-
async function confirmWithdrawalWaiver(pack) {
|
|
7426
|
-
if (!process.stdin.isTTY) {
|
|
7427
|
-
console.log(c.yellow("\n Buying credits needs an interactive terminal."));
|
|
7428
|
-
console.log(
|
|
7429
|
-
c.dim(" We have to record your confirmation before charging you, and a pipe has nobody to ask.")
|
|
7430
|
-
);
|
|
7431
|
-
return false;
|
|
7432
|
-
}
|
|
7433
|
-
const price = "$" + pack.price_usd.toFixed(2);
|
|
7434
|
-
console.log(`
|
|
7435
|
-
${c.bold(pack.label)} \u2014 ${credits(pack.credits)} credits for ${c.bold(price)}`);
|
|
7436
|
-
console.log(c.dim(` One purchase. No subscription, nothing to cancel. Terms: ${TERMS_URL}`));
|
|
7437
|
-
console.log("\n Before you pay, please confirm:");
|
|
7438
|
-
console.log(" \xB7 You request that we deliver the credits immediately, before the");
|
|
7439
|
-
console.log(" 14-day withdrawal period has expired.");
|
|
7440
|
-
console.log(" \xB7 You acknowledge that your right of withdrawal lapses once they");
|
|
7441
|
-
console.log(" have been delivered.");
|
|
7442
|
-
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
7443
|
-
let answer = "";
|
|
7444
|
-
try {
|
|
7445
|
-
answer = (await rl.question(`
|
|
7446
|
-
Type ${c.bold("yes")} to continue: `)).trim().toLowerCase();
|
|
7447
|
-
} catch {
|
|
7448
|
-
answer = "";
|
|
7449
|
-
} finally {
|
|
7450
|
-
rl.close();
|
|
7451
|
-
}
|
|
7452
|
-
if (answer !== "yes" && answer !== "y") {
|
|
7453
|
-
console.log(c.dim("\n Cancelled. You have not been charged."));
|
|
7454
|
-
return false;
|
|
7455
|
-
}
|
|
7456
|
-
return true;
|
|
7457
|
-
}
|
|
7458
7549
|
async function ask(client, config, prompt) {
|
|
7459
7550
|
if (!prompt) return console.error(c.red('usage: clixad ask "your prompt"'));
|
|
7460
7551
|
const messages = [{ role: "user", content: prompt }];
|
|
@@ -7495,9 +7586,10 @@ ${NO_SEARCH_INSTRUCTION}`),
|
|
|
7495
7586
|
Out of credits (balance ${err.balance}, need ~${err.estimatedCost}).`));
|
|
7496
7587
|
console.log(
|
|
7497
7588
|
c.yellow(
|
|
7498
|
-
`Run \`clixad earn\` to open the offerwall \u2014 offers pay ${
|
|
7589
|
+
`Run \`clixad earn\` to open the offerwall \u2014 offers pay ${grantLabel(err.grantPerAd)}.`
|
|
7499
7590
|
)
|
|
7500
7591
|
);
|
|
7592
|
+
console.log(c.yellow("Credits can also be bought: start `clixad` and type /buy."));
|
|
7501
7593
|
console.log(c.dim(" Offers you are screened out of pay nothing \u2014 that is normal, just start another."));
|
|
7502
7594
|
} else if (err instanceof AuthError) {
|
|
7503
7595
|
console.log(c.red(err.message));
|
|
@@ -7611,8 +7703,9 @@ async function runTurn(client, config, messages) {
|
|
|
7611
7703
|
if (err instanceof PaywallError) {
|
|
7612
7704
|
console.log(c.yellow(`Out of credits (balance ${err.balance}, need ~${err.estimatedCost}).`));
|
|
7613
7705
|
console.log(
|
|
7614
|
-
c.yellow(`Run
|
|
7706
|
+
c.yellow(`Run \`clixad earn\` to open the offerwall \u2014 offers pay ${grantLabel(err.grantPerAd)}.`)
|
|
7615
7707
|
);
|
|
7708
|
+
console.log(c.yellow("Credits can also be bought: start `clixad` and type /buy."));
|
|
7616
7709
|
console.log(c.dim(" Offers you are screened out of pay nothing \u2014 that is normal, just start another."));
|
|
7617
7710
|
} else if (err instanceof AuthError) {
|
|
7618
7711
|
console.log(c.red(err.message));
|
|
@@ -7648,7 +7741,6 @@ function printHelp() {
|
|
|
7648
7741
|
${c.cyan("earn")} open the ad wall to earn credits
|
|
7649
7742
|
${c.cyan("refer")} your invite code, and what it has earned
|
|
7650
7743
|
${c.cyan("redeem")} <code> spend a code you were given
|
|
7651
|
-
${c.cyan("buy")} [pack] list credit packs / buy one via Stripe
|
|
7652
7744
|
${c.cyan("ask")} "<prompt>" one-shot completion (no tools)
|
|
7653
7745
|
${c.cyan("agent")} "<task>" open the REPL with a task already running
|
|
7654
7746
|
${c.cyan("-p")} "<task>" headless agent run (read-only tools)
|