clixad 0.0.1-beta.21 → 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/README.md +8 -1
- package/dist/clixad.mjs +932 -203
- package/package.json +1 -1
package/dist/clixad.mjs
CHANGED
|
@@ -14,6 +14,94 @@ var __export = (target, all) => {
|
|
|
14
14
|
__defProp(target, name, { get: all[name], enumerable: true });
|
|
15
15
|
};
|
|
16
16
|
|
|
17
|
+
// src/estimate.ts
|
|
18
|
+
function parseTaskShape(raw) {
|
|
19
|
+
if (!raw || typeof raw !== "object") return void 0;
|
|
20
|
+
const r = raw;
|
|
21
|
+
const count = (v, min) => typeof v === "number" && Number.isFinite(v) && v >= min && v <= 1e6 ? Math.floor(v) : void 0;
|
|
22
|
+
const callsTypical = count(r.calls_typical, 1);
|
|
23
|
+
const callsLong = count(r.calls_long, 1);
|
|
24
|
+
const growthTokens = count(r.growth_tokens, 0);
|
|
25
|
+
const outputTokens = count(r.output_tokens, 0);
|
|
26
|
+
if (callsTypical === void 0 || callsLong === void 0) return void 0;
|
|
27
|
+
if (growthTokens === void 0 || outputTokens === void 0) return void 0;
|
|
28
|
+
if (callsLong < callsTypical) return void 0;
|
|
29
|
+
return { callsTypical, callsLong, growthTokens, outputTokens };
|
|
30
|
+
}
|
|
31
|
+
function callCredits(model, inputTokens, outputTokens) {
|
|
32
|
+
return Math.ceil(
|
|
33
|
+
(inputTokens * model.credits_per_mtoken_input + outputTokens * model.credits_per_mtoken_output) / 1e6
|
|
34
|
+
);
|
|
35
|
+
}
|
|
36
|
+
function taskCredits(model, contextTokens, calls, shape) {
|
|
37
|
+
const start = Math.max(0, Math.round(contextTokens));
|
|
38
|
+
let total = 0;
|
|
39
|
+
for (let i = 0; i < calls; i++) {
|
|
40
|
+
total += callCredits(model, start + i * shape.growthTokens, shape.outputTokens);
|
|
41
|
+
}
|
|
42
|
+
return total;
|
|
43
|
+
}
|
|
44
|
+
function estimateTask(model, contextTokens, shape) {
|
|
45
|
+
return {
|
|
46
|
+
low: taskCredits(model, contextTokens, shape.callsTypical, shape),
|
|
47
|
+
high: taskCredits(model, contextTokens, shape.callsLong, shape)
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
function taskVerdict(range, wallet) {
|
|
51
|
+
const free = Math.max(0, wallet.freeLeft ?? 0);
|
|
52
|
+
if (free > 0 && free >= range.high) return "free";
|
|
53
|
+
const available = Math.max(0, wallet.balance) + free;
|
|
54
|
+
if (available >= range.high) return "fits";
|
|
55
|
+
if (available >= range.low) return "tight";
|
|
56
|
+
return "short";
|
|
57
|
+
}
|
|
58
|
+
function shortCredits(n, direction) {
|
|
59
|
+
const round = direction === "down" ? Math.floor : Math.ceil;
|
|
60
|
+
if (n < 1e3) return String(Math.max(0, Math.round(n)));
|
|
61
|
+
if (n < 1e4) return `${trim(round(n / 100) / 10)}k`;
|
|
62
|
+
if (n < 1e6) return `${round(n / 1e3)}k`;
|
|
63
|
+
return `${trim(round(n / 1e5) / 10)}M`;
|
|
64
|
+
}
|
|
65
|
+
function trim(n) {
|
|
66
|
+
return Number.isInteger(n) ? String(n) : n.toFixed(1);
|
|
67
|
+
}
|
|
68
|
+
function estimateClause(range, verdict, freeElsewhere) {
|
|
69
|
+
const span = range.low === range.high ? shortCredits(range.high, "up") : `${shortCredits(range.low, "down")}\u2013${shortCredits(range.high, "up")}`;
|
|
70
|
+
const head = `task \u2248 ${span}`;
|
|
71
|
+
const tight = `\u2248${span}`;
|
|
72
|
+
switch (verdict) {
|
|
73
|
+
case "free":
|
|
74
|
+
return { variants: [`${head} \xB7 free`, `${tight} free`], warn: false };
|
|
75
|
+
case "fits":
|
|
76
|
+
return { variants: [`${head} cr`, `${tight} cr`], warn: false };
|
|
77
|
+
case "tight":
|
|
78
|
+
return { variants: [`${head} \xB7 may run short`, `${tight}, may run short`, `${tight} tight`], warn: true };
|
|
79
|
+
case "short":
|
|
80
|
+
return {
|
|
81
|
+
variants: [
|
|
82
|
+
`${head} \xB7 not enough, ${freeElsewhere ? "/model" : "/earn"}`,
|
|
83
|
+
`${tight}, not enough`,
|
|
84
|
+
`${tight} short`
|
|
85
|
+
],
|
|
86
|
+
warn: true
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
var init_estimate = __esm({
|
|
91
|
+
"src/estimate.ts"() {
|
|
92
|
+
"use strict";
|
|
93
|
+
}
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
// src/sponsor/formats.ts
|
|
97
|
+
var EXTRA_AD_FORMATS;
|
|
98
|
+
var init_formats = __esm({
|
|
99
|
+
"src/sponsor/formats.ts"() {
|
|
100
|
+
"use strict";
|
|
101
|
+
EXTRA_AD_FORMATS = ["sidebar_card", "status_bar"];
|
|
102
|
+
}
|
|
103
|
+
});
|
|
104
|
+
|
|
17
105
|
// src/edge.ts
|
|
18
106
|
function detectEdgeBlock(status, headers, body) {
|
|
19
107
|
if (status !== 403) return void 0;
|
|
@@ -225,6 +313,17 @@ function redeemMessage(result) {
|
|
|
225
313
|
function ledgerReasonLabel(reason) {
|
|
226
314
|
return LEDGER_REASONS[reason] ?? reason.replace(/_/g, " ");
|
|
227
315
|
}
|
|
316
|
+
function extraFormatIds(raw) {
|
|
317
|
+
const out = {};
|
|
318
|
+
if (typeof raw !== "object" || raw === null) return out;
|
|
319
|
+
for (const format of EXTRA_AD_FORMATS) {
|
|
320
|
+
const entry = raw[format];
|
|
321
|
+
if (typeof entry !== "object" || entry === null) continue;
|
|
322
|
+
const id = entry.publisher_id;
|
|
323
|
+
if (typeof id === "string" && id.trim()) out[format] = id.trim();
|
|
324
|
+
}
|
|
325
|
+
return out;
|
|
326
|
+
}
|
|
228
327
|
function imagePart(image) {
|
|
229
328
|
return { type: "image_url", image_url: { url: `data:${image.mimeType};base64,${image.data}` } };
|
|
230
329
|
}
|
|
@@ -251,8 +350,47 @@ function asSignupClosed(status, data) {
|
|
|
251
350
|
const d = data;
|
|
252
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;
|
|
253
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
|
+
}
|
|
254
358
|
function paywallError(body) {
|
|
255
|
-
|
|
359
|
+
const free = Array.isArray(body.free_models) ? body.free_models.filter(
|
|
360
|
+
(m) => !!m && typeof m.id === "string" && m.id.length > 0 && Number.isFinite(m.turns_left)
|
|
361
|
+
).map((m) => ({ id: m.id, turnsLeft: Math.max(0, Math.floor(m.turns_left)) })).filter((m) => m.turnsLeft > 0) : [];
|
|
362
|
+
return new PaywallError(body.balance, body.estimated_cost, body.grant_per_ad, free);
|
|
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
|
+
}
|
|
256
394
|
}
|
|
257
395
|
async function authFailure(res, what) {
|
|
258
396
|
const body = await res.text().catch(() => "");
|
|
@@ -278,16 +416,16 @@ async function httpFailure(res, what) {
|
|
|
278
416
|
return detail ? `${what} failed: ${res.status} \u2014 ${detail}` : `${what} failed: ${res.status}`;
|
|
279
417
|
}
|
|
280
418
|
function sleep(ms, signal) {
|
|
281
|
-
return new Promise((
|
|
419
|
+
return new Promise((resolve3) => {
|
|
282
420
|
if (!signal) {
|
|
283
|
-
setTimeout(
|
|
421
|
+
setTimeout(resolve3, ms);
|
|
284
422
|
return;
|
|
285
423
|
}
|
|
286
|
-
if (signal.aborted) return
|
|
424
|
+
if (signal.aborted) return resolve3();
|
|
287
425
|
const done = () => {
|
|
288
426
|
clearTimeout(timer);
|
|
289
427
|
signal.removeEventListener("abort", done);
|
|
290
|
-
|
|
428
|
+
resolve3();
|
|
291
429
|
};
|
|
292
430
|
const timer = setTimeout(done, ms);
|
|
293
431
|
signal.addEventListener("abort", done, { once: true });
|
|
@@ -327,10 +465,12 @@ async function* parseSSE(body) {
|
|
|
327
465
|
}
|
|
328
466
|
}
|
|
329
467
|
}
|
|
330
|
-
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;
|
|
331
469
|
var init_client = __esm({
|
|
332
470
|
"src/client.ts"() {
|
|
333
471
|
"use strict";
|
|
472
|
+
init_estimate();
|
|
473
|
+
init_formats();
|
|
334
474
|
init_edge();
|
|
335
475
|
CHAT_ENCODED_CONTENT_TYPE = "application/vnd.clixad.chat+gzip";
|
|
336
476
|
LEDGER_REASONS = {
|
|
@@ -361,17 +501,46 @@ var init_client = __esm({
|
|
|
361
501
|
waitlistUrl;
|
|
362
502
|
};
|
|
363
503
|
PaywallError = class extends Error {
|
|
364
|
-
constructor(balance, estimatedCost, grantPerAd) {
|
|
504
|
+
constructor(balance, estimatedCost, grantPerAd, freeModels = []) {
|
|
365
505
|
super("Out of credits");
|
|
366
506
|
this.balance = balance;
|
|
367
507
|
this.estimatedCost = estimatedCost;
|
|
368
508
|
this.grantPerAd = grantPerAd;
|
|
509
|
+
this.freeModels = freeModels;
|
|
369
510
|
this.name = "PaywallError";
|
|
370
511
|
}
|
|
371
512
|
balance;
|
|
372
513
|
estimatedCost;
|
|
373
514
|
grantPerAd;
|
|
515
|
+
freeModels;
|
|
374
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
|
+
]);
|
|
375
544
|
AuthError = class extends Error {
|
|
376
545
|
constructor() {
|
|
377
546
|
super("Not logged in. Run `clixad login` first.");
|
|
@@ -412,7 +581,7 @@ var init_client = __esm({
|
|
|
412
581
|
}
|
|
413
582
|
/** Dev-only signup shortcut (production replaces this with GitHub OAuth). */
|
|
414
583
|
async signupDev(email, invite) {
|
|
415
|
-
const res = await
|
|
584
|
+
const res = await gatewayFetch(`${this.config.gatewayUrl}/dev/users`, {
|
|
416
585
|
method: "POST",
|
|
417
586
|
headers: this.headers(false),
|
|
418
587
|
body: JSON.stringify({ ...email ? { email } : {}, ...invite ? { invite } : {} })
|
|
@@ -428,7 +597,7 @@ var init_client = __esm({
|
|
|
428
597
|
/** Begin GitHub device-flow login. Returns null if the gateway has no GitHub
|
|
429
598
|
* configured (501), so the caller can fall back to the dev shortcut. */
|
|
430
599
|
async deviceStart() {
|
|
431
|
-
const res = await
|
|
600
|
+
const res = await gatewayFetch(`${this.config.gatewayUrl}/v1/auth/device/start`, {
|
|
432
601
|
method: "POST",
|
|
433
602
|
headers: this.headers(false)
|
|
434
603
|
});
|
|
@@ -438,7 +607,7 @@ var init_client = __esm({
|
|
|
438
607
|
}
|
|
439
608
|
/** Poll once for device-flow completion. `invite` unlocks a gated signup. */
|
|
440
609
|
async devicePoll(session, invite) {
|
|
441
|
-
const res = await
|
|
610
|
+
const res = await gatewayFetch(`${this.config.gatewayUrl}/v1/auth/device/poll`, {
|
|
442
611
|
method: "POST",
|
|
443
612
|
headers: this.headers(false),
|
|
444
613
|
body: JSON.stringify(invite ? { session, invite } : { session })
|
|
@@ -460,7 +629,7 @@ var init_client = __esm({
|
|
|
460
629
|
*/
|
|
461
630
|
async handoffCode() {
|
|
462
631
|
try {
|
|
463
|
-
const res = await
|
|
632
|
+
const res = await gatewayFetch(`${this.config.gatewayUrl}/v1/auth/handoff`, {
|
|
464
633
|
method: "POST",
|
|
465
634
|
headers: this.headers()
|
|
466
635
|
});
|
|
@@ -487,7 +656,7 @@ var init_client = __esm({
|
|
|
487
656
|
*/
|
|
488
657
|
async claimStreak(signal) {
|
|
489
658
|
try {
|
|
490
|
-
const res = await
|
|
659
|
+
const res = await gatewayFetch(`${this.config.gatewayUrl}/v1/streak/claim`, {
|
|
491
660
|
method: "POST",
|
|
492
661
|
headers: this.headers(),
|
|
493
662
|
...signal ? { signal } : {}
|
|
@@ -522,7 +691,7 @@ var init_client = __esm({
|
|
|
522
691
|
*/
|
|
523
692
|
async sendFeedback(report, signal) {
|
|
524
693
|
try {
|
|
525
|
-
const res = await
|
|
694
|
+
const res = await gatewayFetch(`${this.config.gatewayUrl}/v1/feedback`, {
|
|
526
695
|
method: "POST",
|
|
527
696
|
headers: this.headers(),
|
|
528
697
|
...signal ? { signal } : {},
|
|
@@ -557,7 +726,7 @@ var init_client = __esm({
|
|
|
557
726
|
* that did not run, and saying so beats printing an empty summary.
|
|
558
727
|
*/
|
|
559
728
|
async referral(signal) {
|
|
560
|
-
const res = await
|
|
729
|
+
const res = await gatewayFetch(`${this.config.gatewayUrl}/v1/referrals`, {
|
|
561
730
|
headers: this.headers(),
|
|
562
731
|
...signal ? { signal } : {}
|
|
563
732
|
});
|
|
@@ -588,7 +757,7 @@ var init_client = __esm({
|
|
|
588
757
|
* 200 with a status, because that is an answer rather than a failure.
|
|
589
758
|
*/
|
|
590
759
|
async redeem(code, signal) {
|
|
591
|
-
const res = await
|
|
760
|
+
const res = await gatewayFetch(`${this.config.gatewayUrl}/v1/redeem`, {
|
|
592
761
|
method: "POST",
|
|
593
762
|
headers: this.headers(),
|
|
594
763
|
...signal ? { signal } : {},
|
|
@@ -609,7 +778,7 @@ var init_client = __esm({
|
|
|
609
778
|
* them with the input box locked and "esc to stop" on screen.
|
|
610
779
|
*/
|
|
611
780
|
async creditPacks(signal) {
|
|
612
|
-
const res = await
|
|
781
|
+
const res = await gatewayFetch(`${this.config.gatewayUrl}/v1/billing/packs`, { signal });
|
|
613
782
|
if (!res.ok) throw new Error(`packs failed: ${res.status}`);
|
|
614
783
|
return res.json();
|
|
615
784
|
}
|
|
@@ -620,7 +789,7 @@ var init_client = __esm({
|
|
|
620
789
|
* could not deliver instantly is worse than not starting one.
|
|
621
790
|
*/
|
|
622
791
|
async checkout(pack, consent, signal) {
|
|
623
|
-
const res = await
|
|
792
|
+
const res = await gatewayFetch(`${this.config.gatewayUrl}/v1/billing/checkout`, {
|
|
624
793
|
method: "POST",
|
|
625
794
|
headers: this.headers(),
|
|
626
795
|
...signal ? { signal } : {},
|
|
@@ -636,12 +805,46 @@ var init_client = __esm({
|
|
|
636
805
|
// the better part of a minute) would otherwise be indistinguishable from a
|
|
637
806
|
// hung terminal. Nothing else passes one — a one-shot CLI command has Ctrl+C.
|
|
638
807
|
async models(signal) {
|
|
639
|
-
const res = await
|
|
808
|
+
const res = await gatewayFetch(`${this.config.gatewayUrl}/v1/models`, { signal });
|
|
640
809
|
if (!res.ok) throw new Error(`models failed: ${res.status}`);
|
|
641
810
|
const data = await res.json();
|
|
642
811
|
this.seen = { web_search: data.capabilities?.web_search === true };
|
|
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
|
+
}
|
|
643
821
|
return data.data;
|
|
644
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;
|
|
839
|
+
/**
|
|
840
|
+
* The task shape the cost preview assumes, as of the last catalog fetch, or
|
|
841
|
+
* undefined when the gateway sent none (or one that did not parse). Undefined
|
|
842
|
+
* means no estimate is shown at all, the silence an older gateway gets.
|
|
843
|
+
*/
|
|
844
|
+
get taskShape() {
|
|
845
|
+
return this.shape;
|
|
846
|
+
}
|
|
847
|
+
shape;
|
|
645
848
|
/**
|
|
646
849
|
* What the gateway said it can do, as of the last catalog fetch.
|
|
647
850
|
*
|
|
@@ -657,7 +860,7 @@ var init_client = __esm({
|
|
|
657
860
|
}
|
|
658
861
|
seen = { web_search: false };
|
|
659
862
|
async wallet(signal) {
|
|
660
|
-
const res = await
|
|
863
|
+
const res = await gatewayFetch(`${this.config.gatewayUrl}/v1/wallet`, { headers: this.headers(), signal });
|
|
661
864
|
if (res.status === 401) throw await authFailure(res, "wallet");
|
|
662
865
|
if (!res.ok) throw new Error(`wallet failed: ${res.status}`);
|
|
663
866
|
return res.json();
|
|
@@ -693,7 +896,7 @@ var init_client = __esm({
|
|
|
693
896
|
*/
|
|
694
897
|
async sponsorConfig(signal) {
|
|
695
898
|
try {
|
|
696
|
-
const res = await
|
|
899
|
+
const res = await gatewayFetch(`${this.config.gatewayUrl}/v1/sponsor/config`, {
|
|
697
900
|
headers: this.headers(),
|
|
698
901
|
...signal ? { signal } : {}
|
|
699
902
|
});
|
|
@@ -701,11 +904,13 @@ var init_client = __esm({
|
|
|
701
904
|
const body = await res.json();
|
|
702
905
|
const publisherId = typeof body.publisher_id === "string" ? body.publisher_id.trim() : "";
|
|
703
906
|
if (!publisherId) return null;
|
|
907
|
+
const formats = extraFormatIds(body.formats);
|
|
704
908
|
return {
|
|
705
909
|
publisherId,
|
|
706
910
|
...typeof body.category === "string" ? { category: body.category } : {},
|
|
707
911
|
...typeof body.subject === "string" ? { subject: body.subject } : {},
|
|
708
|
-
...typeof body.dwell_ms === "number" ? { dwellMs: body.dwell_ms } : {}
|
|
912
|
+
...typeof body.dwell_ms === "number" ? { dwellMs: body.dwell_ms } : {},
|
|
913
|
+
...Object.keys(formats).length > 0 ? { formats } : {}
|
|
709
914
|
};
|
|
710
915
|
} catch {
|
|
711
916
|
return null;
|
|
@@ -729,12 +934,13 @@ var init_client = __esm({
|
|
|
729
934
|
*/
|
|
730
935
|
async reportImpression(report, signal) {
|
|
731
936
|
try {
|
|
732
|
-
const res = await
|
|
937
|
+
const res = await gatewayFetch(`${this.config.gatewayUrl}/v1/sponsor/impression`, {
|
|
733
938
|
method: "POST",
|
|
734
939
|
headers: this.headers(),
|
|
735
940
|
...signal ? { signal } : {},
|
|
736
941
|
body: JSON.stringify({
|
|
737
942
|
impression_id: report.impressionId,
|
|
943
|
+
...report.format && report.format !== "top_bar" ? { format: report.format } : {},
|
|
738
944
|
...report.adId ? { ad_id: report.adId } : {},
|
|
739
945
|
billable: report.billable,
|
|
740
946
|
from_cache: report.fromCache,
|
|
@@ -761,7 +967,7 @@ var init_client = __esm({
|
|
|
761
967
|
const url = new URL(`${this.config.gatewayUrl}/v1/web/search`);
|
|
762
968
|
url.searchParams.set("q", query);
|
|
763
969
|
if (opts.count) url.searchParams.set("count", String(opts.count));
|
|
764
|
-
const res = await
|
|
970
|
+
const res = await gatewayFetch(url, {
|
|
765
971
|
headers: this.headers(),
|
|
766
972
|
...opts.signal ? { signal: opts.signal } : {}
|
|
767
973
|
});
|
|
@@ -801,7 +1007,7 @@ var init_client = __esm({
|
|
|
801
1007
|
/** One POST to the chat route, in whichever transport is currently in use. */
|
|
802
1008
|
sendChat(payload, encoded, signal) {
|
|
803
1009
|
const json = JSON.stringify(payload);
|
|
804
|
-
return
|
|
1010
|
+
return gatewayFetch(`${this.config.gatewayUrl}/v1/chat/completions`, {
|
|
805
1011
|
method: "POST",
|
|
806
1012
|
headers: encoded ? { ...this.headers(), "Content-Type": CHAT_ENCODED_CONTENT_TYPE } : this.headers(),
|
|
807
1013
|
body: encoded ? encodeChatBody(json) : json,
|
|
@@ -1132,12 +1338,13 @@ function saveConfig(config) {
|
|
|
1132
1338
|
function configPath() {
|
|
1133
1339
|
return CONFIG_PATH;
|
|
1134
1340
|
}
|
|
1135
|
-
var CONFIG_PATH, QUARANTINE_PATH, DEFAULTS, ENV_OVERRIDES, SUPERSEDED_DEFAULTS, IDENTITY_KEYS, baseline, envApplied, notices;
|
|
1341
|
+
var CONFIG_PATH, QUARANTINE_PATH, FALLBACK_MODEL, DEFAULTS, ENV_OVERRIDES, SUPERSEDED_DEFAULTS, IDENTITY_KEYS, baseline, envApplied, notices;
|
|
1136
1342
|
var init_config = __esm({
|
|
1137
1343
|
"src/config.ts"() {
|
|
1138
1344
|
"use strict";
|
|
1139
1345
|
CONFIG_PATH = join(homedir(), ".clixad", "config.json");
|
|
1140
1346
|
QUARANTINE_PATH = `${CONFIG_PATH}.corrupt`;
|
|
1347
|
+
FALLBACK_MODEL = "gemini-2.5-flash-lite";
|
|
1141
1348
|
DEFAULTS = {
|
|
1142
1349
|
gatewayUrl: "https://clixad.onrender.com",
|
|
1143
1350
|
// Same origin as the gateway: the ad wall is served by the gateway process
|
|
@@ -1145,7 +1352,7 @@ var init_config = __esm({
|
|
|
1145
1352
|
// fields stay separate so either can be pointed elsewhere, but they no longer
|
|
1146
1353
|
// diverge by default.
|
|
1147
1354
|
dashboardUrl: "https://clixad.onrender.com",
|
|
1148
|
-
model:
|
|
1355
|
+
model: FALLBACK_MODEL
|
|
1149
1356
|
};
|
|
1150
1357
|
ENV_OVERRIDES = [
|
|
1151
1358
|
["gatewayUrl", "CLIXAD_GATEWAY_URL"],
|
|
@@ -1188,6 +1395,12 @@ function parseLoginArgs(args) {
|
|
|
1188
1395
|
}
|
|
1189
1396
|
return { invite: invite?.trim() || void 0, email: rest[0] };
|
|
1190
1397
|
}
|
|
1398
|
+
function adoptNewAccountModel(config, created, named) {
|
|
1399
|
+
if (!created || typeof named !== "string" || !/^[A-Za-z0-9._\/-]{1,64}$/.test(named)) return void 0;
|
|
1400
|
+
if (config.model !== FALLBACK_MODEL || named === config.model) return void 0;
|
|
1401
|
+
config.model = named;
|
|
1402
|
+
return named;
|
|
1403
|
+
}
|
|
1191
1404
|
async function performLogin(client, config, args, report, signal) {
|
|
1192
1405
|
if (!args.email) {
|
|
1193
1406
|
const start = await client.deviceStart().catch(() => null);
|
|
@@ -1218,6 +1431,7 @@ async function githubLogin(client, config, start, invite, report, signal) {
|
|
|
1218
1431
|
config.email = poll.email;
|
|
1219
1432
|
config.login = poll.login;
|
|
1220
1433
|
config.tokenGateway = config.gatewayUrl;
|
|
1434
|
+
const model = adoptNewAccountModel(config, poll.created, poll.default_model);
|
|
1221
1435
|
saveConfig(config);
|
|
1222
1436
|
return {
|
|
1223
1437
|
status: "ok",
|
|
@@ -1226,7 +1440,8 @@ async function githubLogin(client, config, start, invite, report, signal) {
|
|
|
1226
1440
|
login: poll.login,
|
|
1227
1441
|
balance: poll.balance,
|
|
1228
1442
|
created: poll.created,
|
|
1229
|
-
signupBonus: poll.signupBonus
|
|
1443
|
+
signupBonus: poll.signupBonus,
|
|
1444
|
+
...model ? { model } : {}
|
|
1230
1445
|
}
|
|
1231
1446
|
};
|
|
1232
1447
|
}
|
|
@@ -1249,20 +1464,24 @@ async function devLogin(client, config, email, invite) {
|
|
|
1249
1464
|
config.email = res.email;
|
|
1250
1465
|
config.tokenGateway = config.gatewayUrl;
|
|
1251
1466
|
delete config.login;
|
|
1467
|
+
const model = adoptNewAccountModel(config, true, res.default_model);
|
|
1252
1468
|
saveConfig(config);
|
|
1253
|
-
return {
|
|
1469
|
+
return {
|
|
1470
|
+
status: "ok",
|
|
1471
|
+
account: { email: res.email, balance: res.balance, created: true, ...model ? { model } : {} }
|
|
1472
|
+
};
|
|
1254
1473
|
}
|
|
1255
1474
|
function sleep2(ms, signal) {
|
|
1256
|
-
return new Promise((
|
|
1475
|
+
return new Promise((resolve3) => {
|
|
1257
1476
|
if (!signal) {
|
|
1258
|
-
setTimeout(
|
|
1477
|
+
setTimeout(resolve3, ms);
|
|
1259
1478
|
return;
|
|
1260
1479
|
}
|
|
1261
|
-
if (signal.aborted) return
|
|
1480
|
+
if (signal.aborted) return resolve3();
|
|
1262
1481
|
const done = () => {
|
|
1263
1482
|
clearTimeout(timer);
|
|
1264
1483
|
signal.removeEventListener("abort", done);
|
|
1265
|
-
|
|
1484
|
+
resolve3();
|
|
1266
1485
|
};
|
|
1267
1486
|
const timer = setTimeout(done, ms);
|
|
1268
1487
|
signal.addEventListener("abort", done, { once: true });
|
|
@@ -1283,6 +1502,49 @@ and the Privacy Policy \u2014 ${PRIVACY_URL}`;
|
|
|
1283
1502
|
}
|
|
1284
1503
|
});
|
|
1285
1504
|
|
|
1505
|
+
// src/free-tier.ts
|
|
1506
|
+
function freeModelToName(free, current) {
|
|
1507
|
+
const left = (id2) => free.turns_left[id2] ?? 0;
|
|
1508
|
+
const usable = (id2) => id2 !== void 0 && free.models.includes(id2) && left(id2) > 0;
|
|
1509
|
+
const id = usable(current) ? current : usable(free.recommended) ? free.recommended : free.models.find(usable);
|
|
1510
|
+
return id === void 0 ? void 0 : { id, turns: left(id) };
|
|
1511
|
+
}
|
|
1512
|
+
function freeTierNotice(o) {
|
|
1513
|
+
const free = o.free;
|
|
1514
|
+
if (!free || !(free.daily_credits > 0)) return void 0;
|
|
1515
|
+
const pick = free.credits_left > 0 ? freeModelToName(free, o.model) : void 0;
|
|
1516
|
+
if (!pick) {
|
|
1517
|
+
return o.balance <= 0 ? "Your balance is empty and today's free turns are used up. They come back at midnight UTC, and /earn tops up before then." : void 0;
|
|
1518
|
+
}
|
|
1519
|
+
if (pick.id === o.model) {
|
|
1520
|
+
return `${pick.id} is free today, ${turns(pick.turns)} left. /model shows the other free models.`;
|
|
1521
|
+
}
|
|
1522
|
+
if (o.balance <= 0) {
|
|
1523
|
+
return `Your balance is empty, but ${pick.id} is still free today (${turns(pick.turns)}). /model switches to it, /earn tops up.`;
|
|
1524
|
+
}
|
|
1525
|
+
return `${free.models.length} models are free today. /model switches, and ${pick.id} has ${turns(pick.turns)} left.`;
|
|
1526
|
+
}
|
|
1527
|
+
function freeHelpLine(free) {
|
|
1528
|
+
if (!free || !(free.daily_credits > 0)) return void 0;
|
|
1529
|
+
if (free.credits_left <= 0) return { label: "free models", desc: "used up today, back at midnight UTC" };
|
|
1530
|
+
return {
|
|
1531
|
+
label: "free today",
|
|
1532
|
+
desc: `${free.models.length} models, ${free.credits_left.toLocaleString("en-US")} free credits left \xB7 /model switches`
|
|
1533
|
+
};
|
|
1534
|
+
}
|
|
1535
|
+
function freeAlternativeLine(freeModels, { rerun = true } = {}) {
|
|
1536
|
+
const first = freeModels.find((m) => m.turnsLeft > 0);
|
|
1537
|
+
if (!first) return void 0;
|
|
1538
|
+
return `${first.id} is still free today (${turns(first.turnsLeft)}). Switch with \`clixad model ${first.id}\`${rerun ? " and run it again" : ""}.`;
|
|
1539
|
+
}
|
|
1540
|
+
var turns;
|
|
1541
|
+
var init_free_tier = __esm({
|
|
1542
|
+
"src/free-tier.ts"() {
|
|
1543
|
+
"use strict";
|
|
1544
|
+
turns = (n) => `about ${n.toLocaleString("en-US")} turn${n === 1 ? "" : "s"}`;
|
|
1545
|
+
}
|
|
1546
|
+
});
|
|
1547
|
+
|
|
1286
1548
|
// src/diff.ts
|
|
1287
1549
|
function splitLines(text) {
|
|
1288
1550
|
const lines = text.split("\n");
|
|
@@ -2381,7 +2643,7 @@ var init_mcp = __esm({
|
|
|
2381
2643
|
if (this.exited) return Promise.reject(new McpError(this.exited));
|
|
2382
2644
|
const id = ++this.seq;
|
|
2383
2645
|
const payload = JSON.stringify({ jsonrpc: "2.0", id, method, params }) + "\n";
|
|
2384
|
-
return new Promise((
|
|
2646
|
+
return new Promise((resolve3, reject) => {
|
|
2385
2647
|
const settle = (fn) => {
|
|
2386
2648
|
clearTimeout(timer);
|
|
2387
2649
|
signal?.removeEventListener("abort", onAbort);
|
|
@@ -2395,7 +2657,7 @@ var init_mcp = __esm({
|
|
|
2395
2657
|
const onAbort = () => settle(() => reject(new McpError("cancelled")));
|
|
2396
2658
|
signal?.addEventListener("abort", onAbort, { once: true });
|
|
2397
2659
|
this.pending.set(id, {
|
|
2398
|
-
resolve: (v) => settle(() =>
|
|
2660
|
+
resolve: (v) => settle(() => resolve3(v)),
|
|
2399
2661
|
reject: (e) => settle(() => reject(e))
|
|
2400
2662
|
});
|
|
2401
2663
|
this.child.stdin?.write(payload, (err) => {
|
|
@@ -3989,7 +4251,7 @@ var init_agent = __esm({
|
|
|
3989
4251
|
import { spawnSync } from "node:child_process";
|
|
3990
4252
|
import { existsSync as existsSync3, readdirSync as readdirSync2, readFileSync as readFileSync4 } from "node:fs";
|
|
3991
4253
|
import { homedir as homedir3, platform } from "node:os";
|
|
3992
|
-
import { join as join4 } from "node:path";
|
|
4254
|
+
import { join as join4, parse, resolve as resolve2 } from "node:path";
|
|
3993
4255
|
function contextFileCandidates(root) {
|
|
3994
4256
|
return [join4(homedir3(), ".clixad", "CLIXAD.md"), join4(root, "AGENTS.md"), join4(root, "CLIXAD.md")];
|
|
3995
4257
|
}
|
|
@@ -4050,13 +4312,21 @@ function topLevel(root) {
|
|
|
4050
4312
|
return void 0;
|
|
4051
4313
|
}
|
|
4052
4314
|
}
|
|
4053
|
-
|
|
4315
|
+
function shouldSuggestInit(root) {
|
|
4316
|
+
const dir = resolve2(root);
|
|
4317
|
+
if (dir === resolve2(homedir3()) || dir === parse(dir).root) return false;
|
|
4318
|
+
if (existsSync3(join4(dir, "CLIXAD.md")) || existsSync3(join4(dir, "AGENTS.md"))) return false;
|
|
4319
|
+
return PROJECT_MARKERS.some((name) => existsSync3(join4(dir, name)));
|
|
4320
|
+
}
|
|
4321
|
+
var MAX_CONTEXT_FILE_CHARS, INIT_PROMPT, INIT_HINT, PROJECT_MARKERS;
|
|
4054
4322
|
var init_context = __esm({
|
|
4055
4323
|
"src/context.ts"() {
|
|
4056
4324
|
"use strict";
|
|
4057
4325
|
init_agent();
|
|
4058
4326
|
MAX_CONTEXT_FILE_CHARS = 2e4;
|
|
4059
4327
|
INIT_PROMPT = "Create a CLIXAD.md file in the workspace root that tells a future coding agent how to work in this project. Explore the repository first (glob, grep, read the README and package manifests). Keep it under 60 lines and cover: what the project is, the layout, how to build/test/run it, and the conventions a newcomer would otherwise get wrong. If CLIXAD.md already exists, improve it instead of replacing it wholesale.";
|
|
4328
|
+
INIT_HINT = "No CLIXAD.md in this project yet. /init writes one, and every later session here starts out knowing the layout.";
|
|
4329
|
+
PROJECT_MARKERS = [".git", "package.json", "pyproject.toml", "Cargo.toml", "go.mod", "pom.xml", "Gemfile"];
|
|
4060
4330
|
}
|
|
4061
4331
|
});
|
|
4062
4332
|
|
|
@@ -4150,7 +4420,18 @@ var init_permissions = __esm({
|
|
|
4150
4420
|
});
|
|
4151
4421
|
|
|
4152
4422
|
// src/session.ts
|
|
4153
|
-
import {
|
|
4423
|
+
import {
|
|
4424
|
+
closeSync as closeSync2,
|
|
4425
|
+
existsSync as existsSync4,
|
|
4426
|
+
mkdirSync as mkdirSync3,
|
|
4427
|
+
openSync as openSync2,
|
|
4428
|
+
readdirSync as readdirSync3,
|
|
4429
|
+
readFileSync as readFileSync5,
|
|
4430
|
+
readSync,
|
|
4431
|
+
rmSync,
|
|
4432
|
+
statSync as statSync2,
|
|
4433
|
+
writeFileSync as writeFileSync3
|
|
4434
|
+
} from "node:fs";
|
|
4154
4435
|
import { homedir as homedir4 } from "node:os";
|
|
4155
4436
|
import { join as join5 } from "node:path";
|
|
4156
4437
|
function newSessionId() {
|
|
@@ -4187,6 +4468,28 @@ function listSessions(cwd, limit = 20) {
|
|
|
4187
4468
|
metas.sort((a, b) => b.updated.localeCompare(a.updated));
|
|
4188
4469
|
return metas.slice(0, limit);
|
|
4189
4470
|
}
|
|
4471
|
+
function hasSessionIn(cwd) {
|
|
4472
|
+
if (!existsSync4(SESSION_DIR)) return false;
|
|
4473
|
+
const needle = `"cwd":${JSON.stringify(cwd)}`;
|
|
4474
|
+
try {
|
|
4475
|
+
for (const name of readdirSync3(SESSION_DIR)) {
|
|
4476
|
+
if (!name.endsWith(".json")) continue;
|
|
4477
|
+
const path = join5(SESSION_DIR, name);
|
|
4478
|
+
let head = "";
|
|
4479
|
+
const fd = openSync2(path, "r");
|
|
4480
|
+
try {
|
|
4481
|
+
const buf = Buffer.alloc(4096);
|
|
4482
|
+
head = buf.subarray(0, readSync(fd, buf, 0, buf.length, 0)).toString("utf8");
|
|
4483
|
+
} finally {
|
|
4484
|
+
closeSync2(fd);
|
|
4485
|
+
}
|
|
4486
|
+
if (head.includes(needle)) return true;
|
|
4487
|
+
if (!head.includes('"cwd":') && loadSession(name.slice(0, -5))?.cwd === cwd) return true;
|
|
4488
|
+
}
|
|
4489
|
+
} catch {
|
|
4490
|
+
}
|
|
4491
|
+
return false;
|
|
4492
|
+
}
|
|
4190
4493
|
function latestSession(cwd) {
|
|
4191
4494
|
const [meta] = listSessions(cwd, 1);
|
|
4192
4495
|
return meta ? loadSession(meta.id) : void 0;
|
|
@@ -4284,14 +4587,14 @@ function runKimiCode(config, task, opts = {}) {
|
|
|
4284
4587
|
OPENAI_BASE_URL: `${config.gatewayUrl.replace(/\/+$/, "")}/v1`,
|
|
4285
4588
|
OPENAI_API_KEY: config.token
|
|
4286
4589
|
};
|
|
4287
|
-
return new Promise((
|
|
4590
|
+
return new Promise((resolve3) => {
|
|
4288
4591
|
const child = process.platform === "win32" ? spawn5([bin, ...args].map(quoteWin).join(" "), { stdio: "inherit", env, shell: true }) : spawn5(bin, args, { stdio: "inherit", env });
|
|
4289
4592
|
child.on("error", (err) => {
|
|
4290
4593
|
if (err.code === "ENOENT") console.log("\n" + INSTALL_HELP);
|
|
4291
4594
|
else console.error(`failed to launch Kimi CLI: ${err.message}`);
|
|
4292
|
-
|
|
4595
|
+
resolve3(127);
|
|
4293
4596
|
});
|
|
4294
|
-
child.on("exit", (code) =>
|
|
4597
|
+
child.on("exit", (code) => resolve3(code ?? 0));
|
|
4295
4598
|
});
|
|
4296
4599
|
}
|
|
4297
4600
|
function quoteWin(s) {
|
|
@@ -4312,17 +4615,26 @@ var init_kimi = __esm({
|
|
|
4312
4615
|
"gpt-5-nano": 4e5,
|
|
4313
4616
|
"gemini-2.5-flash-lite": 1048576,
|
|
4314
4617
|
"deepseek-v4-flash": 1048576,
|
|
4315
|
-
"gemini-3.
|
|
4316
|
-
"kimi-k2":
|
|
4618
|
+
"gemini-3.5-flash-lite": 1048576,
|
|
4619
|
+
"kimi-k2.5": 262144,
|
|
4620
|
+
"gemini-3.8-flash": 1048576,
|
|
4317
4621
|
"kimi-k2.7-code": 262144,
|
|
4318
4622
|
"claude-haiku-4.5": 2e5,
|
|
4319
|
-
"gemini-3.6-flash": 1048576,
|
|
4320
4623
|
"claude-sonnet-5": 1e6,
|
|
4321
|
-
"gpt-
|
|
4624
|
+
"gpt-6-sol": 105e4,
|
|
4322
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,
|
|
4323
4635
|
"claude-opus-5": 1e6,
|
|
4324
4636
|
"gpt-5.5": 105e4,
|
|
4325
|
-
"gpt-5.2-pro":
|
|
4637
|
+
"gpt-5.2-pro": 105e4
|
|
4326
4638
|
};
|
|
4327
4639
|
DEFAULT_CONTEXT_TOKENS = 256e3;
|
|
4328
4640
|
INSTALL_HELP = `Kimi CLI is not installed (or not on PATH).
|
|
@@ -4372,6 +4684,14 @@ function bannerTip(index) {
|
|
|
4372
4684
|
const i = index ?? Math.floor(Math.random() * BANNER_TIPS.length);
|
|
4373
4685
|
return BANNER_TIPS[(i % BANNER_TIPS.length + BANNER_TIPS.length) % BANNER_TIPS.length];
|
|
4374
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
|
+
}
|
|
4375
4695
|
function earnedLabel(opts) {
|
|
4376
4696
|
return `$${opts.earnedUsdToday.toFixed(2)}/$${opts.maxRewardUsd.toFixed(2)} today`;
|
|
4377
4697
|
}
|
|
@@ -4381,7 +4701,7 @@ function welcomeBanner(opts) {
|
|
|
4381
4701
|
const rightW = Math.min(RIGHT_MAX, Math.max(26, inner - 30));
|
|
4382
4702
|
const leftW = inner - rightW;
|
|
4383
4703
|
const cwd = opts.cwd.replace(homedir6(), "~").replace(/\\/g, "/");
|
|
4384
|
-
const welcome = opts.
|
|
4704
|
+
const welcome = greeting(opts, Math.max(1, Math.min(18, leftW - (opts.newAccount ? 20 : 15))));
|
|
4385
4705
|
const duck = duckLines();
|
|
4386
4706
|
const home = truncate(cwd, leftW);
|
|
4387
4707
|
const hint = bannerTip(opts.tipIndex);
|
|
@@ -4407,8 +4727,10 @@ function welcomeBanner(opts) {
|
|
|
4407
4727
|
tip("/wallet", "balance & ads"),
|
|
4408
4728
|
cell("", 0),
|
|
4409
4729
|
cell(BOLD + MANGO + "Wallet" + R2, 6),
|
|
4410
|
-
cell(TEXT +
|
|
4411
|
-
|
|
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)
|
|
4412
4734
|
];
|
|
4413
4735
|
const rows = Math.max(left.length, right.length);
|
|
4414
4736
|
const out = [];
|
|
@@ -4426,9 +4748,9 @@ function welcomeBanner(opts) {
|
|
|
4426
4748
|
}
|
|
4427
4749
|
function compactBanner(opts) {
|
|
4428
4750
|
const width = Math.max(1, opts.width ?? MIN_BANNER_WIDTH);
|
|
4429
|
-
const welcome = truncate(opts
|
|
4751
|
+
const welcome = truncate(greeting(opts), Math.max(1, width - 2));
|
|
4430
4752
|
const hint = bannerTip(opts.tipIndex);
|
|
4431
|
-
const balance =
|
|
4753
|
+
const balance = balanceLabel(opts);
|
|
4432
4754
|
const room = width - (4 + stringWidth(hint.key) + 1 + 4 + stringWidth(balance));
|
|
4433
4755
|
const desc = room >= 3 ? truncate(hint.desc, room) : "";
|
|
4434
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);
|
|
@@ -4562,6 +4884,34 @@ function readVersion() {
|
|
|
4562
4884
|
return "unknown";
|
|
4563
4885
|
}
|
|
4564
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
|
+
}
|
|
4565
4915
|
var VERSION, VERSION_LABEL;
|
|
4566
4916
|
var init_version = __esm({
|
|
4567
4917
|
"src/version.ts"() {
|
|
@@ -4804,6 +5154,16 @@ function sponsorEnabled({ isTTY, env, configEnabled }) {
|
|
|
4804
5154
|
function adFitsWidth(cols) {
|
|
4805
5155
|
return cols >= AD_MIN_COLS;
|
|
4806
5156
|
}
|
|
5157
|
+
function adFitsFormat(format, cols) {
|
|
5158
|
+
return format === "sidebar_card" ? cols >= SIDEBAR_MIN_COLS : adFitsWidth(cols);
|
|
5159
|
+
}
|
|
5160
|
+
function sidebarCard(line2, cols) {
|
|
5161
|
+
if (!adFitsFormat("sidebar_card", cols)) return null;
|
|
5162
|
+
const text = line2.trim();
|
|
5163
|
+
if (!text) return null;
|
|
5164
|
+
const body = visibleLength(text) <= SIDEBAR_BODY_COLS ? text : `${sliceColumns(text, SIDEBAR_BODY_COLS - 1).trimEnd()}\u2026`;
|
|
5165
|
+
return { rule: SIDEBAR_RULE, label: `${SPONSOR_SIGIL} ${AD_LABEL}`, body };
|
|
5166
|
+
}
|
|
4807
5167
|
function sponsorRow(line2, cols, origin) {
|
|
4808
5168
|
const marker = origin === "house" || cols >= AD_FULL_PREFIX_MIN_COLS ? HOUSE_MARKER_ROW : AD_MARKER_ROW;
|
|
4809
5169
|
const label = origin === "network" ? LABEL_ROW : null;
|
|
@@ -4818,7 +5178,7 @@ function adColorEnabled({ isTTY, env }) {
|
|
|
4818
5178
|
if (env.CI !== void 0 && env.CI !== "") return false;
|
|
4819
5179
|
return true;
|
|
4820
5180
|
}
|
|
4821
|
-
var SPONSOR_SIGIL, SPONSOR_MARKER, AD_LABEL, INDENT, HOUSE_MARKER_ROW, AD_MARKER_ROW, LABEL_ROW, MIN_BODY_COLS, SHORT_AD_COLS, AD_MIN_COLS, AD_FULL_PREFIX_MIN_COLS;
|
|
5181
|
+
var SPONSOR_SIGIL, SPONSOR_MARKER, AD_LABEL, INDENT, HOUSE_MARKER_ROW, AD_MARKER_ROW, LABEL_ROW, MIN_BODY_COLS, SHORT_AD_COLS, AD_MIN_COLS, AD_FULL_PREFIX_MIN_COLS, SIDEBAR_BODY_COLS, SIDEBAR_RULE, SIDEBAR_CARD_COLS, SIDEBAR_MIN_COLS;
|
|
4822
5182
|
var init_render = __esm({
|
|
4823
5183
|
"src/sponsor/render.ts"() {
|
|
4824
5184
|
"use strict";
|
|
@@ -4835,6 +5195,10 @@ var init_render = __esm({
|
|
|
4835
5195
|
SHORT_AD_COLS = 29;
|
|
4836
5196
|
AD_MIN_COLS = visibleLength(AD_MARKER_ROW) + visibleLength(LABEL_ROW) + SHORT_AD_COLS;
|
|
4837
5197
|
AD_FULL_PREFIX_MIN_COLS = visibleLength(HOUSE_MARKER_ROW) + visibleLength(LABEL_ROW) + SHORT_AD_COLS;
|
|
5198
|
+
SIDEBAR_BODY_COLS = SHORT_AD_COLS + 9;
|
|
5199
|
+
SIDEBAR_RULE = "\u2502 ";
|
|
5200
|
+
SIDEBAR_CARD_COLS = visibleLength(SIDEBAR_RULE) + SIDEBAR_BODY_COLS;
|
|
5201
|
+
SIDEBAR_MIN_COLS = 50 + SIDEBAR_CARD_COLS;
|
|
4838
5202
|
}
|
|
4839
5203
|
});
|
|
4840
5204
|
|
|
@@ -4845,7 +5209,7 @@ function sponsorLineText(ad) {
|
|
|
4845
5209
|
if (!text) return void 0;
|
|
4846
5210
|
return text.length > MAX_TEXT_CHARS3 ? text.slice(0, MAX_TEXT_CHARS3).trimEnd() : text;
|
|
4847
5211
|
}
|
|
4848
|
-
function createSponsorSlot(config) {
|
|
5212
|
+
function createSponsorSlot(config, format = "top_bar") {
|
|
4849
5213
|
const slot = new SponsorSlot({
|
|
4850
5214
|
publisherId: config.publisherId,
|
|
4851
5215
|
serveOnly: true,
|
|
@@ -4868,7 +5232,7 @@ function createSponsorSlot(config) {
|
|
|
4868
5232
|
});
|
|
4869
5233
|
return {
|
|
4870
5234
|
async serve(cols, signal) {
|
|
4871
|
-
if (!
|
|
5235
|
+
if (!adFitsFormat(format, cols)) return null;
|
|
4872
5236
|
const ad = await slot.next({
|
|
4873
5237
|
...config.subject ? { subject: config.subject } : {},
|
|
4874
5238
|
...isCategory(config.category) ? { category: config.category } : {}
|
|
@@ -4900,9 +5264,11 @@ var init_network = __esm({
|
|
|
4900
5264
|
function commandLabel(c2) {
|
|
4901
5265
|
return `/${c2.name}${c2.args ? ` ${c2.args}` : ""}`;
|
|
4902
5266
|
}
|
|
4903
|
-
function helpText() {
|
|
4904
|
-
const w = Math.max(...COMMANDS.map((c2) => commandLabel(c2).length));
|
|
4905
|
-
return
|
|
5267
|
+
function helpText(free) {
|
|
5268
|
+
const w = Math.max(...COMMANDS.map((c2) => commandLabel(c2).length), free ? free.label.length : 0);
|
|
5269
|
+
return (free ? ` ${free.label.padEnd(w + 2)}${free.desc}
|
|
5270
|
+
|
|
5271
|
+
` : "") + COMMANDS.map((c2) => ` ${commandLabel(c2).padEnd(w + 2)}${c2.desc}`).join("\n") + "\n\n" + [
|
|
4906
5272
|
" @path attach a file to the message (tab completes)",
|
|
4907
5273
|
" !command run a shell command yourself, no model call",
|
|
4908
5274
|
" shift+tab cycle permission mode \xB7 esc stop the current turn",
|
|
@@ -5387,7 +5753,7 @@ var init_views = __esm({
|
|
|
5387
5753
|
// src/tui/app.tsx
|
|
5388
5754
|
import { useCallback, useEffect, useMemo, useRef, useState, useSyncExternalStore } from "react";
|
|
5389
5755
|
import { Box as Box2, Static, Text as Text2, useApp, useInput, usePaste, useStdout } from "ink";
|
|
5390
|
-
import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
5756
|
+
import { Fragment, jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
5391
5757
|
function buyDeclarationLines(pack) {
|
|
5392
5758
|
return [
|
|
5393
5759
|
`${pack.label} \u2014 ${pack.credits.toLocaleString("en-US")} credits for $${pack.price_usd.toFixed(2)}`,
|
|
@@ -5430,7 +5796,19 @@ function useTerminalSize() {
|
|
|
5430
5796
|
}, [stdout]);
|
|
5431
5797
|
return useSyncExternalStore(subscribe, getSnapshot, getSnapshot);
|
|
5432
5798
|
}
|
|
5433
|
-
function App({
|
|
5799
|
+
function App({
|
|
5800
|
+
client,
|
|
5801
|
+
config,
|
|
5802
|
+
wallet,
|
|
5803
|
+
streak,
|
|
5804
|
+
session,
|
|
5805
|
+
newAccount,
|
|
5806
|
+
initialTask,
|
|
5807
|
+
notices: notices2,
|
|
5808
|
+
sponsorServe,
|
|
5809
|
+
sponsorFormats,
|
|
5810
|
+
initHint
|
|
5811
|
+
}) {
|
|
5434
5812
|
const { exit } = useApp();
|
|
5435
5813
|
const { stdout, write: writeToStdout } = useStdout();
|
|
5436
5814
|
const idRef = useRef(1);
|
|
@@ -5447,8 +5825,11 @@ function App({ client, config, wallet, streak, session, initialTask, notices: no
|
|
|
5447
5825
|
text: (cols >= MIN_BANNER_WIDTH ? welcomeBanner : compactBanner)({
|
|
5448
5826
|
width: cols,
|
|
5449
5827
|
name: config.login ?? config.email?.split("@")[0],
|
|
5828
|
+
...newAccount ? { newAccount: true } : {},
|
|
5450
5829
|
model: config.model,
|
|
5451
|
-
|
|
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,
|
|
5452
5833
|
adsToday: wallet?.ads_today ?? 0,
|
|
5453
5834
|
earnedUsdToday: wallet?.earned_usd_today ?? 0,
|
|
5454
5835
|
maxRewardUsd: wallet?.max_reward_usd_per_day ?? 0,
|
|
@@ -5462,11 +5843,16 @@ function App({ client, config, wallet, streak, session, initialTask, notices: no
|
|
|
5462
5843
|
const [editor, setEditor] = useState(EMPTY);
|
|
5463
5844
|
const [busy, setBusy] = useState(false);
|
|
5464
5845
|
const [live, setLive] = useState(null);
|
|
5465
|
-
const [balance, setBalance] = useState(wallet?.balance
|
|
5846
|
+
const [balance, setBalance] = useState(wallet?.balance);
|
|
5466
5847
|
const [spent, setSpent] = useState(0);
|
|
5467
5848
|
const [freeTier, setFreeTier] = useState(wallet?.free_tier);
|
|
5468
5849
|
const [model, setModel2] = useState(config.model);
|
|
5850
|
+
const modelRef = useRef(config.model);
|
|
5469
5851
|
const [mode, setMode] = useState("normal");
|
|
5852
|
+
const switchModel = useCallback((id) => {
|
|
5853
|
+
modelRef.current = id;
|
|
5854
|
+
setModel2(id);
|
|
5855
|
+
}, []);
|
|
5470
5856
|
const [hist, setHist] = useState(() => loadHistory());
|
|
5471
5857
|
const [histIdx, setHistIdx] = useState(-1);
|
|
5472
5858
|
const [sel, setSel] = useState(0);
|
|
@@ -5480,7 +5866,12 @@ function App({ client, config, wallet, streak, session, initialTask, notices: no
|
|
|
5480
5866
|
const [startedAt, setStartedAt] = useState(0);
|
|
5481
5867
|
const [quitHint, setQuitHint] = useState(false);
|
|
5482
5868
|
const [busyLabel, setBusyLabel] = useState(WORKING);
|
|
5483
|
-
const [sponsor, setSponsor] = useState(
|
|
5869
|
+
const [sponsor, setSponsor] = useState(
|
|
5870
|
+
null
|
|
5871
|
+
);
|
|
5872
|
+
const [adSlots, setAdSlots] = useState(
|
|
5873
|
+
{}
|
|
5874
|
+
);
|
|
5484
5875
|
const messagesRef = useRef(messages);
|
|
5485
5876
|
messagesRef.current = messages;
|
|
5486
5877
|
const busyRef = useRef(false);
|
|
@@ -5506,10 +5897,16 @@ function App({ client, config, wallet, streak, session, initialTask, notices: no
|
|
|
5506
5897
|
const spinnerRef = useRef(spinnerFrames());
|
|
5507
5898
|
const runningToolRef = useRef(null);
|
|
5508
5899
|
const pendingTaskRef = useRef(null);
|
|
5900
|
+
const pendingFreeRef = useRef([]);
|
|
5901
|
+
const pendingGrantRef = useRef(0);
|
|
5902
|
+
const pendingBalanceRef = useRef(void 0);
|
|
5509
5903
|
const mcpRef = useRef(McpHub.empty());
|
|
5510
5904
|
const todosRef = useRef(new TodoList());
|
|
5511
5905
|
const lastTurnRef = useRef(null);
|
|
5512
5906
|
const freeSpentNoticeRef = useRef(false);
|
|
5907
|
+
const emptyNoticeRef = useRef(false);
|
|
5908
|
+
const initHintedRef = useRef(false);
|
|
5909
|
+
const sessionTurnsRef = useRef(0);
|
|
5513
5910
|
const turnCountRef = useRef(config.turns ?? 0);
|
|
5514
5911
|
const deltaBufRef = useRef("");
|
|
5515
5912
|
const deltaTimerRef = useRef(null);
|
|
@@ -5517,36 +5914,54 @@ function App({ client, config, wallet, streak, session, initialTask, notices: no
|
|
|
5517
5914
|
const sponsorIdxRef = useRef(0);
|
|
5518
5915
|
const sponsorPrevRef = useRef(void 0);
|
|
5519
5916
|
const sponsorCallRef = useRef(0);
|
|
5520
|
-
const pendingImpressionRef = useRef(
|
|
5521
|
-
const
|
|
5917
|
+
const pendingImpressionRef = useRef(/* @__PURE__ */ new Map());
|
|
5918
|
+
const drawnRef = useRef(/* @__PURE__ */ new Set());
|
|
5919
|
+
const sponsorSlotRef = useRef(
|
|
5920
|
+
/* @__PURE__ */ new Map()
|
|
5921
|
+
);
|
|
5522
5922
|
const colsRef = useRef(cols);
|
|
5523
5923
|
colsRef.current = cols;
|
|
5924
|
+
const modalOpenRef = useRef(false);
|
|
5524
5925
|
const sponsorConfigRef = useRef(null);
|
|
5525
|
-
const
|
|
5526
|
-
|
|
5527
|
-
|
|
5528
|
-
if (
|
|
5529
|
-
|
|
5530
|
-
|
|
5531
|
-
|
|
5532
|
-
|
|
5533
|
-
|
|
5534
|
-
|
|
5535
|
-
|
|
5926
|
+
const serveFormat = async (format, signal) => {
|
|
5927
|
+
let served;
|
|
5928
|
+
if (sponsorServe) {
|
|
5929
|
+
if (format !== "top_bar" && !sponsorFormats?.includes(format)) return null;
|
|
5930
|
+
served = await sponsorServe(colsRef.current, signal, format);
|
|
5931
|
+
} else {
|
|
5932
|
+
sponsorConfigRef.current ??= client.sponsorConfig(signal);
|
|
5933
|
+
const conf = await sponsorConfigRef.current;
|
|
5934
|
+
if (!conf) return null;
|
|
5935
|
+
const publisherId = format === "top_bar" ? conf.publisherId : conf.formats?.[format];
|
|
5936
|
+
if (!publisherId) return null;
|
|
5937
|
+
let slot = sponsorSlotRef.current.get(format);
|
|
5938
|
+
if (!slot) {
|
|
5939
|
+
slot = createSponsorSlot({ ...conf, publisherId }, format);
|
|
5940
|
+
sponsorSlotRef.current.set(format, slot);
|
|
5536
5941
|
}
|
|
5537
|
-
|
|
5538
|
-
|
|
5539
|
-
|
|
5540
|
-
|
|
5541
|
-
|
|
5542
|
-
|
|
5543
|
-
|
|
5544
|
-
|
|
5545
|
-
|
|
5546
|
-
|
|
5547
|
-
|
|
5548
|
-
|
|
5549
|
-
|
|
5942
|
+
served = await slot.serve(colsRef.current, signal);
|
|
5943
|
+
}
|
|
5944
|
+
if (!served) return null;
|
|
5945
|
+
return {
|
|
5946
|
+
id: served.id,
|
|
5947
|
+
text: served.text,
|
|
5948
|
+
impression: {
|
|
5949
|
+
impressionId: served.impressionId,
|
|
5950
|
+
...format === "top_bar" ? {} : { format },
|
|
5951
|
+
adId: served.id,
|
|
5952
|
+
billable: served.billable,
|
|
5953
|
+
fromCache: served.fromCache,
|
|
5954
|
+
credit: served.credit
|
|
5955
|
+
}
|
|
5956
|
+
};
|
|
5957
|
+
};
|
|
5958
|
+
const remoteSponsorRef = useRef(
|
|
5959
|
+
Object.fromEntries(
|
|
5960
|
+
["top_bar", ...EXTRA_AD_FORMATS].map((format) => [
|
|
5961
|
+
format,
|
|
5962
|
+
adtentionSource((signal) => serveFormat(format, signal))
|
|
5963
|
+
])
|
|
5964
|
+
)
|
|
5550
5965
|
);
|
|
5551
5966
|
const tallyRef = useRef(createTally());
|
|
5552
5967
|
const heightCacheRef = useRef(/* @__PURE__ */ new Map());
|
|
@@ -5586,19 +6001,66 @@ function App({ client, config, wallet, streak, session, initialTask, notices: no
|
|
|
5586
6001
|
deltaBufRef.current = "";
|
|
5587
6002
|
}, []);
|
|
5588
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
|
+
}, []);
|
|
5589
6013
|
useEffect(() => {
|
|
5590
6014
|
client.onNotice = (text2) => push({ kind: "notice", tone: "warn", text: text2 });
|
|
5591
6015
|
for (const text2 of notices2 ?? []) push({ kind: "notice", tone: "warn", text: ` ${text2}` });
|
|
5592
6016
|
const files = contextRef.current.files;
|
|
5593
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
|
+
}
|
|
5594
6030
|
const streakLine = streak && streak.claimed ? streakLabel(streak) : null;
|
|
5595
6031
|
if (streakLine) push({ kind: "notice", tone: "good", text: ` ${streakLine}` });
|
|
6032
|
+
const freeLine = freeTierNotice({ model: config.model, balance: wallet?.balance ?? 0, free: wallet?.free_tier });
|
|
6033
|
+
if (freeLine) {
|
|
6034
|
+
push({ kind: "notice", tone: (wallet?.balance ?? 0) <= 0 ? "warn" : "good", text: ` ${freeLine}` });
|
|
6035
|
+
}
|
|
5596
6036
|
if (session?.messages.length) {
|
|
5597
6037
|
push({ kind: "notice", text: ` resumed session ${session.id} (${session.messages.length} messages)` });
|
|
5598
6038
|
}
|
|
6039
|
+
if (initHint?.returning) {
|
|
6040
|
+
initHintedRef.current = true;
|
|
6041
|
+
push({ kind: "notice", text: ` ${INIT_HINT}` });
|
|
6042
|
+
}
|
|
5599
6043
|
client.models().then((m) => {
|
|
5600
6044
|
catalogRef.current = m;
|
|
5601
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
|
+
}
|
|
5602
6064
|
}).catch(() => void 0);
|
|
5603
6065
|
}, []);
|
|
5604
6066
|
useEffect(() => {
|
|
@@ -5645,6 +6107,7 @@ function App({ client, config, wallet, streak, session, initialTask, notices: no
|
|
|
5645
6107
|
configEnabled: config.sponsor
|
|
5646
6108
|
})) {
|
|
5647
6109
|
setSponsor(null);
|
|
6110
|
+
setAdSlots({});
|
|
5648
6111
|
return;
|
|
5649
6112
|
}
|
|
5650
6113
|
const est = catalogRef.current.find((m) => m.id === modelId)?.est_ads_per_task;
|
|
@@ -5660,31 +6123,47 @@ function App({ client, config, wallet, streak, session, initialTask, notices: no
|
|
|
5660
6123
|
} else {
|
|
5661
6124
|
setSponsor(null);
|
|
5662
6125
|
}
|
|
5663
|
-
|
|
5664
|
-
|
|
5665
|
-
|
|
5666
|
-
|
|
5667
|
-
|
|
5668
|
-
|
|
5669
|
-
|
|
5670
|
-
|
|
5671
|
-
|
|
6126
|
+
for (const format of ["top_bar", ...EXTRA_AD_FORMATS]) {
|
|
6127
|
+
if (!adFitsFormat(format, colsRef.current)) continue;
|
|
6128
|
+
void remoteSponsorRef.current[format].fetch().then((line2) => {
|
|
6129
|
+
if (!line2 || call !== sponsorCallRef.current) return;
|
|
6130
|
+
if (!adFitsFormat(format, colsRef.current)) return;
|
|
6131
|
+
if (format !== "top_bar" && modalOpenRef.current) return;
|
|
6132
|
+
if (format === "top_bar") {
|
|
6133
|
+
if (!sponsorRow(line2.text, colsRef.current, "network")) return;
|
|
6134
|
+
sponsorPrevRef.current = line2.id;
|
|
6135
|
+
setSponsor({ text: line2.text, origin: "network", impressionId: line2.impression.impressionId });
|
|
6136
|
+
} else {
|
|
6137
|
+
setAdSlots((slots) => ({
|
|
6138
|
+
...slots,
|
|
6139
|
+
[format]: { text: line2.text, impressionId: line2.impression.impressionId }
|
|
6140
|
+
}));
|
|
6141
|
+
}
|
|
6142
|
+
recordImpression(tallyRef.current, line2.id);
|
|
6143
|
+
pendingImpressionRef.current.set(format, { call, impression: line2.impression });
|
|
6144
|
+
});
|
|
6145
|
+
}
|
|
5672
6146
|
},
|
|
5673
6147
|
[config.sponsor, stdout, wallet]
|
|
5674
6148
|
);
|
|
5675
6149
|
const reportSponsor = useCallback(() => {
|
|
5676
|
-
const pending = pendingImpressionRef.current;
|
|
5677
|
-
|
|
5678
|
-
|
|
5679
|
-
|
|
6150
|
+
const pending = [...pendingImpressionRef.current.values()];
|
|
6151
|
+
const drawn = drawnRef.current;
|
|
6152
|
+
pendingImpressionRef.current = /* @__PURE__ */ new Map();
|
|
6153
|
+
drawnRef.current = /* @__PURE__ */ new Set();
|
|
6154
|
+
const reports = pending.filter((p) => p.call === sponsorCallRef.current).filter((p) => drawn.has(p.impression.impressionId)).map((p) => p.impression);
|
|
6155
|
+
if (reports.length === 0) return;
|
|
6156
|
+
void (async () => {
|
|
6157
|
+
for (const report of reports) await client.reportImpression(report);
|
|
6158
|
+
})();
|
|
5680
6159
|
}, [client]);
|
|
5681
6160
|
const askUser = useCallback(
|
|
5682
|
-
(req) => new Promise((
|
|
6161
|
+
(req) => new Promise((resolve3) => setAsk({ req, resolve: resolve3 })),
|
|
5683
6162
|
[]
|
|
5684
6163
|
);
|
|
5685
6164
|
const permit = useMemo(() => createPermit(() => permRef.current, askUser), [askUser]);
|
|
5686
6165
|
const approvePlan = useCallback(
|
|
5687
|
-
(proposed) => new Promise((
|
|
6166
|
+
(proposed) => new Promise((resolve3) => {
|
|
5688
6167
|
setPlan({
|
|
5689
6168
|
plan: proposed,
|
|
5690
6169
|
resolve: (decision) => {
|
|
@@ -5692,7 +6171,7 @@ function App({ client, config, wallet, streak, session, initialTask, notices: no
|
|
|
5692
6171
|
permRef.current.mode = decision;
|
|
5693
6172
|
setMode(decision);
|
|
5694
6173
|
}
|
|
5695
|
-
|
|
6174
|
+
resolve3(decision);
|
|
5696
6175
|
}
|
|
5697
6176
|
});
|
|
5698
6177
|
}),
|
|
@@ -5704,7 +6183,7 @@ function App({ client, config, wallet, streak, session, initialTask, notices: no
|
|
|
5704
6183
|
setMode(next);
|
|
5705
6184
|
}, []);
|
|
5706
6185
|
const askQuestion = useCallback(
|
|
5707
|
-
(req) => new Promise((
|
|
6186
|
+
(req) => new Promise((resolve3) => {
|
|
5708
6187
|
setPickerSel(0);
|
|
5709
6188
|
setPicker({
|
|
5710
6189
|
title: req.question,
|
|
@@ -5716,10 +6195,10 @@ function App({ client, config, wallet, streak, session, initialTask, notices: no
|
|
|
5716
6195
|
})),
|
|
5717
6196
|
onPick: (choice) => {
|
|
5718
6197
|
push({ kind: "notice", tone: "good", text: ` answered: ${choice.value}` });
|
|
5719
|
-
|
|
6198
|
+
resolve3(choice.value);
|
|
5720
6199
|
},
|
|
5721
6200
|
onCancel: () => {
|
|
5722
|
-
|
|
6201
|
+
resolve3("nothing \u2014 the user dismissed the question. Do not ask it again.");
|
|
5723
6202
|
stopCurrent();
|
|
5724
6203
|
}
|
|
5725
6204
|
});
|
|
@@ -5798,17 +6277,19 @@ function App({ client, config, wallet, streak, session, initialTask, notices: no
|
|
|
5798
6277
|
);
|
|
5799
6278
|
const runTurn2 = useCallback(
|
|
5800
6279
|
async (task, images = []) => {
|
|
6280
|
+
pendingFreeRef.current = [];
|
|
6281
|
+
const model2 = modelRef.current;
|
|
5801
6282
|
const ac = new AbortController();
|
|
5802
6283
|
abortRef.current = ac;
|
|
5803
6284
|
setBusy(true);
|
|
5804
6285
|
setStartedAt(Date.now());
|
|
5805
6286
|
setLive({ text: "" });
|
|
5806
|
-
nextSponsor(
|
|
6287
|
+
nextSponsor(model2);
|
|
5807
6288
|
let history = messagesRef.current;
|
|
5808
6289
|
try {
|
|
5809
|
-
const window = contextWindow(
|
|
6290
|
+
const window = contextWindow(model2);
|
|
5810
6291
|
if (shouldCompact([{ role: "system", content: contextRef.current.systemPrompt }, ...history], window)) {
|
|
5811
|
-
const res = await compact(client,
|
|
6292
|
+
const res = await compact(client, model2, [
|
|
5812
6293
|
{ role: "system", content: contextRef.current.systemPrompt },
|
|
5813
6294
|
...history
|
|
5814
6295
|
], { signal: ac.signal });
|
|
@@ -5819,12 +6300,12 @@ function App({ client, config, wallet, streak, session, initialTask, notices: no
|
|
|
5819
6300
|
setSpent((s) => s + res.creditsCharged);
|
|
5820
6301
|
push({
|
|
5821
6302
|
kind: "notice",
|
|
5822
|
-
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)`
|
|
5823
6304
|
});
|
|
5824
6305
|
}
|
|
5825
6306
|
}
|
|
5826
6307
|
const result = await runAgent(client, task, {
|
|
5827
|
-
model,
|
|
6308
|
+
model: model2,
|
|
5828
6309
|
root,
|
|
5829
6310
|
history,
|
|
5830
6311
|
attachments: images,
|
|
@@ -5866,7 +6347,7 @@ ${NO_SEARCH_INSTRUCTION}`),
|
|
|
5866
6347
|
started: sessionRef.current.started,
|
|
5867
6348
|
updated: (/* @__PURE__ */ new Date()).toISOString(),
|
|
5868
6349
|
cwd: root,
|
|
5869
|
-
model,
|
|
6350
|
+
model: model2,
|
|
5870
6351
|
title: sessionRef.current.title,
|
|
5871
6352
|
messages: next
|
|
5872
6353
|
});
|
|
@@ -5882,8 +6363,10 @@ ${NO_SEARCH_INSTRUCTION}`),
|
|
|
5882
6363
|
text: result.content,
|
|
5883
6364
|
meta: turnMeta(result)
|
|
5884
6365
|
});
|
|
5885
|
-
|
|
6366
|
+
let spentNotice = false;
|
|
6367
|
+
if (freeTier?.models.includes(model2) && result.creditsWaived === 0 && result.creditsCharged > 0 && (result.freeCreditsLeft ?? freeTier.credits_left) === 0 && !freeSpentNoticeRef.current) {
|
|
5886
6368
|
freeSpentNoticeRef.current = true;
|
|
6369
|
+
spentNotice = true;
|
|
5887
6370
|
push({
|
|
5888
6371
|
kind: "notice",
|
|
5889
6372
|
tone: "warn",
|
|
@@ -5891,9 +6374,18 @@ ${NO_SEARCH_INSTRUCTION}`),
|
|
|
5891
6374
|
The free allowance resets at midnight UTC. /earn tops up before then.`
|
|
5892
6375
|
});
|
|
5893
6376
|
}
|
|
6377
|
+
if (result.creditsCharged > 0 && result.balance <= 0 && !spentNotice && !emptyNoticeRef.current) {
|
|
6378
|
+
emptyNoticeRef.current = true;
|
|
6379
|
+
const on = model2;
|
|
6380
|
+
void client.wallet().then((w) => {
|
|
6381
|
+
setFreeTier(w.free_tier);
|
|
6382
|
+
const line2 = freeTierNotice({ model: on, balance: w.balance, free: w.free_tier });
|
|
6383
|
+
if (line2) push({ kind: "notice", tone: "warn", text: ` ${line2}` });
|
|
6384
|
+
}).catch(() => void 0);
|
|
6385
|
+
}
|
|
5894
6386
|
lastTurnRef.current = {
|
|
5895
6387
|
turn: turnCountRef.current,
|
|
5896
|
-
model,
|
|
6388
|
+
model: model2,
|
|
5897
6389
|
prompt: task,
|
|
5898
6390
|
response: result.content,
|
|
5899
6391
|
steps: result.steps,
|
|
@@ -5906,18 +6398,40 @@ ${NO_SEARCH_INSTRUCTION}`),
|
|
|
5906
6398
|
turnCountRef.current += 1;
|
|
5907
6399
|
config.turns = turnCountRef.current;
|
|
5908
6400
|
saveConfig(config);
|
|
6401
|
+
sessionTurnsRef.current += 1;
|
|
6402
|
+
if (initHint && !initHintedRef.current && sessionTurnsRef.current >= INIT_HINT_AFTER_TURNS && shouldSuggestInit(root)) {
|
|
6403
|
+
initHintedRef.current = true;
|
|
6404
|
+
push({ kind: "notice", text: ` ${INIT_HINT}` });
|
|
6405
|
+
}
|
|
5909
6406
|
}
|
|
5910
6407
|
} catch (err) {
|
|
5911
6408
|
if (err instanceof PaywallError) {
|
|
5912
6409
|
pendingTaskRef.current = { task, images };
|
|
6410
|
+
pendingFreeRef.current = err.freeModels;
|
|
6411
|
+
pendingGrantRef.current = err.grantPerAd;
|
|
6412
|
+
pendingBalanceRef.current = err.balance;
|
|
6413
|
+
const free = err.freeModels[0];
|
|
5913
6414
|
push({
|
|
5914
6415
|
kind: "notice",
|
|
5915
6416
|
tone: "warn",
|
|
5916
|
-
text: `
|
|
6417
|
+
text: free ? ` ${shortfallHeadline(err, model2)}
|
|
6418
|
+
Press enter \u2014 ${free.id} is still free today (~${free.turnsLeft} turn${free.turnsLeft === 1 ? "" : "s"} left), or take an offer instead.
|
|
6419
|
+
Your request is kept either way, so you will not have to type it again.
|
|
6420
|
+
Offers you are screened out of pay nothing; that is normal, just start another.
|
|
6421
|
+
/buy is there too, if you would rather pay than do an offer.` : ` ${shortfallHeadline(err, model2)}
|
|
5917
6422
|
Press enter to open the ad wall \u2014 offers pay ${grantLabel(err.grantPerAd)}.
|
|
5918
6423
|
I'll continue automatically. Offers you are screened out of pay nothing;
|
|
5919
6424
|
that is normal, just start another.
|
|
5920
|
-
|
|
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.`
|
|
5921
6435
|
});
|
|
5922
6436
|
} else if (err instanceof AuthError) {
|
|
5923
6437
|
push({ kind: "notice", tone: "error", text: NOT_SIGNED_IN });
|
|
@@ -5932,6 +6446,7 @@ ${NO_SEARCH_INSTRUCTION}`),
|
|
|
5932
6446
|
reportSponsor();
|
|
5933
6447
|
sponsorCallRef.current++;
|
|
5934
6448
|
setSponsor(null);
|
|
6449
|
+
setAdSlots({});
|
|
5935
6450
|
}
|
|
5936
6451
|
},
|
|
5937
6452
|
[
|
|
@@ -6002,10 +6517,21 @@ ${NO_SEARCH_INSTRUCTION}`),
|
|
|
6002
6517
|
tone: "good",
|
|
6003
6518
|
text: ` signed in as ${account.login ?? account.email ?? "you"} \xB7 balance ${account.balance.toLocaleString("en-US")} credits` + (account.created ? " (signup bonus)" : "")
|
|
6004
6519
|
});
|
|
6520
|
+
if (account.model) {
|
|
6521
|
+
switchModel(account.model);
|
|
6522
|
+
push({ kind: "notice", text: ` model \u2192 ${account.model} (the default for new accounts)` });
|
|
6523
|
+
}
|
|
6005
6524
|
const claimed = await client.claimStreak(signal);
|
|
6006
6525
|
const line2 = claimed?.claimed ? streakLabel(claimed) : null;
|
|
6007
6526
|
if (claimed?.claimed) setBalance(claimed.balance);
|
|
6008
6527
|
if (line2) push({ kind: "notice", tone: "good", text: ` ${line2}` });
|
|
6528
|
+
const w = await client.wallet(signal).catch(() => void 0);
|
|
6529
|
+
if (w) {
|
|
6530
|
+
setBalance(w.balance);
|
|
6531
|
+
setFreeTier(w.free_tier);
|
|
6532
|
+
const freeLine = freeTierNotice({ model: modelRef.current, balance: w.balance, free: w.free_tier });
|
|
6533
|
+
if (freeLine) push({ kind: "notice", tone: w.balance <= 0 ? "warn" : "good", text: ` ${freeLine}` });
|
|
6534
|
+
}
|
|
6009
6535
|
return;
|
|
6010
6536
|
}
|
|
6011
6537
|
case "closed":
|
|
@@ -6032,7 +6558,7 @@ ${NO_SEARCH_INSTRUCTION}`),
|
|
|
6032
6558
|
const runAdWall = useCallback(async () => {
|
|
6033
6559
|
const pending = pendingTaskRef.current;
|
|
6034
6560
|
pendingTaskRef.current = null;
|
|
6035
|
-
|
|
6561
|
+
let before = balance ?? (await client.wallet().catch(() => void 0))?.balance;
|
|
6036
6562
|
const code = await client.handoffCode();
|
|
6037
6563
|
const base = config.dashboardUrl.replace(/\/+$/, "");
|
|
6038
6564
|
openBrowser(code ? `${base}/?c=${encodeURIComponent(code)}` : base);
|
|
@@ -6050,7 +6576,10 @@ ${NO_SEARCH_INSTRUCTION}`),
|
|
|
6050
6576
|
await sleep4(3e3, signal);
|
|
6051
6577
|
if (signal.aborted) break;
|
|
6052
6578
|
const w = await client.wallet(signal).catch(() => void 0);
|
|
6053
|
-
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) {
|
|
6054
6583
|
setBalance(w.balance);
|
|
6055
6584
|
credited = true;
|
|
6056
6585
|
}
|
|
@@ -6073,6 +6602,46 @@ ${NO_SEARCH_INSTRUCTION}`),
|
|
|
6073
6602
|
push({ kind: "notice", tone: "good", text: " credits added \u2014 continuing" });
|
|
6074
6603
|
if (pending) await runTurn2(pending.task, pending.images);
|
|
6075
6604
|
}, [balance, client, config.dashboardUrl, push, runBusy, runTurn2]);
|
|
6605
|
+
const runPaywallChoice = useCallback(() => {
|
|
6606
|
+
const free = pendingFreeRef.current.filter((m) => m.id !== EARN_ROW);
|
|
6607
|
+
if (free.length === 0) return void runAdWall();
|
|
6608
|
+
const items = [
|
|
6609
|
+
...free.map((m) => ({
|
|
6610
|
+
value: m.id,
|
|
6611
|
+
label: m.id,
|
|
6612
|
+
hint: `free: ~${m.turnsLeft} turn${m.turnsLeft === 1 ? "" : "s"} left today`,
|
|
6613
|
+
current: m.id === model
|
|
6614
|
+
})),
|
|
6615
|
+
{
|
|
6616
|
+
value: EARN_ROW,
|
|
6617
|
+
label: "earn credits instead",
|
|
6618
|
+
// The wall keeps its figure here: this row is the one that costs time,
|
|
6619
|
+
// and what it pays is the only thing that makes the time worth it.
|
|
6620
|
+
hint: `opens the ad wall \u2014 offers pay ${grantLabel(pendingGrantRef.current)}`
|
|
6621
|
+
}
|
|
6622
|
+
];
|
|
6623
|
+
setPickerSel(0);
|
|
6624
|
+
setPicker({
|
|
6625
|
+
title: (pendingBalanceRef.current ?? 0) > 0 ? "Not enough credits" : "Out of credits",
|
|
6626
|
+
subtitle: "Pick a free model to carry on with, or earn credits at the wall.",
|
|
6627
|
+
items,
|
|
6628
|
+
onPick: (choice) => {
|
|
6629
|
+
if (choice.value === EARN_ROW) return void runAdWall();
|
|
6630
|
+
const pending = pendingTaskRef.current;
|
|
6631
|
+
pendingTaskRef.current = null;
|
|
6632
|
+
pendingFreeRef.current = [];
|
|
6633
|
+
config.model = choice.value;
|
|
6634
|
+
saveConfig(config);
|
|
6635
|
+
switchModel(choice.value);
|
|
6636
|
+
push({
|
|
6637
|
+
kind: "notice",
|
|
6638
|
+
tone: "good",
|
|
6639
|
+
text: ` model \u2192 ${choice.value} (free today) \u2014 continuing where you left off`
|
|
6640
|
+
});
|
|
6641
|
+
if (pending) void runTurn2(pending.task, pending.images);
|
|
6642
|
+
}
|
|
6643
|
+
});
|
|
6644
|
+
}, [config, model, push, runAdWall, runTurn2, switchModel]);
|
|
6076
6645
|
const runBuy = useCallback(async () => {
|
|
6077
6646
|
await runBusy(LOADING, async (signal) => {
|
|
6078
6647
|
const { enabled, packs } = await client.creditPacks(signal);
|
|
@@ -6104,7 +6673,7 @@ ${NO_SEARCH_INSTRUCTION}`),
|
|
|
6104
6673
|
}, [client, push, runBusy]);
|
|
6105
6674
|
const runCheckout = useCallback(
|
|
6106
6675
|
async (pack) => {
|
|
6107
|
-
|
|
6676
|
+
let before = balance ?? (await client.wallet().catch(() => void 0))?.balance;
|
|
6108
6677
|
let credited = false;
|
|
6109
6678
|
let stopped = false;
|
|
6110
6679
|
await runBusy(WAITING_FOR_PAYMENT, async (signal) => {
|
|
@@ -6128,7 +6697,10 @@ ${NO_SEARCH_INSTRUCTION}`),
|
|
|
6128
6697
|
await sleep4(3e3, signal);
|
|
6129
6698
|
if (signal.aborted) break;
|
|
6130
6699
|
const w = await client.wallet(signal).catch(() => void 0);
|
|
6131
|
-
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) {
|
|
6132
6704
|
setBalance(w.balance);
|
|
6133
6705
|
credited = true;
|
|
6134
6706
|
}
|
|
@@ -6160,7 +6732,7 @@ ${NO_SEARCH_INSTRUCTION}`),
|
|
|
6160
6732
|
exit();
|
|
6161
6733
|
return;
|
|
6162
6734
|
case "help":
|
|
6163
|
-
push({ kind: "notice", text: helpText() });
|
|
6735
|
+
push({ kind: "notice", text: helpText(freeHelpLine(freeTier)) });
|
|
6164
6736
|
return;
|
|
6165
6737
|
// Clears both halves of what "clear" means: the model's context *and*
|
|
6166
6738
|
// the screen. It used to drop only the context, so nothing visibly
|
|
@@ -6211,11 +6783,12 @@ ${NO_SEARCH_INSTRUCTION}`),
|
|
|
6211
6783
|
setSpent((s) => s + res.creditsCharged);
|
|
6212
6784
|
push({
|
|
6213
6785
|
kind: "notice",
|
|
6214
|
-
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)`
|
|
6215
6787
|
});
|
|
6216
6788
|
});
|
|
6217
6789
|
return;
|
|
6218
6790
|
case "init":
|
|
6791
|
+
initHintedRef.current = true;
|
|
6219
6792
|
await runTurn2(INIT_PROMPT);
|
|
6220
6793
|
return;
|
|
6221
6794
|
// Always the picker — an id you have to remember and type is exactly
|
|
@@ -6248,7 +6821,7 @@ ${NO_SEARCH_INSTRUCTION}`),
|
|
|
6248
6821
|
onPick: (choice) => {
|
|
6249
6822
|
config.model = choice.value;
|
|
6250
6823
|
saveConfig(config);
|
|
6251
|
-
|
|
6824
|
+
switchModel(choice.value);
|
|
6252
6825
|
push({ kind: "notice", text: ` model \u2192 ${choice.value}` });
|
|
6253
6826
|
}
|
|
6254
6827
|
});
|
|
@@ -6391,8 +6964,8 @@ ${NO_SEARCH_INSTRUCTION}`),
|
|
|
6391
6964
|
// stop" whenever it is locked.
|
|
6392
6965
|
case "refer":
|
|
6393
6966
|
await runBusy(LOADING, async (signal) => {
|
|
6394
|
-
const
|
|
6395
|
-
push({ kind: "notice", text: referLines(
|
|
6967
|
+
const info2 = await client.referral(signal);
|
|
6968
|
+
push({ kind: "notice", text: referLines(info2).map((line3) => ` ${line3}`).join("\n") });
|
|
6396
6969
|
});
|
|
6397
6970
|
return;
|
|
6398
6971
|
case "buy":
|
|
@@ -6488,7 +7061,7 @@ ${output}` }
|
|
|
6488
7061
|
lastTurnRef.current = null;
|
|
6489
7062
|
const amount = turn.free ? wallet?.rewards?.feedback_credits_free ?? wallet?.rewards?.feedback_credits : wallet?.rewards?.feedback_credits;
|
|
6490
7063
|
const pay = amount ? `Both answers pay ${amount.toLocaleString("en-US")} credits` : "Both answers pay the same";
|
|
6491
|
-
const choice = await new Promise((
|
|
7064
|
+
const choice = await new Promise((resolve3) => {
|
|
6492
7065
|
setPickerSel(0);
|
|
6493
7066
|
setPicker({
|
|
6494
7067
|
title: "Was that last answer any good?",
|
|
@@ -6498,10 +7071,10 @@ ${output}` }
|
|
|
6498
7071
|
{ value: "down", label: "no \u2014 that missed", hint: "" },
|
|
6499
7072
|
{ value: "skip", label: "skip", hint: "nothing is sent, nothing is stored" }
|
|
6500
7073
|
],
|
|
6501
|
-
onPick: (item) =>
|
|
7074
|
+
onPick: (item) => resolve3(item.value),
|
|
6502
7075
|
// Esc and "skip" are the same outcome and must stay that way: a skip
|
|
6503
7076
|
// that cost something is a prompt people stop reading.
|
|
6504
|
-
onCancel: () =>
|
|
7077
|
+
onCancel: () => resolve3(null)
|
|
6505
7078
|
});
|
|
6506
7079
|
});
|
|
6507
7080
|
if (choice !== "up" && choice !== "down") return;
|
|
@@ -6752,7 +7325,7 @@ ${dropped.map((l) => ` \xB7 ${l}`).join("\n")}`
|
|
|
6752
7325
|
if (key.return) {
|
|
6753
7326
|
if (key.meta || key.shift) return setEditor(newline(editor));
|
|
6754
7327
|
if (endsWithContinuation(editor)) return setEditor(continueLine(editor));
|
|
6755
|
-
if (!busy && pendingTaskRef.current && isEmpty(editor)) return void
|
|
7328
|
+
if (!busy && pendingTaskRef.current && isEmpty(editor)) return void runPaywallChoice();
|
|
6756
7329
|
return void submit(toText(editor));
|
|
6757
7330
|
}
|
|
6758
7331
|
if (key.ctrl && ch === "j") return setEditor(newline(editor));
|
|
@@ -6777,6 +7350,10 @@ ${dropped.map((l) => ` \xB7 ${l}`).join("\n")}`
|
|
|
6777
7350
|
const spinner = spinnerRef.current[Math.floor(tick / SPINNER_TICKS_PER_FRAME) % spinnerRef.current.length];
|
|
6778
7351
|
const sponsorLine = busy && sponsor ? sponsorRow(sponsor.text, cols, sponsor.origin) : null;
|
|
6779
7352
|
const adColor = adColorEnabled({ isTTY: stdout.isTTY, env: process.env });
|
|
7353
|
+
const modalOpen = Boolean(picker || ask2 || plan || buy);
|
|
7354
|
+
modalOpenRef.current = modalOpen;
|
|
7355
|
+
const card = busy && !modalOpen && adSlots.sidebar_card ? sidebarCard(adSlots.sidebar_card.text, cols) : null;
|
|
7356
|
+
const statusBarLine = busy && !modalOpen && adSlots.status_bar ? sponsorRow(adSlots.status_bar.text, cols, "network") : null;
|
|
6780
7357
|
const viewport = Math.max(1, rows - 1);
|
|
6781
7358
|
const inputRows = Math.max(1, Math.min(MAX_INPUT_ROWS, editor.lines.length));
|
|
6782
7359
|
const inputFrom = windowStart(editor.row, editor.lines.length, inputRows);
|
|
@@ -6798,8 +7375,18 @@ ${dropped.map((l) => ` \xB7 ${l}`).join("\n")}`
|
|
|
6798
7375
|
const showStatus = grant(2) > 0;
|
|
6799
7376
|
const queueRows = grant(queuedRows.length) > 0 ? queuedRows : [];
|
|
6800
7377
|
const showSponsor = grant(sponsorLine ? 1 : 0) > 0;
|
|
7378
|
+
const showStatusBar = grant(statusBarLine && showStatus && !quitHint && menu.length === 0 ? 1 : 0) > 0;
|
|
7379
|
+
const showCard = busyHeight > 0 && card !== null;
|
|
6801
7380
|
const inputBoxHeight = inputRows + (inputBorder ? 2 : 0);
|
|
6802
|
-
const chromeHeight = inputBoxHeight + (showStatus ? 2 : 0) + (showSponsor ? 1 : 0) + queueRows.length;
|
|
7381
|
+
const chromeHeight = inputBoxHeight + (showStatus ? 2 : 0) + (showSponsor ? 1 : 0) + (showStatusBar ? 1 : 0) + queueRows.length;
|
|
7382
|
+
const drawnKey = [
|
|
7383
|
+
showSponsor && sponsorLine?.origin === "network" ? sponsor?.impressionId : void 0,
|
|
7384
|
+
showCard ? adSlots.sidebar_card?.impressionId : void 0,
|
|
7385
|
+
showStatusBar ? adSlots.status_bar?.impressionId : void 0
|
|
7386
|
+
].filter(Boolean).join(" ");
|
|
7387
|
+
useEffect(() => {
|
|
7388
|
+
for (const id of drawnKey.split(" ")) if (id) drawnRef.current.add(id);
|
|
7389
|
+
}, [drawnKey]);
|
|
6803
7390
|
let budget = Math.max(0, viewport - chromeHeight - menu.length - busyHeight);
|
|
6804
7391
|
const pickerCompact = Boolean(picker) && budget < PICKER_CHROME + 1;
|
|
6805
7392
|
const pickerChrome = pickerCompact ? PICKER_CHROME_COMPACT : PICKER_CHROME;
|
|
@@ -6887,15 +7474,31 @@ ${dropped.map((l) => ` \xB7 ${l}`).join("\n")}`
|
|
|
6887
7474
|
Math.max(1, Math.floor(cols * 0.5)),
|
|
6888
7475
|
menu.reduce((w, c2) => Math.max(w, visibleLength(c2.label)), 0)
|
|
6889
7476
|
);
|
|
7477
|
+
const conversationUsed = useMemo(
|
|
7478
|
+
() => conversationTokens([{ role: "system", content: contextRef.current.systemPrompt }, ...messages]),
|
|
7479
|
+
[messages]
|
|
7480
|
+
);
|
|
6890
7481
|
const contextPct = useMemo(() => {
|
|
6891
7482
|
const window = contextWindow(model);
|
|
6892
7483
|
if (!window) return 0;
|
|
6893
|
-
|
|
6894
|
-
|
|
6895
|
-
|
|
6896
|
-
|
|
6897
|
-
|
|
6898
|
-
|
|
7484
|
+
return conversationUsed / window;
|
|
7485
|
+
}, [contextWindow, conversationUsed, model]);
|
|
7486
|
+
const schemaTokens = useMemo(
|
|
7487
|
+
() => estimateTokens(
|
|
7488
|
+
JSON.stringify(toolSchemaFor(mode, mcpRef.current.toolSchemas(), { webSearch: searchRef.current }))
|
|
7489
|
+
),
|
|
7490
|
+
[mode]
|
|
7491
|
+
);
|
|
7492
|
+
const typed = toText(editor);
|
|
7493
|
+
let estimate;
|
|
7494
|
+
const info = catalogRef.current.find((m) => m.id === model);
|
|
7495
|
+
const shape = client.taskShape;
|
|
7496
|
+
if (!busy && info && shape && balance !== void 0 && typed.trim() !== "" && !/^[/!]/.test(typed.trimStart())) {
|
|
7497
|
+
const range = estimateTask(info, conversationUsed + schemaTokens + estimateTokens(typed), shape);
|
|
7498
|
+
const onFree = Boolean(info.free && freeTier?.models.includes(model));
|
|
7499
|
+
const verdict = taskVerdict(range, { balance, ...onFree ? { freeLeft: freeTier.credits_left } : {} });
|
|
7500
|
+
estimate = estimateClause(range, verdict, !onFree && (freeTier?.credits_left ?? 0) > 0);
|
|
7501
|
+
}
|
|
6899
7502
|
const modeText = ` ${MODE_STYLE[mode].glyph} ${MODE_LABEL[mode]}`;
|
|
6900
7503
|
const statusText = fitRow(
|
|
6901
7504
|
` ${statusLine(
|
|
@@ -6908,12 +7511,19 @@ ${dropped.map((l) => ` \xB7 ${l}`).join("\n")}`
|
|
|
6908
7511
|
// Only while a free model is selected: on a premium one the allowance
|
|
6909
7512
|
// is real but irrelevant, and a countdown that has nothing to do with
|
|
6910
7513
|
// the next turn is a number that teaches people to stop reading the row.
|
|
6911
|
-
...freeTier?.models.includes(model) ? { freeLeft: freeTier.credits_left } : {}
|
|
7514
|
+
...freeTier?.models.includes(model) ? { freeLeft: freeTier.credits_left } : {},
|
|
7515
|
+
...estimate ? { estimate: estimate.variants } : {}
|
|
6912
7516
|
},
|
|
6913
7517
|
Math.max(1, cols - visibleLength(modeText) - 2)
|
|
6914
7518
|
)}`,
|
|
6915
7519
|
Math.max(0, cols - visibleLength(modeText))
|
|
6916
7520
|
);
|
|
7521
|
+
const statusParts = (() => {
|
|
7522
|
+
const shown = estimate?.warn ? estimate.variants.find((v) => statusText.includes(v)) : void 0;
|
|
7523
|
+
if (shown === void 0) return { before: statusText, after: "" };
|
|
7524
|
+
const at = statusText.indexOf(shown);
|
|
7525
|
+
return { before: statusText.slice(0, at), warn: shown, after: statusText.slice(at + shown.length) };
|
|
7526
|
+
})();
|
|
6917
7527
|
const hintText = quitHint ? fitRow(" press ctrl+c again to quit", cols) : ` ${pickHint(
|
|
6918
7528
|
["\u2191\u2193 choose \xB7 \u23CE run \xB7 tab complete \xB7 esc close", "\u2191\u2193 \xB7 \u23CE run \xB7 tab \xB7 esc", "\u2191\u2193 \u23CE tab esc"],
|
|
6919
7529
|
Math.max(1, cols - 2)
|
|
@@ -6923,12 +7533,47 @@ ${dropped.map((l) => ` \xB7 ${l}`).join("\n")}`
|
|
|
6923
7533
|
const room = cols - visibleLength(head);
|
|
6924
7534
|
return { key: item.value, selected: i === sel, head, hint: room > 1 ? fitRow(item.hint, room) : "" };
|
|
6925
7535
|
});
|
|
6926
|
-
const
|
|
7536
|
+
const busyCols = showCard ? cols - SIDEBAR_CARD_COLS : cols;
|
|
7537
|
+
const busyText = fitRow(
|
|
7538
|
+
`${busyLabel} ${elapsed}s \xB7 esc to stop`,
|
|
7539
|
+
Math.max(1, busyCols - visibleLength(`${spinner} `))
|
|
7540
|
+
);
|
|
6927
7541
|
return /* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", children: [
|
|
6928
7542
|
/* @__PURE__ */ jsx2(Static, { items: entries, children: (entry) => /* @__PURE__ */ jsx2(EntryView, { entry }, entry.id) }, staticEpoch),
|
|
6929
7543
|
spacer > 0 ? /* @__PURE__ */ jsx2(Box2, { height: spacer }) : null,
|
|
6930
7544
|
liveBlock ? /* @__PURE__ */ jsx2(Box2, { marginTop: 1, children: /* @__PURE__ */ jsx2(Text2, { children: liveBlock }) }) : null,
|
|
6931
|
-
busyHeight > 0
|
|
7545
|
+
busyHeight > 0 && showCard && card ? (
|
|
7546
|
+
/*
|
|
7547
|
+
The working line with the sidebar card beside it. Two columns, two
|
|
7548
|
+
rows: the working line and the blank row under it on the left, the
|
|
7549
|
+
card on the right. The blank row is the one the block below would
|
|
7550
|
+
otherwise have spent on its margin, so the frame is exactly as tall
|
|
7551
|
+
with the card as without it — the input box does not move when a card
|
|
7552
|
+
arrives or goes, and the budget above never had to count it.
|
|
7553
|
+
*/
|
|
7554
|
+
/* @__PURE__ */ jsxs2(Box2, { marginTop: 1, children: [
|
|
7555
|
+
/* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", width: busyCols, children: [
|
|
7556
|
+
/* @__PURE__ */ jsxs2(Box2, { children: [
|
|
7557
|
+
/* @__PURE__ */ jsxs2(Text2, { color: MANGO3, children: [
|
|
7558
|
+
spinner,
|
|
7559
|
+
" "
|
|
7560
|
+
] }),
|
|
7561
|
+
/* @__PURE__ */ jsx2(Text2, { dimColor: true, wrap: "truncate-end", children: busyText })
|
|
7562
|
+
] }),
|
|
7563
|
+
/* @__PURE__ */ jsx2(Text2, { children: " " })
|
|
7564
|
+
] }),
|
|
7565
|
+
/* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", width: SIDEBAR_CARD_COLS, children: [
|
|
7566
|
+
/* @__PURE__ */ jsxs2(Box2, { children: [
|
|
7567
|
+
/* @__PURE__ */ jsx2(Text2, { dimColor: true, children: card.rule }),
|
|
7568
|
+
/* @__PURE__ */ jsx2(Text2, { ...adColor ? { color: AD_MARK } : { dimColor: true }, wrap: "truncate-end", children: card.label })
|
|
7569
|
+
] }),
|
|
7570
|
+
/* @__PURE__ */ jsxs2(Box2, { children: [
|
|
7571
|
+
/* @__PURE__ */ jsx2(Text2, { dimColor: true, children: card.rule }),
|
|
7572
|
+
/* @__PURE__ */ jsx2(Text2, { ...adColor ? { color: AD_TEXT } : { dimColor: true }, wrap: "truncate-end", children: card.body })
|
|
7573
|
+
] })
|
|
7574
|
+
] })
|
|
7575
|
+
] })
|
|
7576
|
+
) : busyHeight > 0 ? /* @__PURE__ */ jsxs2(Box2, { marginTop: 1, children: [
|
|
6932
7577
|
/* @__PURE__ */ jsxs2(Text2, { color: MANGO3, children: [
|
|
6933
7578
|
spinner,
|
|
6934
7579
|
" "
|
|
@@ -6968,20 +7613,9 @@ ${dropped.map((l) => ` \xB7 ${l}`).join("\n")}`
|
|
|
6968
7613
|
pickerCompact ? null : /* @__PURE__ */ jsx2(Box2, { height: 1 }),
|
|
6969
7614
|
/* @__PURE__ */ jsx2(Text2, { dimColor: true, children: pickerHint })
|
|
6970
7615
|
] }) : null,
|
|
6971
|
-
/* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", marginTop: 1, children: [
|
|
7616
|
+
/* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", marginTop: showCard ? 0 : 1, children: [
|
|
6972
7617
|
queueRows.map((line2, i) => /* @__PURE__ */ jsx2(Text2, { dimColor: true, children: line2 }, i)),
|
|
6973
|
-
showSponsor && sponsorLine ? /* @__PURE__ */
|
|
6974
|
-
/* @__PURE__ */ jsx2(Text2, { dimColor: true, wrap: "truncate-end", children: sponsorLine.marker }),
|
|
6975
|
-
sponsorLine.label ? /* @__PURE__ */ jsx2(Text2, { ...adColor ? { color: AD_MARK } : { dimColor: true }, wrap: "truncate-end", children: sponsorLine.label }) : null,
|
|
6976
|
-
/* @__PURE__ */ jsx2(
|
|
6977
|
-
Text2,
|
|
6978
|
-
{
|
|
6979
|
-
...adColor && sponsorLine.origin === "network" ? { color: AD_TEXT } : { dimColor: true },
|
|
6980
|
-
wrap: "truncate-end",
|
|
6981
|
-
children: sponsorLine.body
|
|
6982
|
-
}
|
|
6983
|
-
)
|
|
6984
|
-
] }) : null,
|
|
7618
|
+
showSponsor && sponsorLine ? /* @__PURE__ */ jsx2(SponsorRowView, { row: sponsorLine, adColor }) : null,
|
|
6985
7619
|
inputBorder ? /* @__PURE__ */ jsxs2(Box2, { borderStyle: "round", borderColor: busy ? MANGO3 : MANGO_BRIGHT, paddingX: 1, children: [
|
|
6986
7620
|
/* @__PURE__ */ jsx2(Text2, { color: MANGO_BRIGHT, children: "\u276F " }),
|
|
6987
7621
|
/* @__PURE__ */ jsx2(Text2, { children: renderInput(editor, inputFrom, inputRows) })
|
|
@@ -6995,18 +7629,30 @@ ${dropped.map((l) => ` \xB7 ${l}`).join("\n")}`
|
|
|
6995
7629
|
] }, row.key)),
|
|
6996
7630
|
!showStatus ? null : quitHint || menu.length > 0 ? /* @__PURE__ */ jsx2(Text2, { dimColor: true, wrap: "truncate-end", children: hintText }) : /* @__PURE__ */ jsxs2(Box2, { children: [
|
|
6997
7631
|
/* @__PURE__ */ jsx2(Text2, { color: MODE_STYLE[mode].color, bold: true, wrap: "truncate-end", children: modeText }),
|
|
6998
|
-
/* @__PURE__ */ jsx2(Text2, { dimColor: true, wrap: "truncate-end", children: statusText })
|
|
6999
|
-
|
|
7632
|
+
statusParts.warn === void 0 ? /* @__PURE__ */ jsx2(Text2, { dimColor: true, wrap: "truncate-end", children: statusText }) : /* @__PURE__ */ jsxs2(Fragment, { children: [
|
|
7633
|
+
/* @__PURE__ */ jsx2(Text2, { dimColor: true, children: statusParts.before }),
|
|
7634
|
+
/* @__PURE__ */ jsx2(Text2, { color: "yellow", children: statusParts.warn }),
|
|
7635
|
+
/* @__PURE__ */ jsx2(Text2, { dimColor: true, wrap: "truncate-end", children: statusParts.after })
|
|
7636
|
+
] })
|
|
7637
|
+
] }),
|
|
7638
|
+
showStatusBar && statusBarLine ? /* @__PURE__ */ jsx2(SponsorRowView, { row: statusBarLine, adColor }) : null
|
|
7000
7639
|
] })
|
|
7001
7640
|
] });
|
|
7002
7641
|
}
|
|
7642
|
+
function SponsorRowView({ row, adColor }) {
|
|
7643
|
+
return /* @__PURE__ */ jsxs2(Box2, { children: [
|
|
7644
|
+
/* @__PURE__ */ jsx2(Text2, { dimColor: true, wrap: "truncate-end", children: row.marker }),
|
|
7645
|
+
row.label ? /* @__PURE__ */ jsx2(Text2, { ...adColor ? { color: AD_MARK } : { dimColor: true }, wrap: "truncate-end", children: row.label }) : null,
|
|
7646
|
+
/* @__PURE__ */ jsx2(Text2, { ...adColor && row.origin === "network" ? { color: AD_TEXT } : { dimColor: true }, wrap: "truncate-end", children: row.body })
|
|
7647
|
+
] });
|
|
7648
|
+
}
|
|
7003
7649
|
function sleep4(ms, signal) {
|
|
7004
|
-
return new Promise((
|
|
7005
|
-
if (signal.aborted) return
|
|
7650
|
+
return new Promise((resolve3) => {
|
|
7651
|
+
if (signal.aborted) return resolve3();
|
|
7006
7652
|
const done = () => {
|
|
7007
7653
|
clearTimeout(timer);
|
|
7008
7654
|
signal.removeEventListener("abort", done);
|
|
7009
|
-
|
|
7655
|
+
resolve3();
|
|
7010
7656
|
};
|
|
7011
7657
|
const timer = setTimeout(done, ms);
|
|
7012
7658
|
signal.addEventListener("abort", done, { once: true });
|
|
@@ -7030,14 +7676,14 @@ function renderInput(state, from, rows) {
|
|
|
7030
7676
|
function modelHint(m, free) {
|
|
7031
7677
|
const effort = adsPerTaskLabel(m.est_ads_per_task);
|
|
7032
7678
|
if (!m.free || !free?.models.includes(m.id)) return effort;
|
|
7033
|
-
const
|
|
7034
|
-
if (
|
|
7035
|
-
return `free: ~${
|
|
7679
|
+
const turns2 = free.turns_left[m.id] ?? 0;
|
|
7680
|
+
if (turns2 <= 0) return `free: none left today \xB7 ${effort}`;
|
|
7681
|
+
return `free: ~${turns2} turn${turns2 === 1 ? "" : "s"} left \xB7 ${effort}`;
|
|
7036
7682
|
}
|
|
7037
7683
|
function turnMeta(result) {
|
|
7038
7684
|
const n = (v) => v.toLocaleString("en-US");
|
|
7039
7685
|
const balance = `balance ${n(result.balance)}`;
|
|
7040
|
-
if (result.creditsWaived <= 0) return `[${result.creditsCharged} credits \xB7 ${balance}]`;
|
|
7686
|
+
if (result.creditsWaived <= 0) return `[${n(result.creditsCharged)} credits \xB7 ${balance}]`;
|
|
7041
7687
|
const left = result.freeCreditsLeft === void 0 ? "" : ` \xB7 ${n(result.freeCreditsLeft)} free credits left`;
|
|
7042
7688
|
if (result.creditsCharged > 0) {
|
|
7043
7689
|
return `[free: ${n(result.creditsWaived)} credits \xB7 then ${n(result.creditsCharged)} charged \xB7 ${balance}]`;
|
|
@@ -7045,10 +7691,23 @@ function turnMeta(result) {
|
|
|
7045
7691
|
return `[free \xB7 ${n(result.creditsWaived)} credits waived${left}]`;
|
|
7046
7692
|
}
|
|
7047
7693
|
function statusLine(o, cols) {
|
|
7694
|
+
const phrasings = o.estimate === void 0 ? [] : typeof o.estimate === "string" ? [o.estimate] : [...o.estimate];
|
|
7695
|
+
for (const estimate of [...phrasings, void 0]) {
|
|
7696
|
+
const line2 = statusRow(o, estimate, cols);
|
|
7697
|
+
if (estimate === void 0 || visibleLength(line2) <= cols) return fitRow(line2, cols);
|
|
7698
|
+
}
|
|
7699
|
+
return fitRow(statusRow(o, void 0, cols), cols);
|
|
7700
|
+
}
|
|
7701
|
+
function statusRow(o, estimate, cols) {
|
|
7048
7702
|
const burn = o.spent > 0 && o.minutes >= 1 ? `${Math.round(o.spent / o.minutes).toLocaleString("en-US")} cr/min` : void 0;
|
|
7049
7703
|
const clauses = [
|
|
7050
7704
|
{ text: o.model, rank: KEEP_ALWAYS },
|
|
7051
|
-
{
|
|
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
|
+
},
|
|
7052
7711
|
// Above the context warning, below the balance. It is the second number
|
|
7053
7712
|
// deciding whether the next turn costs anything, and unlike the spend it is
|
|
7054
7713
|
// *news* — it only exists while a free model is selected and it counts down.
|
|
@@ -7056,6 +7715,7 @@ function statusLine(o, cols) {
|
|
|
7056
7715
|
text: o.freeLeft === void 0 ? void 0 : `free ${o.freeLeft.toLocaleString("en-US")}`,
|
|
7057
7716
|
rank: 4
|
|
7058
7717
|
},
|
|
7718
|
+
{ text: estimate, rank: 5 },
|
|
7059
7719
|
{ text: o.spent > 0 ? `\u2212${o.spent.toLocaleString("en-US")}` : void 0, rank: 2 },
|
|
7060
7720
|
{ text: burn, rank: 1 },
|
|
7061
7721
|
{
|
|
@@ -7067,7 +7727,7 @@ function statusLine(o, cols) {
|
|
|
7067
7727
|
const join7 = (minRank) => clauses.filter((c2) => c2.rank >= minRank).map((c2) => c2.text).join(" \xB7 ");
|
|
7068
7728
|
let line2 = join7(0);
|
|
7069
7729
|
for (let minRank = 1; minRank <= 5 && visibleLength(line2) > cols; minRank++) line2 = join7(minRank);
|
|
7070
|
-
return
|
|
7730
|
+
return line2;
|
|
7071
7731
|
}
|
|
7072
7732
|
function windowStart(sel, total, size) {
|
|
7073
7733
|
if (total <= size) return 0;
|
|
@@ -7118,7 +7778,7 @@ function tailLines(text, max) {
|
|
|
7118
7778
|
const lines = text.split("\n");
|
|
7119
7779
|
return lines.length <= max ? text : lines.slice(-max).join("\n");
|
|
7120
7780
|
}
|
|
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;
|
|
7781
|
+
var WORKING, WAITING_FOR_REWARD, LOADING, COMPACTING, SIGNING_IN, RUNNING, SENDING_FEEDBACK, WAITING_FOR_PAYMENT, FEEDBACK_TURN_INTERVAL, NOT_SIGNED_IN, LIVE_OUTPUT_LINES, COMMITTED_OUTPUT_LINES, DELTA_FLUSH_MS, MAX_INPUT_ROWS, MAX_PICKER_ROWS, MAX_QUEUE_ROWS, QUEUE_MARKER, PICKER_CURRENT, MIN_PICKER_LABEL_COLS, PICKER_CHROME, PICKER_CHROME_COMPACT, ASK_CHROME, PLAN_CHROME, BUY_CHROME, BUY_CHROME_COMPACT, ASK_HINT, PLAN_HINT, BUY_HINT, BUY_CONFIRM_WORD, BUY_TYPED_MAX, BUY_TOO_SHORT, EXPAND_MAX_LINES, FALLBACK_ROWS, FALLBACK_COLS, INIT_HINT_AFTER_TURNS, EARN_ROW, CONTEXT_NOTICE_AT, KEEP_ALWAYS;
|
|
7122
7782
|
var init_app = __esm({
|
|
7123
7783
|
"src/tui/app.tsx"() {
|
|
7124
7784
|
"use strict";
|
|
@@ -7133,6 +7793,8 @@ var init_app = __esm({
|
|
|
7133
7793
|
init_context();
|
|
7134
7794
|
init_mentions();
|
|
7135
7795
|
init_compact();
|
|
7796
|
+
init_estimate();
|
|
7797
|
+
init_free_tier();
|
|
7136
7798
|
init_kimi();
|
|
7137
7799
|
init_browser();
|
|
7138
7800
|
init_version();
|
|
@@ -7143,6 +7805,7 @@ var init_app = __esm({
|
|
|
7143
7805
|
init_counter();
|
|
7144
7806
|
init_provider();
|
|
7145
7807
|
init_network();
|
|
7808
|
+
init_formats();
|
|
7146
7809
|
init_render();
|
|
7147
7810
|
init_commands();
|
|
7148
7811
|
init_editor();
|
|
@@ -7195,6 +7858,8 @@ var init_app = __esm({
|
|
|
7195
7858
|
EXPAND_MAX_LINES = 400;
|
|
7196
7859
|
FALLBACK_ROWS = 24;
|
|
7197
7860
|
FALLBACK_COLS = 80;
|
|
7861
|
+
INIT_HINT_AFTER_TURNS = 3;
|
|
7862
|
+
EARN_ROW = "\0earn";
|
|
7198
7863
|
CONTEXT_NOTICE_AT = 0.6;
|
|
7199
7864
|
KEEP_ALWAYS = Number.POSITIVE_INFINITY;
|
|
7200
7865
|
}
|
|
@@ -7228,6 +7893,7 @@ var init_tui = __esm({
|
|
|
7228
7893
|
init_client();
|
|
7229
7894
|
init_config();
|
|
7230
7895
|
init_login();
|
|
7896
|
+
init_free_tier();
|
|
7231
7897
|
init_agent();
|
|
7232
7898
|
init_tools();
|
|
7233
7899
|
init_context();
|
|
@@ -7279,7 +7945,7 @@ async function main() {
|
|
|
7279
7945
|
for (const text of configNotices) console.error(c.yellow(text));
|
|
7280
7946
|
switch (cmd) {
|
|
7281
7947
|
case "login":
|
|
7282
|
-
return login(client, config, rest);
|
|
7948
|
+
return void await login(client, config, rest, { nextStep: true });
|
|
7283
7949
|
case "logout":
|
|
7284
7950
|
return logout(config);
|
|
7285
7951
|
case "whoami":
|
|
@@ -7343,7 +8009,7 @@ async function main() {
|
|
|
7343
8009
|
return repl(client, config, { task: [cmd, ...rest].join(" ").trim(), notices: configNotices });
|
|
7344
8010
|
}
|
|
7345
8011
|
}
|
|
7346
|
-
async function login(client, config, args) {
|
|
8012
|
+
async function login(client, config, args, opts = {}) {
|
|
7347
8013
|
const parsed = parseLoginArgs(args);
|
|
7348
8014
|
for (const line2 of TERMS_NOTICE.split("\n")) console.log(c.dim(` ${line2}`));
|
|
7349
8015
|
const outcome = await performLogin(client, config, parsed, {
|
|
@@ -7366,22 +8032,28 @@ async function login(client, config, args) {
|
|
|
7366
8032
|
\u2713 logged in as ${who}`) + suffix);
|
|
7367
8033
|
const bonus = account.created ? c.dim(account.signupBonus === void 0 ? " (signup bonus)" : ` (signup bonus ${credits(account.signupBonus)})`) : "";
|
|
7368
8034
|
console.log(` balance: ${c.bold(credits(account.balance))} credits${bonus}`);
|
|
8035
|
+
if (account.model) console.log(` model: ${c.bold(account.model)} ${c.dim("(the default for new accounts)")}`);
|
|
7369
8036
|
await showStreak(client);
|
|
7370
8037
|
console.log(c.dim(` token stored in ${configPath()}`));
|
|
7371
|
-
|
|
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 };
|
|
7372
8043
|
}
|
|
7373
8044
|
case "closed":
|
|
7374
8045
|
reportSignupClosed(outcome.message, outcome.hadInvite);
|
|
7375
8046
|
process.exitCode = 1;
|
|
7376
|
-
return;
|
|
8047
|
+
return void 0;
|
|
7377
8048
|
case "timeout":
|
|
7378
8049
|
console.log(c.red("\n login timed out \u2014 run `clixad login` again."));
|
|
7379
|
-
return;
|
|
8050
|
+
return void 0;
|
|
7380
8051
|
case "aborted":
|
|
7381
|
-
return;
|
|
8052
|
+
return void 0;
|
|
7382
8053
|
default:
|
|
7383
8054
|
console.log(c.red(`
|
|
7384
8055
|
login failed: ${outcome.error}`));
|
|
8056
|
+
return void 0;
|
|
7385
8057
|
}
|
|
7386
8058
|
}
|
|
7387
8059
|
function reportSignupClosed(message, hadInvite) {
|
|
@@ -7429,8 +8101,8 @@ async function listModels(client) {
|
|
|
7429
8101
|
console.log(c.bold("Models (credit prices per 1M tokens)"));
|
|
7430
8102
|
for (const m of models) {
|
|
7431
8103
|
const tag = m.ad_fundable ? "" : c.dim(" [paid/BYOK]");
|
|
7432
|
-
const
|
|
7433
|
-
const freeTag = !m.free ? "" :
|
|
8104
|
+
const turns2 = free?.models.includes(m.id) ? free.turns_left[m.id] ?? 0 : void 0;
|
|
8105
|
+
const freeTag = !m.free ? "" : turns2 === void 0 ? c.green(" [free]") : turns2 > 0 ? c.green(` [free: ~${turns2} turn${turns2 === 1 ? "" : "s"} left today]`) : c.dim(" [free: none left today]");
|
|
7434
8106
|
console.log(
|
|
7435
8107
|
` ${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}`
|
|
7436
8108
|
);
|
|
@@ -7583,7 +8255,9 @@ ${NO_SEARCH_INSTRUCTION}`),
|
|
|
7583
8255
|
} catch (err) {
|
|
7584
8256
|
if (err instanceof PaywallError) {
|
|
7585
8257
|
console.log(c.yellow(`
|
|
7586
|
-
|
|
8258
|
+
${shortfallHeadline(err, config.model)}`));
|
|
8259
|
+
const freeLine = freeAlternativeLine(err.freeModels);
|
|
8260
|
+
if (freeLine) console.log(c.green(freeLine));
|
|
7587
8261
|
console.log(
|
|
7588
8262
|
c.yellow(
|
|
7589
8263
|
`Run \`clixad earn\` to open the offerwall \u2014 offers pay ${grantLabel(err.grantPerAd)}.`
|
|
@@ -7632,10 +8306,14 @@ async function codeCmd(client, config, rest) {
|
|
|
7632
8306
|
const w = await safeWallet(client);
|
|
7633
8307
|
if (w) {
|
|
7634
8308
|
console.log(
|
|
7635
|
-
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}`)
|
|
7636
8310
|
);
|
|
7637
8311
|
if (w.balance <= 0) {
|
|
7638
8312
|
console.log(c.yellow(" You're out of credits \u2014 run `clixad earn` first, or the agent will hit a paywall."));
|
|
8313
|
+
const pick = w.free_tier && w.free_tier.credits_left > 0 ? freeModelToName(w.free_tier, config.model) : void 0;
|
|
8314
|
+
if (pick && pick.id !== config.model) {
|
|
8315
|
+
console.log(c.green(` ${freeAlternativeLine([{ id: pick.id, turnsLeft: pick.turns }], { rerun: false })}`));
|
|
8316
|
+
}
|
|
7639
8317
|
}
|
|
7640
8318
|
}
|
|
7641
8319
|
console.log(c.dim(" launching Kimi CLI (Clixad-configured)\u2026\n"));
|
|
@@ -7663,8 +8341,9 @@ async function repl(client, config, opts = {}) {
|
|
|
7663
8341
|
console.log(c.yellow('Interactive mode needs a terminal. Use `clixad -p "..."` for scripted runs.'));
|
|
7664
8342
|
return;
|
|
7665
8343
|
}
|
|
8344
|
+
let newAccount = false;
|
|
7666
8345
|
if (!config.token) {
|
|
7667
|
-
await login(client, config, []);
|
|
8346
|
+
newAccount = Boolean((await login(client, config, []))?.created);
|
|
7668
8347
|
if (!config.token) return;
|
|
7669
8348
|
}
|
|
7670
8349
|
let session;
|
|
@@ -7675,8 +8354,9 @@ async function repl(client, config, opts = {}) {
|
|
|
7675
8354
|
session = loadSession(opts.resume);
|
|
7676
8355
|
if (!session) return console.log(c.red(`no such session: ${opts.resume}`));
|
|
7677
8356
|
}
|
|
7678
|
-
const streak = await client.
|
|
7679
|
-
const
|
|
8357
|
+
const { streak, wallet } = await startupReads(client, config.gatewayUrl);
|
|
8358
|
+
const cwd = process.cwd();
|
|
8359
|
+
const initHint = shouldSuggestInit(cwd) ? { returning: hasSessionIn(cwd) } : void 0;
|
|
7680
8360
|
clearScreen();
|
|
7681
8361
|
const { startTui: startTui2 } = await Promise.resolve().then(() => (init_tui(), tui_exports));
|
|
7682
8362
|
await startTui2({
|
|
@@ -7685,23 +8365,32 @@ async function repl(client, config, opts = {}) {
|
|
|
7685
8365
|
wallet,
|
|
7686
8366
|
streak,
|
|
7687
8367
|
session,
|
|
8368
|
+
newAccount,
|
|
7688
8369
|
initialTask: opts.task,
|
|
7689
|
-
...opts.notices?.length ? { notices: opts.notices } : {}
|
|
8370
|
+
...opts.notices?.length ? { notices: opts.notices } : {},
|
|
8371
|
+
...initHint ? { initHint } : {}
|
|
7690
8372
|
});
|
|
7691
8373
|
}
|
|
7692
8374
|
async function runTurn(client, config, messages) {
|
|
8375
|
+
let started = false;
|
|
7693
8376
|
try {
|
|
7694
|
-
process.stdout.write(c.green("assistant ") + "");
|
|
7695
8377
|
const result = await client.chatStream(messages, config.model, {
|
|
7696
|
-
onDelta: (chunk) =>
|
|
8378
|
+
onDelta: (chunk) => {
|
|
8379
|
+
if (!started) process.stdout.write(c.green("assistant "));
|
|
8380
|
+
started = true;
|
|
8381
|
+
process.stdout.write(chunk);
|
|
8382
|
+
}
|
|
7697
8383
|
});
|
|
8384
|
+
if (!started) process.stdout.write(c.green("assistant "));
|
|
7698
8385
|
process.stdout.write("\n");
|
|
7699
|
-
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)}]`));
|
|
7700
8387
|
return result;
|
|
7701
8388
|
} catch (err) {
|
|
7702
|
-
process.stdout.write("\n");
|
|
8389
|
+
if (started) process.stdout.write("\n");
|
|
7703
8390
|
if (err instanceof PaywallError) {
|
|
7704
|
-
console.log(c.yellow(
|
|
8391
|
+
console.log(c.yellow(shortfallHeadline(err, config.model)));
|
|
8392
|
+
const freeLine = freeAlternativeLine(err.freeModels);
|
|
8393
|
+
if (freeLine) console.log(c.green(freeLine));
|
|
7705
8394
|
console.log(
|
|
7706
8395
|
c.yellow(`Run \`clixad earn\` to open the offerwall \u2014 offers pay ${grantLabel(err.grantPerAd)}.`)
|
|
7707
8396
|
);
|
|
@@ -7729,29 +8418,69 @@ async function safeWallet(client) {
|
|
|
7729
8418
|
return void 0;
|
|
7730
8419
|
}
|
|
7731
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
|
+
}
|
|
7732
8452
|
function printHelp() {
|
|
7733
|
-
|
|
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
|
|
7734
8479
|
|
|
7735
|
-
|
|
7736
|
-
${c.cyan("logout")} forget the stored token
|
|
7737
|
-
${c.cyan("whoami")} show account + balance
|
|
7738
|
-
${c.cyan("models")} list models with credit prices
|
|
7739
|
-
${c.cyan("model")} <id> set default model
|
|
7740
|
-
${c.cyan("wallet")} balance, ads today, ledger
|
|
7741
|
-
${c.cyan("earn")} open the ad wall to earn credits
|
|
7742
|
-
${c.cyan("refer")} your invite code, and what it has earned
|
|
7743
|
-
${c.cyan("redeem")} <code> spend a code you were given
|
|
7744
|
-
${c.cyan("ask")} "<prompt>" one-shot completion (no tools)
|
|
7745
|
-
${c.cyan("agent")} "<task>" open the REPL with a task already running
|
|
7746
|
-
${c.cyan("-p")} "<task>" headless agent run (read-only tools)
|
|
7747
|
-
${c.cyan("code")} ["<task>"] launch the full Kimi CLI coding agent on the current dir
|
|
7748
|
-
${c.cyan("--continue")} resume the last session in this directory
|
|
7749
|
-
${c.cyan("--resume")} [id] list saved sessions, or resume one
|
|
7750
|
-
${c.cyan("--version")} print the version and exit
|
|
7751
|
-
(no command) interactive coding REPL ${c.dim("(signs you in if needed)")}
|
|
7752
|
-
"<task>" same as ${c.cyan("agent")} \u2014 anything that is not a command is a task
|
|
8480
|
+
${lines.join("\n")}
|
|
7753
8481
|
|
|
7754
|
-
${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)")}`);
|
|
7755
8484
|
}
|
|
7756
8485
|
main().catch((err) => {
|
|
7757
8486
|
console.error(c.red(err.message));
|