clixad 0.0.1-beta.22 → 0.0.1-beta.23
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 +328 -78
- package/package.json +1 -1
package/dist/clixad.mjs
CHANGED
|
@@ -350,12 +350,48 @@ function asSignupClosed(status, data) {
|
|
|
350
350
|
const d = data;
|
|
351
351
|
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;
|
|
352
352
|
}
|
|
353
|
+
function shortfallHeadline(err, model) {
|
|
354
|
+
const n = (v) => Math.max(0, Math.round(v)).toLocaleString("en-US");
|
|
355
|
+
if (err.balance <= 0) return `Out of credits (this needs about ${n(err.estimatedCost)}).`;
|
|
356
|
+
return `Not enough credits for this turn${model ? ` on ${model}` : ""}: it needs about ${n(err.estimatedCost)}, and the balance is ${n(err.balance)}.`;
|
|
357
|
+
}
|
|
353
358
|
function paywallError(body) {
|
|
354
359
|
const free = Array.isArray(body.free_models) ? body.free_models.filter(
|
|
355
360
|
(m) => !!m && typeof m.id === "string" && m.id.length > 0 && Number.isFinite(m.turns_left)
|
|
356
361
|
).map((m) => ({ id: m.id, turnsLeft: Math.max(0, Math.floor(m.turns_left)) })).filter((m) => m.turnsLeft > 0) : [];
|
|
357
362
|
return new PaywallError(body.balance, body.estimated_cost, body.grant_per_ad, free);
|
|
358
363
|
}
|
|
364
|
+
function neverConnected(code) {
|
|
365
|
+
return code !== void 0 && NEVER_CONNECTED.has(code);
|
|
366
|
+
}
|
|
367
|
+
function networkCode(err) {
|
|
368
|
+
let cause = err?.cause;
|
|
369
|
+
for (let depth = 0; depth < 3 && cause; depth++) {
|
|
370
|
+
const code = cause.code;
|
|
371
|
+
if (typeof code === "string" && code) return code;
|
|
372
|
+
const inner = cause.errors;
|
|
373
|
+
if (Array.isArray(inner)) {
|
|
374
|
+
const first = inner.find((e) => typeof e?.code === "string");
|
|
375
|
+
if (first) return first.code;
|
|
376
|
+
}
|
|
377
|
+
cause = cause.cause;
|
|
378
|
+
}
|
|
379
|
+
return void 0;
|
|
380
|
+
}
|
|
381
|
+
async function gatewayFetch(url, init) {
|
|
382
|
+
try {
|
|
383
|
+
return await fetch(url, init);
|
|
384
|
+
} catch (err) {
|
|
385
|
+
const aborted = init?.signal?.aborted || err?.name === "AbortError";
|
|
386
|
+
if (aborted || !(err instanceof TypeError)) throw err;
|
|
387
|
+
let host = String(url);
|
|
388
|
+
try {
|
|
389
|
+
host = new URL(url).host;
|
|
390
|
+
} catch {
|
|
391
|
+
}
|
|
392
|
+
throw new GatewayUnreachableError(host, networkCode(err));
|
|
393
|
+
}
|
|
394
|
+
}
|
|
359
395
|
async function authFailure(res, what) {
|
|
360
396
|
const body = await res.text().catch(() => "");
|
|
361
397
|
const isJson = (res.headers.get("content-type") ?? "").includes("json");
|
|
@@ -429,7 +465,7 @@ async function* parseSSE(body) {
|
|
|
429
465
|
}
|
|
430
466
|
}
|
|
431
467
|
}
|
|
432
|
-
var CHAT_ENCODED_CONTENT_TYPE, LEDGER_REASONS, SignupClosedError, PaywallError, AuthError, GatewayClient;
|
|
468
|
+
var CHAT_ENCODED_CONTENT_TYPE, LEDGER_REASONS, SignupClosedError, PaywallError, GatewayUnreachableError, NEVER_CONNECTED, AuthError, GatewayClient;
|
|
433
469
|
var init_client = __esm({
|
|
434
470
|
"src/client.ts"() {
|
|
435
471
|
"use strict";
|
|
@@ -478,6 +514,33 @@ var init_client = __esm({
|
|
|
478
514
|
grantPerAd;
|
|
479
515
|
freeModels;
|
|
480
516
|
};
|
|
517
|
+
GatewayUnreachableError = class extends Error {
|
|
518
|
+
constructor(host, code) {
|
|
519
|
+
const where = `${host}${code ? ` (${code})` : ""}`;
|
|
520
|
+
super(
|
|
521
|
+
neverConnected(code) ? `Can't reach the Clixad gateway at ${where}. Nothing was sent, so nothing was charged. Check your connection and try again.` : `The connection to the Clixad gateway at ${where} broke before an answer arrived. If that was a model call it may still have been charged, so check your balance before sending it again.`
|
|
522
|
+
);
|
|
523
|
+
this.host = host;
|
|
524
|
+
this.code = code;
|
|
525
|
+
this.name = "GatewayUnreachableError";
|
|
526
|
+
}
|
|
527
|
+
host;
|
|
528
|
+
code;
|
|
529
|
+
};
|
|
530
|
+
NEVER_CONNECTED = /* @__PURE__ */ new Set([
|
|
531
|
+
"ENOTFOUND",
|
|
532
|
+
"EAI_AGAIN",
|
|
533
|
+
"ECONNREFUSED",
|
|
534
|
+
"ENETUNREACH",
|
|
535
|
+
"EHOSTUNREACH",
|
|
536
|
+
"EADDRNOTAVAIL",
|
|
537
|
+
"UND_ERR_CONNECT_TIMEOUT",
|
|
538
|
+
"CERT_HAS_EXPIRED",
|
|
539
|
+
"DEPTH_ZERO_SELF_SIGNED_CERT",
|
|
540
|
+
"SELF_SIGNED_CERT_IN_CHAIN",
|
|
541
|
+
"UNABLE_TO_VERIFY_LEAF_SIGNATURE",
|
|
542
|
+
"ERR_TLS_CERT_ALTNAME_INVALID"
|
|
543
|
+
]);
|
|
481
544
|
AuthError = class extends Error {
|
|
482
545
|
constructor() {
|
|
483
546
|
super("Not logged in. Run `clixad login` first.");
|
|
@@ -518,7 +581,7 @@ var init_client = __esm({
|
|
|
518
581
|
}
|
|
519
582
|
/** Dev-only signup shortcut (production replaces this with GitHub OAuth). */
|
|
520
583
|
async signupDev(email, invite) {
|
|
521
|
-
const res = await
|
|
584
|
+
const res = await gatewayFetch(`${this.config.gatewayUrl}/dev/users`, {
|
|
522
585
|
method: "POST",
|
|
523
586
|
headers: this.headers(false),
|
|
524
587
|
body: JSON.stringify({ ...email ? { email } : {}, ...invite ? { invite } : {} })
|
|
@@ -534,7 +597,7 @@ var init_client = __esm({
|
|
|
534
597
|
/** Begin GitHub device-flow login. Returns null if the gateway has no GitHub
|
|
535
598
|
* configured (501), so the caller can fall back to the dev shortcut. */
|
|
536
599
|
async deviceStart() {
|
|
537
|
-
const res = await
|
|
600
|
+
const res = await gatewayFetch(`${this.config.gatewayUrl}/v1/auth/device/start`, {
|
|
538
601
|
method: "POST",
|
|
539
602
|
headers: this.headers(false)
|
|
540
603
|
});
|
|
@@ -544,7 +607,7 @@ var init_client = __esm({
|
|
|
544
607
|
}
|
|
545
608
|
/** Poll once for device-flow completion. `invite` unlocks a gated signup. */
|
|
546
609
|
async devicePoll(session, invite) {
|
|
547
|
-
const res = await
|
|
610
|
+
const res = await gatewayFetch(`${this.config.gatewayUrl}/v1/auth/device/poll`, {
|
|
548
611
|
method: "POST",
|
|
549
612
|
headers: this.headers(false),
|
|
550
613
|
body: JSON.stringify(invite ? { session, invite } : { session })
|
|
@@ -566,7 +629,7 @@ var init_client = __esm({
|
|
|
566
629
|
*/
|
|
567
630
|
async handoffCode() {
|
|
568
631
|
try {
|
|
569
|
-
const res = await
|
|
632
|
+
const res = await gatewayFetch(`${this.config.gatewayUrl}/v1/auth/handoff`, {
|
|
570
633
|
method: "POST",
|
|
571
634
|
headers: this.headers()
|
|
572
635
|
});
|
|
@@ -593,7 +656,7 @@ var init_client = __esm({
|
|
|
593
656
|
*/
|
|
594
657
|
async claimStreak(signal) {
|
|
595
658
|
try {
|
|
596
|
-
const res = await
|
|
659
|
+
const res = await gatewayFetch(`${this.config.gatewayUrl}/v1/streak/claim`, {
|
|
597
660
|
method: "POST",
|
|
598
661
|
headers: this.headers(),
|
|
599
662
|
...signal ? { signal } : {}
|
|
@@ -628,7 +691,7 @@ var init_client = __esm({
|
|
|
628
691
|
*/
|
|
629
692
|
async sendFeedback(report, signal) {
|
|
630
693
|
try {
|
|
631
|
-
const res = await
|
|
694
|
+
const res = await gatewayFetch(`${this.config.gatewayUrl}/v1/feedback`, {
|
|
632
695
|
method: "POST",
|
|
633
696
|
headers: this.headers(),
|
|
634
697
|
...signal ? { signal } : {},
|
|
@@ -663,7 +726,7 @@ var init_client = __esm({
|
|
|
663
726
|
* that did not run, and saying so beats printing an empty summary.
|
|
664
727
|
*/
|
|
665
728
|
async referral(signal) {
|
|
666
|
-
const res = await
|
|
729
|
+
const res = await gatewayFetch(`${this.config.gatewayUrl}/v1/referrals`, {
|
|
667
730
|
headers: this.headers(),
|
|
668
731
|
...signal ? { signal } : {}
|
|
669
732
|
});
|
|
@@ -694,7 +757,7 @@ var init_client = __esm({
|
|
|
694
757
|
* 200 with a status, because that is an answer rather than a failure.
|
|
695
758
|
*/
|
|
696
759
|
async redeem(code, signal) {
|
|
697
|
-
const res = await
|
|
760
|
+
const res = await gatewayFetch(`${this.config.gatewayUrl}/v1/redeem`, {
|
|
698
761
|
method: "POST",
|
|
699
762
|
headers: this.headers(),
|
|
700
763
|
...signal ? { signal } : {},
|
|
@@ -715,7 +778,7 @@ var init_client = __esm({
|
|
|
715
778
|
* them with the input box locked and "esc to stop" on screen.
|
|
716
779
|
*/
|
|
717
780
|
async creditPacks(signal) {
|
|
718
|
-
const res = await
|
|
781
|
+
const res = await gatewayFetch(`${this.config.gatewayUrl}/v1/billing/packs`, { signal });
|
|
719
782
|
if (!res.ok) throw new Error(`packs failed: ${res.status}`);
|
|
720
783
|
return res.json();
|
|
721
784
|
}
|
|
@@ -726,7 +789,7 @@ var init_client = __esm({
|
|
|
726
789
|
* could not deliver instantly is worse than not starting one.
|
|
727
790
|
*/
|
|
728
791
|
async checkout(pack, consent, signal) {
|
|
729
|
-
const res = await
|
|
792
|
+
const res = await gatewayFetch(`${this.config.gatewayUrl}/v1/billing/checkout`, {
|
|
730
793
|
method: "POST",
|
|
731
794
|
headers: this.headers(),
|
|
732
795
|
...signal ? { signal } : {},
|
|
@@ -742,13 +805,37 @@ var init_client = __esm({
|
|
|
742
805
|
// the better part of a minute) would otherwise be indistinguishable from a
|
|
743
806
|
// hung terminal. Nothing else passes one — a one-shot CLI command has Ctrl+C.
|
|
744
807
|
async models(signal) {
|
|
745
|
-
const res = await
|
|
808
|
+
const res = await gatewayFetch(`${this.config.gatewayUrl}/v1/models`, { signal });
|
|
746
809
|
if (!res.ok) throw new Error(`models failed: ${res.status}`);
|
|
747
810
|
const data = await res.json();
|
|
748
811
|
this.seen = { web_search: data.capabilities?.web_search === true };
|
|
749
812
|
this.shape = parseTaskShape(data.task_shape);
|
|
813
|
+
this.latest = typeof data.cli_latest === "string" ? data.cli_latest : void 0;
|
|
814
|
+
const live = new Set(data.data.map((m) => m.id));
|
|
815
|
+
this.retired = {};
|
|
816
|
+
if (data.retired_models && typeof data.retired_models === "object") {
|
|
817
|
+
for (const [from, to] of Object.entries(data.retired_models)) {
|
|
818
|
+
if (typeof to === "string" && live.has(to) && !live.has(from)) this.retired[from] = to;
|
|
819
|
+
}
|
|
820
|
+
}
|
|
750
821
|
return data.data;
|
|
751
822
|
}
|
|
823
|
+
/**
|
|
824
|
+
* Model ids that were retired, and the live model that serves each one, as of
|
|
825
|
+
* the last catalog fetch. Empty from a gateway that does not send the map.
|
|
826
|
+
*/
|
|
827
|
+
get retiredModels() {
|
|
828
|
+
return this.retired;
|
|
829
|
+
}
|
|
830
|
+
retired = {};
|
|
831
|
+
/**
|
|
832
|
+
* The newest `clixad` published to npm, as the gateway last read it, or
|
|
833
|
+
* undefined when it did not say. Undefined means unknown, never up to date.
|
|
834
|
+
*/
|
|
835
|
+
get latestCli() {
|
|
836
|
+
return this.latest;
|
|
837
|
+
}
|
|
838
|
+
latest;
|
|
752
839
|
/**
|
|
753
840
|
* The task shape the cost preview assumes, as of the last catalog fetch, or
|
|
754
841
|
* undefined when the gateway sent none (or one that did not parse). Undefined
|
|
@@ -773,7 +860,7 @@ var init_client = __esm({
|
|
|
773
860
|
}
|
|
774
861
|
seen = { web_search: false };
|
|
775
862
|
async wallet(signal) {
|
|
776
|
-
const res = await
|
|
863
|
+
const res = await gatewayFetch(`${this.config.gatewayUrl}/v1/wallet`, { headers: this.headers(), signal });
|
|
777
864
|
if (res.status === 401) throw await authFailure(res, "wallet");
|
|
778
865
|
if (!res.ok) throw new Error(`wallet failed: ${res.status}`);
|
|
779
866
|
return res.json();
|
|
@@ -809,7 +896,7 @@ var init_client = __esm({
|
|
|
809
896
|
*/
|
|
810
897
|
async sponsorConfig(signal) {
|
|
811
898
|
try {
|
|
812
|
-
const res = await
|
|
899
|
+
const res = await gatewayFetch(`${this.config.gatewayUrl}/v1/sponsor/config`, {
|
|
813
900
|
headers: this.headers(),
|
|
814
901
|
...signal ? { signal } : {}
|
|
815
902
|
});
|
|
@@ -847,7 +934,7 @@ var init_client = __esm({
|
|
|
847
934
|
*/
|
|
848
935
|
async reportImpression(report, signal) {
|
|
849
936
|
try {
|
|
850
|
-
const res = await
|
|
937
|
+
const res = await gatewayFetch(`${this.config.gatewayUrl}/v1/sponsor/impression`, {
|
|
851
938
|
method: "POST",
|
|
852
939
|
headers: this.headers(),
|
|
853
940
|
...signal ? { signal } : {},
|
|
@@ -880,7 +967,7 @@ var init_client = __esm({
|
|
|
880
967
|
const url = new URL(`${this.config.gatewayUrl}/v1/web/search`);
|
|
881
968
|
url.searchParams.set("q", query);
|
|
882
969
|
if (opts.count) url.searchParams.set("count", String(opts.count));
|
|
883
|
-
const res = await
|
|
970
|
+
const res = await gatewayFetch(url, {
|
|
884
971
|
headers: this.headers(),
|
|
885
972
|
...opts.signal ? { signal: opts.signal } : {}
|
|
886
973
|
});
|
|
@@ -920,7 +1007,7 @@ var init_client = __esm({
|
|
|
920
1007
|
/** One POST to the chat route, in whichever transport is currently in use. */
|
|
921
1008
|
sendChat(payload, encoded, signal) {
|
|
922
1009
|
const json = JSON.stringify(payload);
|
|
923
|
-
return
|
|
1010
|
+
return gatewayFetch(`${this.config.gatewayUrl}/v1/chat/completions`, {
|
|
924
1011
|
method: "POST",
|
|
925
1012
|
headers: encoded ? { ...this.headers(), "Content-Type": CHAT_ENCODED_CONTENT_TYPE } : this.headers(),
|
|
926
1013
|
body: encoded ? encodeChatBody(json) : json,
|
|
@@ -4528,17 +4615,26 @@ var init_kimi = __esm({
|
|
|
4528
4615
|
"gpt-5-nano": 4e5,
|
|
4529
4616
|
"gemini-2.5-flash-lite": 1048576,
|
|
4530
4617
|
"deepseek-v4-flash": 1048576,
|
|
4531
|
-
"gemini-3.
|
|
4532
|
-
"kimi-k2":
|
|
4618
|
+
"gemini-3.5-flash-lite": 1048576,
|
|
4619
|
+
"kimi-k2.5": 262144,
|
|
4620
|
+
"gemini-3.8-flash": 1048576,
|
|
4533
4621
|
"kimi-k2.7-code": 262144,
|
|
4534
4622
|
"claude-haiku-4.5": 2e5,
|
|
4535
|
-
"gemini-3.6-flash": 1048576,
|
|
4536
4623
|
"claude-sonnet-5": 1e6,
|
|
4537
|
-
"gpt-
|
|
4624
|
+
"gpt-6-sol": 105e4,
|
|
4538
4625
|
"kimi-k3": 1048576,
|
|
4626
|
+
"claude-opus-5.5": 1e6,
|
|
4627
|
+
"claude-fable-5.1": 1e6,
|
|
4628
|
+
// Retired on 2026-09-27 and served by their successor (`RETIRED_MODELS` in
|
|
4629
|
+
// packages/pricing), so an install still configured with one compacts
|
|
4630
|
+
// against the window of the model that actually answers.
|
|
4631
|
+
"gemini-3.1-flash-lite": 1048576,
|
|
4632
|
+
"kimi-k2": 262144,
|
|
4633
|
+
"gemini-3.6-flash": 1048576,
|
|
4634
|
+
"gpt-5.2": 105e4,
|
|
4539
4635
|
"claude-opus-5": 1e6,
|
|
4540
4636
|
"gpt-5.5": 105e4,
|
|
4541
|
-
"gpt-5.2-pro":
|
|
4637
|
+
"gpt-5.2-pro": 105e4
|
|
4542
4638
|
};
|
|
4543
4639
|
DEFAULT_CONTEXT_TOKENS = 256e3;
|
|
4544
4640
|
INSTALL_HELP = `Kimi CLI is not installed (or not on PATH).
|
|
@@ -4588,6 +4684,14 @@ function bannerTip(index) {
|
|
|
4588
4684
|
const i = index ?? Math.floor(Math.random() * BANNER_TIPS.length);
|
|
4589
4685
|
return BANNER_TIPS[(i % BANNER_TIPS.length + BANNER_TIPS.length) % BANNER_TIPS.length];
|
|
4590
4686
|
}
|
|
4687
|
+
function greeting(opts, nameWidth) {
|
|
4688
|
+
if (!opts.name) return "Welcome to Clixad!";
|
|
4689
|
+
const name = nameWidth === void 0 ? opts.name : truncate(opts.name, nameWidth);
|
|
4690
|
+
return opts.newAccount ? `Welcome to Clixad, ${name}!` : `Welcome back, ${name}!`;
|
|
4691
|
+
}
|
|
4692
|
+
function balanceLabel(opts) {
|
|
4693
|
+
return opts.balance === void 0 ? "balance unavailable" : `${opts.balance.toLocaleString("en-US")} credits`;
|
|
4694
|
+
}
|
|
4591
4695
|
function earnedLabel(opts) {
|
|
4592
4696
|
return `$${opts.earnedUsdToday.toFixed(2)}/$${opts.maxRewardUsd.toFixed(2)} today`;
|
|
4593
4697
|
}
|
|
@@ -4597,7 +4701,7 @@ function welcomeBanner(opts) {
|
|
|
4597
4701
|
const rightW = Math.min(RIGHT_MAX, Math.max(26, inner - 30));
|
|
4598
4702
|
const leftW = inner - rightW;
|
|
4599
4703
|
const cwd = opts.cwd.replace(homedir6(), "~").replace(/\\/g, "/");
|
|
4600
|
-
const welcome = opts.
|
|
4704
|
+
const welcome = greeting(opts, Math.max(1, Math.min(18, leftW - (opts.newAccount ? 20 : 15))));
|
|
4601
4705
|
const duck = duckLines();
|
|
4602
4706
|
const home = truncate(cwd, leftW);
|
|
4603
4707
|
const hint = bannerTip(opts.tipIndex);
|
|
@@ -4623,8 +4727,10 @@ function welcomeBanner(opts) {
|
|
|
4623
4727
|
tip("/wallet", "balance & ads"),
|
|
4624
4728
|
cell("", 0),
|
|
4625
4729
|
cell(BOLD + MANGO + "Wallet" + R2, 6),
|
|
4626
|
-
cell(TEXT +
|
|
4627
|
-
|
|
4730
|
+
cell(TEXT + balanceLabel(opts) + R2, balanceLabel(opts).length),
|
|
4731
|
+
// Without a wallet there is no earned figure either, and "$0.00/$0.00" is
|
|
4732
|
+
// two invented numbers rather than a statement of what is unknown.
|
|
4733
|
+
opts.balance === void 0 ? cell("", 0) : cell(MUTED + earnedLabel(opts) + R2, earnedLabel(opts).length)
|
|
4628
4734
|
];
|
|
4629
4735
|
const rows = Math.max(left.length, right.length);
|
|
4630
4736
|
const out = [];
|
|
@@ -4642,9 +4748,9 @@ function welcomeBanner(opts) {
|
|
|
4642
4748
|
}
|
|
4643
4749
|
function compactBanner(opts) {
|
|
4644
4750
|
const width = Math.max(1, opts.width ?? MIN_BANNER_WIDTH);
|
|
4645
|
-
const welcome = truncate(opts
|
|
4751
|
+
const welcome = truncate(greeting(opts), Math.max(1, width - 2));
|
|
4646
4752
|
const hint = bannerTip(opts.tipIndex);
|
|
4647
|
-
const balance =
|
|
4753
|
+
const balance = balanceLabel(opts);
|
|
4648
4754
|
const room = width - (4 + stringWidth(hint.key) + 1 + 4 + stringWidth(balance));
|
|
4649
4755
|
const desc = room >= 3 ? truncate(hint.desc, room) : "";
|
|
4650
4756
|
const tipRow = desc ? ` ${MANGO}\u25C6${R2} ${BOLD}${TEXT}${hint.key}${R2} ${FAINT}${desc}${R2} ${FAINT}\xB7${R2} ${balance}` : truncate(` ${MANGO}\u25C6${R2} ${BOLD}${TEXT}${hint.key}${R2} ${FAINT}\xB7${R2} ${balance}`, width);
|
|
@@ -4778,6 +4884,34 @@ function readVersion() {
|
|
|
4778
4884
|
return "unknown";
|
|
4779
4885
|
}
|
|
4780
4886
|
}
|
|
4887
|
+
function isNewerVersion(candidate, current) {
|
|
4888
|
+
const parse2 = (v) => {
|
|
4889
|
+
const m = /^(\d+)\.(\d+)\.(\d+)(?:-([0-9A-Za-z.-]+))?$/.exec(v.trim());
|
|
4890
|
+
if (!m) return void 0;
|
|
4891
|
+
return { core: [Number(m[1]), Number(m[2]), Number(m[3])], pre: m[4] ? m[4].split(".") : [] };
|
|
4892
|
+
};
|
|
4893
|
+
const a = parse2(candidate);
|
|
4894
|
+
const b = parse2(current);
|
|
4895
|
+
if (!a || !b) return false;
|
|
4896
|
+
for (let i = 0; i < 3; i++) {
|
|
4897
|
+
if (a.core[i] !== b.core[i]) return a.core[i] > b.core[i];
|
|
4898
|
+
}
|
|
4899
|
+
if (!a.pre.length || !b.pre.length) return !a.pre.length && b.pre.length > 0;
|
|
4900
|
+
for (let i = 0; i < Math.max(a.pre.length, b.pre.length); i++) {
|
|
4901
|
+
const x = a.pre[i];
|
|
4902
|
+
const y = b.pre[i];
|
|
4903
|
+
if (x === void 0) return false;
|
|
4904
|
+
if (y === void 0) return true;
|
|
4905
|
+
if (x === y) continue;
|
|
4906
|
+
const nx = /^\d+$/.test(x) ? Number(x) : void 0;
|
|
4907
|
+
const ny = /^\d+$/.test(y) ? Number(y) : void 0;
|
|
4908
|
+
if (nx !== void 0 && ny !== void 0) return nx > ny;
|
|
4909
|
+
if (nx !== void 0) return false;
|
|
4910
|
+
if (ny !== void 0) return true;
|
|
4911
|
+
return x > y;
|
|
4912
|
+
}
|
|
4913
|
+
return false;
|
|
4914
|
+
}
|
|
4781
4915
|
var VERSION, VERSION_LABEL;
|
|
4782
4916
|
var init_version = __esm({
|
|
4783
4917
|
"src/version.ts"() {
|
|
@@ -5668,6 +5802,7 @@ function App({
|
|
|
5668
5802
|
wallet,
|
|
5669
5803
|
streak,
|
|
5670
5804
|
session,
|
|
5805
|
+
newAccount,
|
|
5671
5806
|
initialTask,
|
|
5672
5807
|
notices: notices2,
|
|
5673
5808
|
sponsorServe,
|
|
@@ -5690,8 +5825,11 @@ function App({
|
|
|
5690
5825
|
text: (cols >= MIN_BANNER_WIDTH ? welcomeBanner : compactBanner)({
|
|
5691
5826
|
width: cols,
|
|
5692
5827
|
name: config.login ?? config.email?.split("@")[0],
|
|
5828
|
+
...newAccount ? { newAccount: true } : {},
|
|
5693
5829
|
model: config.model,
|
|
5694
|
-
|
|
5830
|
+
// Undefined when the wallet could not be read: the banner says so
|
|
5831
|
+
// rather than drawing "0 credits", which reads as an empty account.
|
|
5832
|
+
balance: wallet?.balance,
|
|
5695
5833
|
adsToday: wallet?.ads_today ?? 0,
|
|
5696
5834
|
earnedUsdToday: wallet?.earned_usd_today ?? 0,
|
|
5697
5835
|
maxRewardUsd: wallet?.max_reward_usd_per_day ?? 0,
|
|
@@ -5705,7 +5843,7 @@ function App({
|
|
|
5705
5843
|
const [editor, setEditor] = useState(EMPTY);
|
|
5706
5844
|
const [busy, setBusy] = useState(false);
|
|
5707
5845
|
const [live, setLive] = useState(null);
|
|
5708
|
-
const [balance, setBalance] = useState(wallet?.balance
|
|
5846
|
+
const [balance, setBalance] = useState(wallet?.balance);
|
|
5709
5847
|
const [spent, setSpent] = useState(0);
|
|
5710
5848
|
const [freeTier, setFreeTier] = useState(wallet?.free_tier);
|
|
5711
5849
|
const [model, setModel2] = useState(config.model);
|
|
@@ -5761,6 +5899,7 @@ function App({
|
|
|
5761
5899
|
const pendingTaskRef = useRef(null);
|
|
5762
5900
|
const pendingFreeRef = useRef([]);
|
|
5763
5901
|
const pendingGrantRef = useRef(0);
|
|
5902
|
+
const pendingBalanceRef = useRef(void 0);
|
|
5764
5903
|
const mcpRef = useRef(McpHub.empty());
|
|
5765
5904
|
const todosRef = useRef(new TodoList());
|
|
5766
5905
|
const lastTurnRef = useRef(null);
|
|
@@ -5862,11 +6001,32 @@ function App({
|
|
|
5862
6001
|
deltaBufRef.current = "";
|
|
5863
6002
|
}, []);
|
|
5864
6003
|
useEffect(() => dropDelta, [dropDelta]);
|
|
6004
|
+
useEffect(() => {
|
|
6005
|
+
if (wallet) return;
|
|
6006
|
+
const ctl = new AbortController();
|
|
6007
|
+
void client.wallet(ctl.signal).then((w) => {
|
|
6008
|
+
setBalance((current) => current ?? w.balance);
|
|
6009
|
+
setFreeTier((current) => current ?? w.free_tier);
|
|
6010
|
+
}).catch(() => void 0);
|
|
6011
|
+
return () => ctl.abort();
|
|
6012
|
+
}, []);
|
|
5865
6013
|
useEffect(() => {
|
|
5866
6014
|
client.onNotice = (text2) => push({ kind: "notice", tone: "warn", text: text2 });
|
|
5867
6015
|
for (const text2 of notices2 ?? []) push({ kind: "notice", tone: "warn", text: ` ${text2}` });
|
|
5868
6016
|
const files = contextRef.current.files;
|
|
5869
6017
|
if (files.length) push({ kind: "notice", text: ` context: ${files.join(", ")}` });
|
|
6018
|
+
if (!wallet) {
|
|
6019
|
+
let host = config.gatewayUrl;
|
|
6020
|
+
try {
|
|
6021
|
+
host = new URL(config.gatewayUrl).host;
|
|
6022
|
+
} catch {
|
|
6023
|
+
}
|
|
6024
|
+
push({
|
|
6025
|
+
kind: "notice",
|
|
6026
|
+
tone: "warn",
|
|
6027
|
+
text: ` ${host} did not answer, so the balance is unknown for now. It fills in once a request gets through.`
|
|
6028
|
+
});
|
|
6029
|
+
}
|
|
5870
6030
|
const streakLine = streak && streak.claimed ? streakLabel(streak) : null;
|
|
5871
6031
|
if (streakLine) push({ kind: "notice", tone: "good", text: ` ${streakLine}` });
|
|
5872
6032
|
const freeLine = freeTierNotice({ model: config.model, balance: wallet?.balance ?? 0, free: wallet?.free_tier });
|
|
@@ -5883,6 +6043,24 @@ function App({
|
|
|
5883
6043
|
client.models().then((m) => {
|
|
5884
6044
|
catalogRef.current = m;
|
|
5885
6045
|
searchRef.current = client.capabilities?.web_search === true;
|
|
6046
|
+
const successor = client.retiredModels[modelRef.current];
|
|
6047
|
+
if (successor) {
|
|
6048
|
+
const from = modelRef.current;
|
|
6049
|
+
config.model = successor;
|
|
6050
|
+
saveConfig(config);
|
|
6051
|
+
switchModel(successor);
|
|
6052
|
+
push({
|
|
6053
|
+
kind: "notice",
|
|
6054
|
+
text: ` ${from} was retired; now using its successor, ${successor}. /model picks another.`
|
|
6055
|
+
});
|
|
6056
|
+
}
|
|
6057
|
+
const latest = client.latestCli;
|
|
6058
|
+
if (latest && isNewerVersion(latest, VERSION)) {
|
|
6059
|
+
push({
|
|
6060
|
+
kind: "notice",
|
|
6061
|
+
text: ` clixad ${latest} is out (you have ${VERSION}). Update with: npm install -g clixad@latest`
|
|
6062
|
+
});
|
|
6063
|
+
}
|
|
5886
6064
|
}).catch(() => void 0);
|
|
5887
6065
|
}, []);
|
|
5888
6066
|
useEffect(() => {
|
|
@@ -6122,7 +6300,7 @@ function App({
|
|
|
6122
6300
|
setSpent((s) => s + res.creditsCharged);
|
|
6123
6301
|
push({
|
|
6124
6302
|
kind: "notice",
|
|
6125
|
-
text: ` compacted context: ~${Math.round(res.before / 1e3)}k \u2192 ~${Math.round(res.after / 1e3)}k tokens (${res.creditsCharged} credits)`
|
|
6303
|
+
text: ` compacted context: ~${Math.round(res.before / 1e3)}k \u2192 ~${Math.round(res.after / 1e3)}k tokens (${res.creditsCharged.toLocaleString("en-US")} credits)`
|
|
6126
6304
|
});
|
|
6127
6305
|
}
|
|
6128
6306
|
}
|
|
@@ -6231,19 +6409,29 @@ ${NO_SEARCH_INSTRUCTION}`),
|
|
|
6231
6409
|
pendingTaskRef.current = { task, images };
|
|
6232
6410
|
pendingFreeRef.current = err.freeModels;
|
|
6233
6411
|
pendingGrantRef.current = err.grantPerAd;
|
|
6412
|
+
pendingBalanceRef.current = err.balance;
|
|
6234
6413
|
const free = err.freeModels[0];
|
|
6235
6414
|
push({
|
|
6236
6415
|
kind: "notice",
|
|
6237
6416
|
tone: "warn",
|
|
6238
|
-
text: free ? `
|
|
6417
|
+
text: free ? ` ${shortfallHeadline(err, model2)}
|
|
6239
6418
|
Press enter \u2014 ${free.id} is still free today (~${free.turnsLeft} turn${free.turnsLeft === 1 ? "" : "s"} left), or take an offer instead.
|
|
6240
6419
|
Your request is kept either way, so you will not have to type it again.
|
|
6241
6420
|
Offers you are screened out of pay nothing; that is normal, just start another.
|
|
6242
|
-
/buy is there too, if you would rather pay than do an offer.` : `
|
|
6421
|
+
/buy is there too, if you would rather pay than do an offer.` : ` ${shortfallHeadline(err, model2)}
|
|
6243
6422
|
Press enter to open the ad wall \u2014 offers pay ${grantLabel(err.grantPerAd)}.
|
|
6244
6423
|
I'll continue automatically. Offers you are screened out of pay nothing;
|
|
6245
6424
|
that is normal, just start another.
|
|
6246
|
-
|
|
6425
|
+
` + // A wallet that is short rather than empty has a third way on,
|
|
6426
|
+
// and it is the cheapest: the same credits on a smaller model.
|
|
6427
|
+
// It names the command and no figure, since what a model costs
|
|
6428
|
+
// is the gateway's to say and `/model` says it.
|
|
6429
|
+
(err.balance > 0 ? ` Or pick a cheaper model with /model, which shows what each one costs.
|
|
6430
|
+
` : "") + // One clause, last, and it stays last. The wall is how this product
|
|
6431
|
+
// is meant to be paid for and the offer is free; naming /buy here
|
|
6432
|
+
// is discoverability rather than a pitch, and it is the moment
|
|
6433
|
+
// somebody actually wants to know the command exists.
|
|
6434
|
+
` /buy is there too, if you would rather pay than do an offer.`
|
|
6247
6435
|
});
|
|
6248
6436
|
} else if (err instanceof AuthError) {
|
|
6249
6437
|
push({ kind: "notice", tone: "error", text: NOT_SIGNED_IN });
|
|
@@ -6370,7 +6558,7 @@ ${NO_SEARCH_INSTRUCTION}`),
|
|
|
6370
6558
|
const runAdWall = useCallback(async () => {
|
|
6371
6559
|
const pending = pendingTaskRef.current;
|
|
6372
6560
|
pendingTaskRef.current = null;
|
|
6373
|
-
|
|
6561
|
+
let before = balance ?? (await client.wallet().catch(() => void 0))?.balance;
|
|
6374
6562
|
const code = await client.handoffCode();
|
|
6375
6563
|
const base = config.dashboardUrl.replace(/\/+$/, "");
|
|
6376
6564
|
openBrowser(code ? `${base}/?c=${encodeURIComponent(code)}` : base);
|
|
@@ -6388,7 +6576,10 @@ ${NO_SEARCH_INSTRUCTION}`),
|
|
|
6388
6576
|
await sleep4(3e3, signal);
|
|
6389
6577
|
if (signal.aborted) break;
|
|
6390
6578
|
const w = await client.wallet(signal).catch(() => void 0);
|
|
6391
|
-
if (w &&
|
|
6579
|
+
if (w && before === void 0) {
|
|
6580
|
+
before = w.balance;
|
|
6581
|
+
setBalance(w.balance);
|
|
6582
|
+
} else if (w && before !== void 0 && w.balance > before) {
|
|
6392
6583
|
setBalance(w.balance);
|
|
6393
6584
|
credited = true;
|
|
6394
6585
|
}
|
|
@@ -6431,7 +6622,7 @@ ${NO_SEARCH_INSTRUCTION}`),
|
|
|
6431
6622
|
];
|
|
6432
6623
|
setPickerSel(0);
|
|
6433
6624
|
setPicker({
|
|
6434
|
-
title: "Out of credits",
|
|
6625
|
+
title: (pendingBalanceRef.current ?? 0) > 0 ? "Not enough credits" : "Out of credits",
|
|
6435
6626
|
subtitle: "Pick a free model to carry on with, or earn credits at the wall.",
|
|
6436
6627
|
items,
|
|
6437
6628
|
onPick: (choice) => {
|
|
@@ -6482,7 +6673,7 @@ ${NO_SEARCH_INSTRUCTION}`),
|
|
|
6482
6673
|
}, [client, push, runBusy]);
|
|
6483
6674
|
const runCheckout = useCallback(
|
|
6484
6675
|
async (pack) => {
|
|
6485
|
-
|
|
6676
|
+
let before = balance ?? (await client.wallet().catch(() => void 0))?.balance;
|
|
6486
6677
|
let credited = false;
|
|
6487
6678
|
let stopped = false;
|
|
6488
6679
|
await runBusy(WAITING_FOR_PAYMENT, async (signal) => {
|
|
@@ -6506,7 +6697,10 @@ ${NO_SEARCH_INSTRUCTION}`),
|
|
|
6506
6697
|
await sleep4(3e3, signal);
|
|
6507
6698
|
if (signal.aborted) break;
|
|
6508
6699
|
const w = await client.wallet(signal).catch(() => void 0);
|
|
6509
|
-
if (w &&
|
|
6700
|
+
if (w && before === void 0) {
|
|
6701
|
+
before = w.balance;
|
|
6702
|
+
setBalance(w.balance);
|
|
6703
|
+
} else if (w && before !== void 0 && w.balance > before) {
|
|
6510
6704
|
setBalance(w.balance);
|
|
6511
6705
|
credited = true;
|
|
6512
6706
|
}
|
|
@@ -6589,7 +6783,7 @@ ${NO_SEARCH_INSTRUCTION}`),
|
|
|
6589
6783
|
setSpent((s) => s + res.creditsCharged);
|
|
6590
6784
|
push({
|
|
6591
6785
|
kind: "notice",
|
|
6592
|
-
text: ` compacted: ~${Math.round(res.before / 1e3)}k \u2192 ~${Math.round(res.after / 1e3)}k tokens (${res.creditsCharged} credits)`
|
|
6786
|
+
text: ` compacted: ~${Math.round(res.before / 1e3)}k \u2192 ~${Math.round(res.after / 1e3)}k tokens (${res.creditsCharged.toLocaleString("en-US")} credits)`
|
|
6593
6787
|
});
|
|
6594
6788
|
});
|
|
6595
6789
|
return;
|
|
@@ -7299,7 +7493,7 @@ ${dropped.map((l) => ` \xB7 ${l}`).join("\n")}`
|
|
|
7299
7493
|
let estimate;
|
|
7300
7494
|
const info = catalogRef.current.find((m) => m.id === model);
|
|
7301
7495
|
const shape = client.taskShape;
|
|
7302
|
-
if (!busy && info && shape && typed.trim() !== "" && !/^[/!]/.test(typed.trimStart())) {
|
|
7496
|
+
if (!busy && info && shape && balance !== void 0 && typed.trim() !== "" && !/^[/!]/.test(typed.trimStart())) {
|
|
7303
7497
|
const range = estimateTask(info, conversationUsed + schemaTokens + estimateTokens(typed), shape);
|
|
7304
7498
|
const onFree = Boolean(info.free && freeTier?.models.includes(model));
|
|
7305
7499
|
const verdict = taskVerdict(range, { balance, ...onFree ? { freeLeft: freeTier.credits_left } : {} });
|
|
@@ -7489,7 +7683,7 @@ function modelHint(m, free) {
|
|
|
7489
7683
|
function turnMeta(result) {
|
|
7490
7684
|
const n = (v) => v.toLocaleString("en-US");
|
|
7491
7685
|
const balance = `balance ${n(result.balance)}`;
|
|
7492
|
-
if (result.creditsWaived <= 0) return `[${result.creditsCharged} credits \xB7 ${balance}]`;
|
|
7686
|
+
if (result.creditsWaived <= 0) return `[${n(result.creditsCharged)} credits \xB7 ${balance}]`;
|
|
7493
7687
|
const left = result.freeCreditsLeft === void 0 ? "" : ` \xB7 ${n(result.freeCreditsLeft)} free credits left`;
|
|
7494
7688
|
if (result.creditsCharged > 0) {
|
|
7495
7689
|
return `[free: ${n(result.creditsWaived)} credits \xB7 then ${n(result.creditsCharged)} charged \xB7 ${balance}]`;
|
|
@@ -7508,7 +7702,12 @@ function statusRow(o, estimate, cols) {
|
|
|
7508
7702
|
const burn = o.spent > 0 && o.minutes >= 1 ? `${Math.round(o.spent / o.minutes).toLocaleString("en-US")} cr/min` : void 0;
|
|
7509
7703
|
const clauses = [
|
|
7510
7704
|
{ text: o.model, rank: KEEP_ALWAYS },
|
|
7511
|
-
{
|
|
7705
|
+
{
|
|
7706
|
+
// "? cr" rather than "0 cr": unknown is not empty, and an empty-looking
|
|
7707
|
+
// wallet is the one misreading on this row that frightens somebody.
|
|
7708
|
+
text: o.balance === void 0 ? "? cr" : `${o.balance.toLocaleString("en-US")} cr`,
|
|
7709
|
+
rank: KEEP_ALWAYS
|
|
7710
|
+
},
|
|
7512
7711
|
// Above the context warning, below the balance. It is the second number
|
|
7513
7712
|
// deciding whether the next turn costs anything, and unlike the spend it is
|
|
7514
7713
|
// *news* — it only exists while a free model is selected and it counts down.
|
|
@@ -7746,7 +7945,7 @@ async function main() {
|
|
|
7746
7945
|
for (const text of configNotices) console.error(c.yellow(text));
|
|
7747
7946
|
switch (cmd) {
|
|
7748
7947
|
case "login":
|
|
7749
|
-
return login(client, config, rest);
|
|
7948
|
+
return void await login(client, config, rest, { nextStep: true });
|
|
7750
7949
|
case "logout":
|
|
7751
7950
|
return logout(config);
|
|
7752
7951
|
case "whoami":
|
|
@@ -7810,7 +8009,7 @@ async function main() {
|
|
|
7810
8009
|
return repl(client, config, { task: [cmd, ...rest].join(" ").trim(), notices: configNotices });
|
|
7811
8010
|
}
|
|
7812
8011
|
}
|
|
7813
|
-
async function login(client, config, args) {
|
|
8012
|
+
async function login(client, config, args, opts = {}) {
|
|
7814
8013
|
const parsed = parseLoginArgs(args);
|
|
7815
8014
|
for (const line2 of TERMS_NOTICE.split("\n")) console.log(c.dim(` ${line2}`));
|
|
7816
8015
|
const outcome = await performLogin(client, config, parsed, {
|
|
@@ -7836,20 +8035,25 @@ async function login(client, config, args) {
|
|
|
7836
8035
|
if (account.model) console.log(` model: ${c.bold(account.model)} ${c.dim("(the default for new accounts)")}`);
|
|
7837
8036
|
await showStreak(client);
|
|
7838
8037
|
console.log(c.dim(` token stored in ${configPath()}`));
|
|
7839
|
-
|
|
8038
|
+
if (opts.nextStep) {
|
|
8039
|
+
console.log(`
|
|
8040
|
+
Next: open a project folder and run ${c.cyan("clixad")} to start coding.`);
|
|
8041
|
+
}
|
|
8042
|
+
return { created: account.created };
|
|
7840
8043
|
}
|
|
7841
8044
|
case "closed":
|
|
7842
8045
|
reportSignupClosed(outcome.message, outcome.hadInvite);
|
|
7843
8046
|
process.exitCode = 1;
|
|
7844
|
-
return;
|
|
8047
|
+
return void 0;
|
|
7845
8048
|
case "timeout":
|
|
7846
8049
|
console.log(c.red("\n login timed out \u2014 run `clixad login` again."));
|
|
7847
|
-
return;
|
|
8050
|
+
return void 0;
|
|
7848
8051
|
case "aborted":
|
|
7849
|
-
return;
|
|
8052
|
+
return void 0;
|
|
7850
8053
|
default:
|
|
7851
8054
|
console.log(c.red(`
|
|
7852
8055
|
login failed: ${outcome.error}`));
|
|
8056
|
+
return void 0;
|
|
7853
8057
|
}
|
|
7854
8058
|
}
|
|
7855
8059
|
function reportSignupClosed(message, hadInvite) {
|
|
@@ -8051,7 +8255,7 @@ ${NO_SEARCH_INSTRUCTION}`),
|
|
|
8051
8255
|
} catch (err) {
|
|
8052
8256
|
if (err instanceof PaywallError) {
|
|
8053
8257
|
console.log(c.yellow(`
|
|
8054
|
-
|
|
8258
|
+
${shortfallHeadline(err, config.model)}`));
|
|
8055
8259
|
const freeLine = freeAlternativeLine(err.freeModels);
|
|
8056
8260
|
if (freeLine) console.log(c.green(freeLine));
|
|
8057
8261
|
console.log(
|
|
@@ -8102,7 +8306,7 @@ async function codeCmd(client, config, rest) {
|
|
|
8102
8306
|
const w = await safeWallet(client);
|
|
8103
8307
|
if (w) {
|
|
8104
8308
|
console.log(
|
|
8105
|
-
c.dim(` wallet: ${w.balance} credits \xB7 model ${config.model} \xB7 gateway ${config.gatewayUrl}`)
|
|
8309
|
+
c.dim(` wallet: ${credits(w.balance)} credits \xB7 model ${config.model} \xB7 gateway ${config.gatewayUrl}`)
|
|
8106
8310
|
);
|
|
8107
8311
|
if (w.balance <= 0) {
|
|
8108
8312
|
console.log(c.yellow(" You're out of credits \u2014 run `clixad earn` first, or the agent will hit a paywall."));
|
|
@@ -8137,8 +8341,9 @@ async function repl(client, config, opts = {}) {
|
|
|
8137
8341
|
console.log(c.yellow('Interactive mode needs a terminal. Use `clixad -p "..."` for scripted runs.'));
|
|
8138
8342
|
return;
|
|
8139
8343
|
}
|
|
8344
|
+
let newAccount = false;
|
|
8140
8345
|
if (!config.token) {
|
|
8141
|
-
await login(client, config, []);
|
|
8346
|
+
newAccount = Boolean((await login(client, config, []))?.created);
|
|
8142
8347
|
if (!config.token) return;
|
|
8143
8348
|
}
|
|
8144
8349
|
let session;
|
|
@@ -8149,8 +8354,7 @@ async function repl(client, config, opts = {}) {
|
|
|
8149
8354
|
session = loadSession(opts.resume);
|
|
8150
8355
|
if (!session) return console.log(c.red(`no such session: ${opts.resume}`));
|
|
8151
8356
|
}
|
|
8152
|
-
const streak = await client.
|
|
8153
|
-
const wallet = await safeWallet(client);
|
|
8357
|
+
const { streak, wallet } = await startupReads(client, config.gatewayUrl);
|
|
8154
8358
|
const cwd = process.cwd();
|
|
8155
8359
|
const initHint = shouldSuggestInit(cwd) ? { returning: hasSessionIn(cwd) } : void 0;
|
|
8156
8360
|
clearScreen();
|
|
@@ -8161,24 +8365,30 @@ async function repl(client, config, opts = {}) {
|
|
|
8161
8365
|
wallet,
|
|
8162
8366
|
streak,
|
|
8163
8367
|
session,
|
|
8368
|
+
newAccount,
|
|
8164
8369
|
initialTask: opts.task,
|
|
8165
8370
|
...opts.notices?.length ? { notices: opts.notices } : {},
|
|
8166
8371
|
...initHint ? { initHint } : {}
|
|
8167
8372
|
});
|
|
8168
8373
|
}
|
|
8169
8374
|
async function runTurn(client, config, messages) {
|
|
8375
|
+
let started = false;
|
|
8170
8376
|
try {
|
|
8171
|
-
process.stdout.write(c.green("assistant ") + "");
|
|
8172
8377
|
const result = await client.chatStream(messages, config.model, {
|
|
8173
|
-
onDelta: (chunk) =>
|
|
8378
|
+
onDelta: (chunk) => {
|
|
8379
|
+
if (!started) process.stdout.write(c.green("assistant "));
|
|
8380
|
+
started = true;
|
|
8381
|
+
process.stdout.write(chunk);
|
|
8382
|
+
}
|
|
8174
8383
|
});
|
|
8384
|
+
if (!started) process.stdout.write(c.green("assistant "));
|
|
8175
8385
|
process.stdout.write("\n");
|
|
8176
|
-
console.log(c.dim(` [${result.creditsCharged} credits \xB7 balance ${result.balance}]`));
|
|
8386
|
+
console.log(c.dim(` [${credits(result.creditsCharged)} credits \xB7 balance ${credits(result.balance)}]`));
|
|
8177
8387
|
return result;
|
|
8178
8388
|
} catch (err) {
|
|
8179
|
-
process.stdout.write("\n");
|
|
8389
|
+
if (started) process.stdout.write("\n");
|
|
8180
8390
|
if (err instanceof PaywallError) {
|
|
8181
|
-
console.log(c.yellow(
|
|
8391
|
+
console.log(c.yellow(shortfallHeadline(err, config.model)));
|
|
8182
8392
|
const freeLine = freeAlternativeLine(err.freeModels);
|
|
8183
8393
|
if (freeLine) console.log(c.green(freeLine));
|
|
8184
8394
|
console.log(
|
|
@@ -8208,29 +8418,69 @@ async function safeWallet(client) {
|
|
|
8208
8418
|
return void 0;
|
|
8209
8419
|
}
|
|
8210
8420
|
}
|
|
8421
|
+
var CONNECTING_AFTER_MS = 700;
|
|
8422
|
+
var WAKING_AFTER_MS = 6e3;
|
|
8423
|
+
async function startupReads(client, gatewayUrl) {
|
|
8424
|
+
const host = (() => {
|
|
8425
|
+
try {
|
|
8426
|
+
return new URL(gatewayUrl).host;
|
|
8427
|
+
} catch {
|
|
8428
|
+
return gatewayUrl;
|
|
8429
|
+
}
|
|
8430
|
+
})();
|
|
8431
|
+
const say = (text) => {
|
|
8432
|
+
if (process.stdout.isTTY) console.log(c.dim(text));
|
|
8433
|
+
};
|
|
8434
|
+
const timers = [
|
|
8435
|
+
setTimeout(() => say(` connecting to ${host}\u2026`), CONNECTING_AFTER_MS),
|
|
8436
|
+
setTimeout(
|
|
8437
|
+
() => say(" still waiting: the server sleeps after a quiet spell and takes up to a minute to wake. ctrl+c cancels."),
|
|
8438
|
+
WAKING_AFTER_MS
|
|
8439
|
+
)
|
|
8440
|
+
];
|
|
8441
|
+
try {
|
|
8442
|
+
const [streak, wallet] = await Promise.all([client.claimStreak(), safeWallet(client)]);
|
|
8443
|
+
const balance = streak?.claimed ? streak.balance : wallet?.balance;
|
|
8444
|
+
return {
|
|
8445
|
+
streak,
|
|
8446
|
+
wallet: wallet && balance !== void 0 ? { ...wallet, balance } : wallet
|
|
8447
|
+
};
|
|
8448
|
+
} finally {
|
|
8449
|
+
for (const t of timers) clearTimeout(t);
|
|
8450
|
+
}
|
|
8451
|
+
}
|
|
8211
8452
|
function printHelp() {
|
|
8212
|
-
|
|
8453
|
+
const rows = [
|
|
8454
|
+
["", "", "start the interactive coding agent here (signs you in if needed)"],
|
|
8455
|
+
['"<task>"', "", "the same, with a task already running"],
|
|
8456
|
+
["--continue", "", "resume the last session in this directory"],
|
|
8457
|
+
["--resume", "[id]", "list saved sessions, or resume one"],
|
|
8458
|
+
["-p", '"<task>"', "headless agent run (read-only tools)"],
|
|
8459
|
+
["ask", '"<prompt>"', "one-shot answer, no tools"],
|
|
8460
|
+
["models", "", "models with credit prices, and which are free today"],
|
|
8461
|
+
["model", "<id>", "set the default model"],
|
|
8462
|
+
["wallet", "", "balance, offers today, recent activity"],
|
|
8463
|
+
["earn", "", "open the offer wall to earn credits"],
|
|
8464
|
+
["refer", "", "your invite code, and what it has earned"],
|
|
8465
|
+
["redeem", "<code>", "spend a code you were given"],
|
|
8466
|
+
["login", "[email]", "sign in or create an account (--invite <code> if you have one)"],
|
|
8467
|
+
["logout", "", "forget the stored token"],
|
|
8468
|
+
["whoami", "", "account and balance"],
|
|
8469
|
+
["code", '["<task>"]', "launch the Kimi CLI coding agent on this directory"],
|
|
8470
|
+
["--version", "", "print the version and exit"]
|
|
8471
|
+
];
|
|
8472
|
+
const plain = (name, arg) => `clixad${name ? ` ${name}` : ""}${arg ? ` ${arg}` : ""}`;
|
|
8473
|
+
const width = Math.max(...rows.map(([name, arg]) => plain(name, arg).length)) + 2;
|
|
8474
|
+
const lines = rows.map(([name, arg, desc]) => {
|
|
8475
|
+
const shown = `${c.cyan("clixad")}${name ? ` ${c.cyan(name)}` : ""}${arg ? ` ${arg}` : ""}`;
|
|
8476
|
+
return ` ${shown}${" ".repeat(width - plain(name, arg).length)}${desc}`;
|
|
8477
|
+
});
|
|
8478
|
+
console.log(`${c.bold("clixad")}: free AI coding in your terminal, funded by ads
|
|
8213
8479
|
|
|
8214
|
-
|
|
8215
|
-
${c.cyan("logout")} forget the stored token
|
|
8216
|
-
${c.cyan("whoami")} show account + balance
|
|
8217
|
-
${c.cyan("models")} list models with credit prices, and which are free today
|
|
8218
|
-
${c.cyan("model")} <id> set default model
|
|
8219
|
-
${c.cyan("wallet")} balance, ads today, ledger
|
|
8220
|
-
${c.cyan("earn")} open the ad wall to earn credits
|
|
8221
|
-
${c.cyan("refer")} your invite code, and what it has earned
|
|
8222
|
-
${c.cyan("redeem")} <code> spend a code you were given
|
|
8223
|
-
${c.cyan("ask")} "<prompt>" one-shot completion (no tools)
|
|
8224
|
-
${c.cyan("agent")} "<task>" open the REPL with a task already running
|
|
8225
|
-
${c.cyan("-p")} "<task>" headless agent run (read-only tools)
|
|
8226
|
-
${c.cyan("code")} ["<task>"] launch the full Kimi CLI coding agent on the current dir
|
|
8227
|
-
${c.cyan("--continue")} resume the last session in this directory
|
|
8228
|
-
${c.cyan("--resume")} [id] list saved sessions, or resume one
|
|
8229
|
-
${c.cyan("--version")} print the version and exit
|
|
8230
|
-
(no command) interactive coding REPL ${c.dim("(signs you in if needed)")}
|
|
8231
|
-
"<task>" same as ${c.cyan("agent")} \u2014 anything that is not a command is a task
|
|
8480
|
+
${lines.join("\n")}
|
|
8232
8481
|
|
|
8233
|
-
${c.
|
|
8482
|
+
Inside the agent, ${c.cyan("/help")} lists everything else, including ${c.cyan("/buy")} for credit packs.
|
|
8483
|
+
${c.dim("Discord: https://discord.gg/pwxPM3QyF (bugs and feature requests are read there)")}`);
|
|
8234
8484
|
}
|
|
8235
8485
|
main().catch((err) => {
|
|
8236
8486
|
console.error(c.red(err.message));
|