clixad 0.0.1-beta.18 → 0.0.1-beta.19
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +6 -2
- package/dist/clixad.mjs +117 -15
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,8 +1,12 @@
|
|
|
1
1
|
# clixad
|
|
2
2
|
|
|
3
3
|
A terminal coding agent that routes model calls through the Clixad metering gateway. No
|
|
4
|
-
subscription:
|
|
5
|
-
model API calls.
|
|
4
|
+
subscription: the cheapest models are free to use up to a daily allowance, and past that you earn
|
|
5
|
+
credits by completing rewarded offers in your browser and spend them on real model API calls.
|
|
6
|
+
|
|
7
|
+
`/model` marks which models are free and shows roughly how many free turns the allowance still buys
|
|
8
|
+
on each — the same allowance goes a lot further on the cheapest one. The status line counts it down,
|
|
9
|
+
and once it is gone the model carries on against credits.
|
|
6
10
|
|
|
7
11
|
**Early beta.** Commands, interfaces and credit mechanics may change between beta versions. Run the
|
|
8
12
|
most recent published build with:
|
package/dist/clixad.mjs
CHANGED
|
@@ -207,6 +207,14 @@ async function httpFailure(res, what) {
|
|
|
207
207
|
const detail = safeDetail(await res.text().catch(() => ""));
|
|
208
208
|
return detail ? `${what} failed: ${res.status} \u2014 ${detail}` : `${what} failed: ${res.status}`;
|
|
209
209
|
}
|
|
210
|
+
function freeMeta(meta) {
|
|
211
|
+
if (meta.free !== true) return {};
|
|
212
|
+
return {
|
|
213
|
+
free: true,
|
|
214
|
+
creditsWaived: meta.credits_waived ?? 0,
|
|
215
|
+
freeCreditsLeft: meta.free_credits_left ?? 0
|
|
216
|
+
};
|
|
217
|
+
}
|
|
210
218
|
function accumulateToolCall(calls, part, fallbackIndex) {
|
|
211
219
|
const index = part.index ?? fallbackIndex;
|
|
212
220
|
const current = calls.get(index) ?? { id: "", type: "function", function: { name: "", arguments: "" } };
|
|
@@ -441,7 +449,8 @@ var init_client = __esm({
|
|
|
441
449
|
model: report.model,
|
|
442
450
|
prompt: report.prompt,
|
|
443
451
|
response: report.response,
|
|
444
|
-
...report.steps === void 0 ? {} : { steps: report.steps }
|
|
452
|
+
...report.steps === void 0 ? {} : { steps: report.steps },
|
|
453
|
+
...report.free ? { free: true } : {}
|
|
445
454
|
})
|
|
446
455
|
});
|
|
447
456
|
if (!res.ok) return null;
|
|
@@ -770,7 +779,8 @@ var init_client = __esm({
|
|
|
770
779
|
message: choice.message,
|
|
771
780
|
finishReason: choice.finish_reason,
|
|
772
781
|
creditsCharged: data.clixad.credits_charged,
|
|
773
|
-
balance: data.clixad.balance
|
|
782
|
+
balance: data.clixad.balance,
|
|
783
|
+
...freeMeta(data.clixad)
|
|
774
784
|
};
|
|
775
785
|
}
|
|
776
786
|
/**
|
|
@@ -792,6 +802,7 @@ var init_client = __esm({
|
|
|
792
802
|
let content = "";
|
|
793
803
|
let finishReason = "stop";
|
|
794
804
|
let creditsCharged = 0;
|
|
805
|
+
let free = {};
|
|
795
806
|
let balance = 0;
|
|
796
807
|
let streamError;
|
|
797
808
|
const calls = /* @__PURE__ */ new Map();
|
|
@@ -817,6 +828,7 @@ var init_client = __esm({
|
|
|
817
828
|
if (json.clixad) {
|
|
818
829
|
creditsCharged = json.clixad.credits_charged;
|
|
819
830
|
balance = json.clixad.balance;
|
|
831
|
+
free = freeMeta(json.clixad);
|
|
820
832
|
}
|
|
821
833
|
}
|
|
822
834
|
if (streamError) throw new Error(streamError);
|
|
@@ -824,7 +836,7 @@ var init_client = __esm({
|
|
|
824
836
|
if (calls.size > 0) {
|
|
825
837
|
message.tool_calls = [...calls.entries()].sort(([a], [b]) => a - b).map(([, c2]) => c2);
|
|
826
838
|
}
|
|
827
|
-
return { message, finishReason, creditsCharged, balance };
|
|
839
|
+
return { message, finishReason, creditsCharged, balance, ...free };
|
|
828
840
|
}
|
|
829
841
|
};
|
|
830
842
|
}
|
|
@@ -3581,6 +3593,8 @@ async function runAgent(client, task, opts) {
|
|
|
3581
3593
|
const depth = opts.depth ?? 0;
|
|
3582
3594
|
const emit = (e) => opts.onEvent?.(e);
|
|
3583
3595
|
let creditsCharged = 0;
|
|
3596
|
+
let creditsWaived = 0;
|
|
3597
|
+
let freeCreditsLeft;
|
|
3584
3598
|
let balance = 0;
|
|
3585
3599
|
let lastText = "";
|
|
3586
3600
|
let pendingImages = [];
|
|
@@ -3637,6 +3651,8 @@ ${NO_SEARCH_INSTRUCTION}`,
|
|
|
3637
3651
|
}
|
|
3638
3652
|
});
|
|
3639
3653
|
creditsCharged += sub.creditsCharged;
|
|
3654
|
+
creditsWaived += sub.creditsWaived;
|
|
3655
|
+
if (sub.freeCreditsLeft !== void 0) freeCreditsLeft = sub.freeCreditsLeft;
|
|
3640
3656
|
if (sub.stopped === "aborted") return "the sub-agent was stopped before it finished";
|
|
3641
3657
|
const answer = sub.content.trim();
|
|
3642
3658
|
if (!answer) return "the sub-agent finished without reporting anything";
|
|
@@ -3658,15 +3674,26 @@ ${NO_SEARCH_INSTRUCTION}`,
|
|
|
3658
3674
|
throw err;
|
|
3659
3675
|
}
|
|
3660
3676
|
creditsCharged += turn.creditsCharged;
|
|
3677
|
+
creditsWaived += turn.creditsWaived ?? 0;
|
|
3678
|
+
if (turn.freeCreditsLeft !== void 0) freeCreditsLeft = turn.freeCreditsLeft;
|
|
3661
3679
|
balance = turn.balance;
|
|
3662
|
-
emit({
|
|
3680
|
+
emit({
|
|
3681
|
+
type: "usage",
|
|
3682
|
+
creditsCharged: turn.creditsCharged,
|
|
3683
|
+
balance: turn.balance,
|
|
3684
|
+
...turn.free ? {
|
|
3685
|
+
free: true,
|
|
3686
|
+
creditsWaived: turn.creditsWaived ?? 0,
|
|
3687
|
+
freeCreditsLeft: turn.freeCreditsLeft ?? 0
|
|
3688
|
+
} : {}
|
|
3689
|
+
});
|
|
3663
3690
|
messages.push(turn.message);
|
|
3664
3691
|
const content = messageText(turn.message);
|
|
3665
3692
|
if (content) lastText = content;
|
|
3666
3693
|
const calls = turn.message.tool_calls ?? [];
|
|
3667
3694
|
if (calls.length === 0) {
|
|
3668
3695
|
emit({ type: "final", content, creditsCharged, balance });
|
|
3669
|
-
return { content, messages, creditsCharged, balance, steps: step + 1 };
|
|
3696
|
+
return { content, messages, creditsCharged, balance, creditsWaived, freeCreditsLeft, steps: step + 1 };
|
|
3670
3697
|
}
|
|
3671
3698
|
if (content) emit({ type: "message", content });
|
|
3672
3699
|
pendingImages = [];
|
|
@@ -3693,12 +3720,14 @@ ${NO_SEARCH_INSTRUCTION}`,
|
|
|
3693
3720
|
messages,
|
|
3694
3721
|
creditsCharged,
|
|
3695
3722
|
balance,
|
|
3723
|
+
creditsWaived,
|
|
3724
|
+
freeCreditsLeft,
|
|
3696
3725
|
steps: maxSteps,
|
|
3697
3726
|
stopped: "max_steps"
|
|
3698
3727
|
};
|
|
3699
3728
|
function stop(reason) {
|
|
3700
3729
|
emit({ type: reason });
|
|
3701
|
-
return { content: lastText, messages, creditsCharged, balance, steps: 0, stopped: reason };
|
|
3730
|
+
return { content: lastText, messages, creditsCharged, balance, creditsWaived, freeCreditsLeft, steps: 0, stopped: reason };
|
|
3702
3731
|
}
|
|
3703
3732
|
}
|
|
3704
3733
|
function toolMessage(call, content) {
|
|
@@ -5274,6 +5303,7 @@ function App({ client, config, wallet, streak, session, initialTask, notices: no
|
|
|
5274
5303
|
const [live, setLive] = useState(null);
|
|
5275
5304
|
const [balance, setBalance] = useState(wallet?.balance ?? 0);
|
|
5276
5305
|
const [spent, setSpent] = useState(0);
|
|
5306
|
+
const [freeTier, setFreeTier] = useState(wallet?.free_tier);
|
|
5277
5307
|
const [model, setModel2] = useState(config.model);
|
|
5278
5308
|
const [mode, setMode] = useState("normal");
|
|
5279
5309
|
const [hist, setHist] = useState(() => loadHistory());
|
|
@@ -5316,6 +5346,7 @@ function App({ client, config, wallet, streak, session, initialTask, notices: no
|
|
|
5316
5346
|
const mcpRef = useRef(McpHub.empty());
|
|
5317
5347
|
const todosRef = useRef(new TodoList());
|
|
5318
5348
|
const lastTurnRef = useRef(null);
|
|
5349
|
+
const freeSpentNoticeRef = useRef(false);
|
|
5319
5350
|
const turnCountRef = useRef(config.turns ?? 0);
|
|
5320
5351
|
const deltaBufRef = useRef("");
|
|
5321
5352
|
const deltaTimerRef = useRef(null);
|
|
@@ -5590,6 +5621,11 @@ function App({ client, config, wallet, streak, session, initialTask, notices: no
|
|
|
5590
5621
|
case "usage":
|
|
5591
5622
|
setBalance(event.balance);
|
|
5592
5623
|
setSpent((s) => s + event.creditsCharged);
|
|
5624
|
+
if (event.free) {
|
|
5625
|
+
setFreeTier(
|
|
5626
|
+
(f) => f ? { ...f, credits_left: event.freeCreditsLeft ?? f.credits_left } : f
|
|
5627
|
+
);
|
|
5628
|
+
}
|
|
5593
5629
|
return;
|
|
5594
5630
|
default:
|
|
5595
5631
|
return;
|
|
@@ -5681,14 +5717,28 @@ ${NO_SEARCH_INSTRUCTION}`),
|
|
|
5681
5717
|
push({
|
|
5682
5718
|
kind: "assistant",
|
|
5683
5719
|
text: result.content,
|
|
5684
|
-
meta:
|
|
5720
|
+
meta: turnMeta(result)
|
|
5685
5721
|
});
|
|
5722
|
+
if (freeTier?.models.includes(model) && result.creditsWaived === 0 && result.creditsCharged > 0 && (result.freeCreditsLeft ?? freeTier.credits_left) === 0 && !freeSpentNoticeRef.current) {
|
|
5723
|
+
freeSpentNoticeRef.current = true;
|
|
5724
|
+
push({
|
|
5725
|
+
kind: "notice",
|
|
5726
|
+
tone: "warn",
|
|
5727
|
+
text: ` Today's free turns are used up. This one cost ${result.creditsCharged.toLocaleString("en-US")} credits.
|
|
5728
|
+
The free allowance resets at midnight UTC. /earn tops up before then.`
|
|
5729
|
+
});
|
|
5730
|
+
}
|
|
5686
5731
|
lastTurnRef.current = {
|
|
5687
5732
|
turn: turnCountRef.current,
|
|
5688
5733
|
model,
|
|
5689
5734
|
prompt: task,
|
|
5690
5735
|
response: result.content,
|
|
5691
|
-
steps: result.steps
|
|
5736
|
+
steps: result.steps,
|
|
5737
|
+
// Whether the free tier paid for it. Sent with the rating, where it
|
|
5738
|
+
// can only ever lower the grant — see `FeedbackReport.free`. A turn
|
|
5739
|
+
// that was partly free counts as free: we funded some of it, and of
|
|
5740
|
+
// the two roundings this is the one that is ours to absorb.
|
|
5741
|
+
free: result.creditsWaived > 0
|
|
5692
5742
|
};
|
|
5693
5743
|
turnCountRef.current += 1;
|
|
5694
5744
|
config.turns = turnCountRef.current;
|
|
@@ -5937,10 +5987,15 @@ ${NO_SEARCH_INSTRUCTION}`),
|
|
|
5937
5987
|
if (arg && !models.some((m) => m.id === arg)) {
|
|
5938
5988
|
push({ kind: "notice", tone: "warn", text: ` unknown model: ${arg}` });
|
|
5939
5989
|
}
|
|
5990
|
+
const free = await client.wallet(signal).then((w) => {
|
|
5991
|
+
setBalance(w.balance);
|
|
5992
|
+
setFreeTier(w.free_tier);
|
|
5993
|
+
return w.free_tier;
|
|
5994
|
+
}).catch(() => freeTier);
|
|
5940
5995
|
const items = models.map((m) => ({
|
|
5941
5996
|
value: m.id,
|
|
5942
5997
|
label: m.id,
|
|
5943
|
-
hint: `${m.tier.padEnd(8)}${
|
|
5998
|
+
hint: `${m.tier.padEnd(8)}${modelHint(m, free)}`,
|
|
5944
5999
|
current: m.id === model
|
|
5945
6000
|
}));
|
|
5946
6001
|
const preselect = items.findIndex((i) => i.value === arg);
|
|
@@ -5964,7 +6019,7 @@ ${NO_SEARCH_INSTRUCTION}`),
|
|
|
5964
6019
|
catalogRef.current = ms;
|
|
5965
6020
|
push({
|
|
5966
6021
|
kind: "notice",
|
|
5967
|
-
text: ms.map((m) => ` ${m.id.padEnd(24)} ${
|
|
6022
|
+
text: ms.map((m) => ` ${m.id.padEnd(24)} ${modelHint(m, freeTier)}`).join("\n")
|
|
5968
6023
|
});
|
|
5969
6024
|
});
|
|
5970
6025
|
return;
|
|
@@ -6187,7 +6242,7 @@ ${output}` }
|
|
|
6187
6242
|
const turn = lastTurnRef.current;
|
|
6188
6243
|
if (!turn) return;
|
|
6189
6244
|
lastTurnRef.current = null;
|
|
6190
|
-
const amount = wallet?.rewards?.feedback_credits;
|
|
6245
|
+
const amount = turn.free ? wallet?.rewards?.feedback_credits_free ?? wallet?.rewards?.feedback_credits : wallet?.rewards?.feedback_credits;
|
|
6191
6246
|
const pay = amount ? `Both answers pay ${amount.toLocaleString("en-US")} credits` : "Both answers pay the same";
|
|
6192
6247
|
const choice = await new Promise((resolve2) => {
|
|
6193
6248
|
setPickerSel(0);
|
|
@@ -6559,7 +6614,17 @@ ${dropped.map((l) => ` \xB7 ${l}`).join("\n")}`
|
|
|
6559
6614
|
const modeText = ` ${MODE_STYLE[mode].glyph} ${MODE_LABEL[mode]}`;
|
|
6560
6615
|
const statusText = fitRow(
|
|
6561
6616
|
` ${statusLine(
|
|
6562
|
-
{
|
|
6617
|
+
{
|
|
6618
|
+
model,
|
|
6619
|
+
balance,
|
|
6620
|
+
spent,
|
|
6621
|
+
contextPct,
|
|
6622
|
+
minutes: (Date.now() - runStartedAtRef.current) / 6e4,
|
|
6623
|
+
// Only while a free model is selected: on a premium one the allowance
|
|
6624
|
+
// is real but irrelevant, and a countdown that has nothing to do with
|
|
6625
|
+
// the next turn is a number that teaches people to stop reading the row.
|
|
6626
|
+
...freeTier?.models.includes(model) ? { freeLeft: freeTier.credits_left } : {}
|
|
6627
|
+
},
|
|
6563
6628
|
Math.max(1, cols - visibleLength(modeText) - 2)
|
|
6564
6629
|
)}`,
|
|
6565
6630
|
Math.max(0, cols - visibleLength(modeText))
|
|
@@ -6667,11 +6732,35 @@ function renderInput(state, from, rows) {
|
|
|
6667
6732
|
] }, row);
|
|
6668
6733
|
});
|
|
6669
6734
|
}
|
|
6735
|
+
function modelHint(m, free) {
|
|
6736
|
+
const effort = adsPerTaskLabel(m.est_ads_per_task);
|
|
6737
|
+
if (!m.free || !free?.models.includes(m.id)) return effort;
|
|
6738
|
+
const turns = free.turns_left[m.id] ?? 0;
|
|
6739
|
+
if (turns <= 0) return `free: none left today \xB7 ${effort}`;
|
|
6740
|
+
return `free: ~${turns} turn${turns === 1 ? "" : "s"} left \xB7 ${effort}`;
|
|
6741
|
+
}
|
|
6742
|
+
function turnMeta(result) {
|
|
6743
|
+
const n = (v) => v.toLocaleString("en-US");
|
|
6744
|
+
const balance = `balance ${n(result.balance)}`;
|
|
6745
|
+
if (result.creditsWaived <= 0) return `[${result.creditsCharged} credits \xB7 ${balance}]`;
|
|
6746
|
+
const left = result.freeCreditsLeft === void 0 ? "" : ` \xB7 ${n(result.freeCreditsLeft)} free credits left`;
|
|
6747
|
+
if (result.creditsCharged > 0) {
|
|
6748
|
+
return `[free: ${n(result.creditsWaived)} credits \xB7 then ${n(result.creditsCharged)} charged \xB7 ${balance}]`;
|
|
6749
|
+
}
|
|
6750
|
+
return `[free \xB7 ${n(result.creditsWaived)} credits waived${left}]`;
|
|
6751
|
+
}
|
|
6670
6752
|
function statusLine(o, cols) {
|
|
6671
6753
|
const burn = o.spent > 0 && o.minutes >= 1 ? `${Math.round(o.spent / o.minutes).toLocaleString("en-US")} cr/min` : void 0;
|
|
6672
6754
|
const clauses = [
|
|
6673
6755
|
{ text: o.model, rank: KEEP_ALWAYS },
|
|
6674
6756
|
{ text: `${o.balance.toLocaleString("en-US")} cr`, rank: KEEP_ALWAYS },
|
|
6757
|
+
// Above the context warning, below the balance. It is the second number
|
|
6758
|
+
// deciding whether the next turn costs anything, and unlike the spend it is
|
|
6759
|
+
// *news* — it only exists while a free model is selected and it counts down.
|
|
6760
|
+
{
|
|
6761
|
+
text: o.freeLeft === void 0 ? void 0 : `free ${o.freeLeft.toLocaleString("en-US")}`,
|
|
6762
|
+
rank: 4
|
|
6763
|
+
},
|
|
6675
6764
|
{ text: o.spent > 0 ? `\u2212${o.spent.toLocaleString("en-US")}` : void 0, rank: 2 },
|
|
6676
6765
|
{ text: burn, rank: 1 },
|
|
6677
6766
|
{
|
|
@@ -6682,7 +6771,7 @@ function statusLine(o, cols) {
|
|
|
6682
6771
|
].filter((c2) => Boolean(c2.text));
|
|
6683
6772
|
const join7 = (minRank) => clauses.filter((c2) => c2.rank >= minRank).map((c2) => c2.text).join(" \xB7 ");
|
|
6684
6773
|
let line2 = join7(0);
|
|
6685
|
-
for (let minRank = 1; minRank <=
|
|
6774
|
+
for (let minRank = 1; minRank <= 5 && visibleLength(line2) > cols; minRank++) line2 = join7(minRank);
|
|
6686
6775
|
return fitRow(line2, cols);
|
|
6687
6776
|
}
|
|
6688
6777
|
function windowStart(sel, total, size) {
|
|
@@ -7023,11 +7112,14 @@ function earnedToday(w) {
|
|
|
7023
7112
|
}
|
|
7024
7113
|
async function listModels(client) {
|
|
7025
7114
|
const models = await client.models();
|
|
7115
|
+
const free = await client.wallet().then((w) => w.free_tier).catch(() => void 0);
|
|
7026
7116
|
console.log(c.bold("Models (credit prices per 1M tokens)"));
|
|
7027
7117
|
for (const m of models) {
|
|
7028
7118
|
const tag = m.ad_fundable ? "" : c.dim(" [paid/BYOK]");
|
|
7119
|
+
const turns = free?.models.includes(m.id) ? free.turns_left[m.id] ?? 0 : void 0;
|
|
7120
|
+
const freeTag = !m.free ? "" : turns === void 0 ? c.green(" [free]") : turns > 0 ? c.green(` [free: ~${turns} turn${turns === 1 ? "" : "s"} left today]`) : c.dim(" [free: none left today]");
|
|
7029
7121
|
console.log(
|
|
7030
|
-
` ${c.cyan(m.id.padEnd(22))} ${m.tier.padEnd(8)} 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}`
|
|
7122
|
+
` ${c.cyan(m.id.padEnd(22))} ${m.tier.padEnd(8)} 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}`
|
|
7031
7123
|
);
|
|
7032
7124
|
}
|
|
7033
7125
|
}
|
|
@@ -7050,6 +7142,15 @@ async function showWallet(client) {
|
|
|
7050
7142
|
const w = await client.wallet();
|
|
7051
7143
|
console.log(`balance: ${c.bold(w.balance.toLocaleString("en-US"))} credits`);
|
|
7052
7144
|
console.log(`${earnedToday(w)} \xB7 offers pay ${grantRangeLabel(w.grant_per_ad_range)}`);
|
|
7145
|
+
const free = w.free_tier;
|
|
7146
|
+
if (free && free.daily_credits > 0) {
|
|
7147
|
+
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];
|
|
7148
|
+
console.log(
|
|
7149
|
+
free.credits_left > 0 ? c.green(
|
|
7150
|
+
`free today: ${credits(free.credits_left)} of ${credits(free.daily_credits)} credits left` + (best ? ` \xB7 ~${best.turns} turn${best.turns === 1 ? "" : "s"} on ${best.id}` : "")
|
|
7151
|
+
) : c.dim(`free today: none left of ${credits(free.daily_credits)} credits \u2014 resets at midnight UTC`)
|
|
7152
|
+
);
|
|
7153
|
+
}
|
|
7053
7154
|
const line2 = streak && streakLabel(streak);
|
|
7054
7155
|
if (line2) console.log(streak.claimed ? c.green(line2) : c.dim(line2));
|
|
7055
7156
|
if (w.ledger.length) {
|
|
@@ -7244,9 +7345,10 @@ ${NO_SEARCH_INSTRUCTION}`),
|
|
|
7244
7345
|
todos: new TodoList(),
|
|
7245
7346
|
onEvent: printAgentEvent
|
|
7246
7347
|
});
|
|
7348
|
+
const cost = res.creditsWaived > 0 ? res.creditsCharged > 0 ? `free: ${res.creditsWaived} credits \xB7 then ${res.creditsCharged} charged` : `free \xB7 ${res.creditsWaived} credits waived` : `${res.creditsCharged} credits`;
|
|
7247
7349
|
console.log(
|
|
7248
7350
|
c.dim(`
|
|
7249
|
-
[${
|
|
7351
|
+
[${cost} \xB7 balance ${res.balance} \xB7 ${res.steps} step(s)]`)
|
|
7250
7352
|
);
|
|
7251
7353
|
} catch (err) {
|
|
7252
7354
|
if (err instanceof PaywallError) {
|