clixad 0.0.1-beta.21 → 0.0.1-beta.22
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 +609 -130
- 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
|
}
|
|
@@ -252,7 +351,10 @@ function asSignupClosed(status, 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
|
}
|
|
254
353
|
function paywallError(body) {
|
|
255
|
-
|
|
354
|
+
const free = Array.isArray(body.free_models) ? body.free_models.filter(
|
|
355
|
+
(m) => !!m && typeof m.id === "string" && m.id.length > 0 && Number.isFinite(m.turns_left)
|
|
356
|
+
).map((m) => ({ id: m.id, turnsLeft: Math.max(0, Math.floor(m.turns_left)) })).filter((m) => m.turnsLeft > 0) : [];
|
|
357
|
+
return new PaywallError(body.balance, body.estimated_cost, body.grant_per_ad, free);
|
|
256
358
|
}
|
|
257
359
|
async function authFailure(res, what) {
|
|
258
360
|
const body = await res.text().catch(() => "");
|
|
@@ -278,16 +380,16 @@ async function httpFailure(res, what) {
|
|
|
278
380
|
return detail ? `${what} failed: ${res.status} \u2014 ${detail}` : `${what} failed: ${res.status}`;
|
|
279
381
|
}
|
|
280
382
|
function sleep(ms, signal) {
|
|
281
|
-
return new Promise((
|
|
383
|
+
return new Promise((resolve3) => {
|
|
282
384
|
if (!signal) {
|
|
283
|
-
setTimeout(
|
|
385
|
+
setTimeout(resolve3, ms);
|
|
284
386
|
return;
|
|
285
387
|
}
|
|
286
|
-
if (signal.aborted) return
|
|
388
|
+
if (signal.aborted) return resolve3();
|
|
287
389
|
const done = () => {
|
|
288
390
|
clearTimeout(timer);
|
|
289
391
|
signal.removeEventListener("abort", done);
|
|
290
|
-
|
|
392
|
+
resolve3();
|
|
291
393
|
};
|
|
292
394
|
const timer = setTimeout(done, ms);
|
|
293
395
|
signal.addEventListener("abort", done, { once: true });
|
|
@@ -331,6 +433,8 @@ var CHAT_ENCODED_CONTENT_TYPE, LEDGER_REASONS, SignupClosedError, PaywallError,
|
|
|
331
433
|
var init_client = __esm({
|
|
332
434
|
"src/client.ts"() {
|
|
333
435
|
"use strict";
|
|
436
|
+
init_estimate();
|
|
437
|
+
init_formats();
|
|
334
438
|
init_edge();
|
|
335
439
|
CHAT_ENCODED_CONTENT_TYPE = "application/vnd.clixad.chat+gzip";
|
|
336
440
|
LEDGER_REASONS = {
|
|
@@ -361,16 +465,18 @@ var init_client = __esm({
|
|
|
361
465
|
waitlistUrl;
|
|
362
466
|
};
|
|
363
467
|
PaywallError = class extends Error {
|
|
364
|
-
constructor(balance, estimatedCost, grantPerAd) {
|
|
468
|
+
constructor(balance, estimatedCost, grantPerAd, freeModels = []) {
|
|
365
469
|
super("Out of credits");
|
|
366
470
|
this.balance = balance;
|
|
367
471
|
this.estimatedCost = estimatedCost;
|
|
368
472
|
this.grantPerAd = grantPerAd;
|
|
473
|
+
this.freeModels = freeModels;
|
|
369
474
|
this.name = "PaywallError";
|
|
370
475
|
}
|
|
371
476
|
balance;
|
|
372
477
|
estimatedCost;
|
|
373
478
|
grantPerAd;
|
|
479
|
+
freeModels;
|
|
374
480
|
};
|
|
375
481
|
AuthError = class extends Error {
|
|
376
482
|
constructor() {
|
|
@@ -640,8 +746,18 @@ var init_client = __esm({
|
|
|
640
746
|
if (!res.ok) throw new Error(`models failed: ${res.status}`);
|
|
641
747
|
const data = await res.json();
|
|
642
748
|
this.seen = { web_search: data.capabilities?.web_search === true };
|
|
749
|
+
this.shape = parseTaskShape(data.task_shape);
|
|
643
750
|
return data.data;
|
|
644
751
|
}
|
|
752
|
+
/**
|
|
753
|
+
* The task shape the cost preview assumes, as of the last catalog fetch, or
|
|
754
|
+
* undefined when the gateway sent none (or one that did not parse). Undefined
|
|
755
|
+
* means no estimate is shown at all, the silence an older gateway gets.
|
|
756
|
+
*/
|
|
757
|
+
get taskShape() {
|
|
758
|
+
return this.shape;
|
|
759
|
+
}
|
|
760
|
+
shape;
|
|
645
761
|
/**
|
|
646
762
|
* What the gateway said it can do, as of the last catalog fetch.
|
|
647
763
|
*
|
|
@@ -701,11 +817,13 @@ var init_client = __esm({
|
|
|
701
817
|
const body = await res.json();
|
|
702
818
|
const publisherId = typeof body.publisher_id === "string" ? body.publisher_id.trim() : "";
|
|
703
819
|
if (!publisherId) return null;
|
|
820
|
+
const formats = extraFormatIds(body.formats);
|
|
704
821
|
return {
|
|
705
822
|
publisherId,
|
|
706
823
|
...typeof body.category === "string" ? { category: body.category } : {},
|
|
707
824
|
...typeof body.subject === "string" ? { subject: body.subject } : {},
|
|
708
|
-
...typeof body.dwell_ms === "number" ? { dwellMs: body.dwell_ms } : {}
|
|
825
|
+
...typeof body.dwell_ms === "number" ? { dwellMs: body.dwell_ms } : {},
|
|
826
|
+
...Object.keys(formats).length > 0 ? { formats } : {}
|
|
709
827
|
};
|
|
710
828
|
} catch {
|
|
711
829
|
return null;
|
|
@@ -735,6 +853,7 @@ var init_client = __esm({
|
|
|
735
853
|
...signal ? { signal } : {},
|
|
736
854
|
body: JSON.stringify({
|
|
737
855
|
impression_id: report.impressionId,
|
|
856
|
+
...report.format && report.format !== "top_bar" ? { format: report.format } : {},
|
|
738
857
|
...report.adId ? { ad_id: report.adId } : {},
|
|
739
858
|
billable: report.billable,
|
|
740
859
|
from_cache: report.fromCache,
|
|
@@ -1132,12 +1251,13 @@ function saveConfig(config) {
|
|
|
1132
1251
|
function configPath() {
|
|
1133
1252
|
return CONFIG_PATH;
|
|
1134
1253
|
}
|
|
1135
|
-
var CONFIG_PATH, QUARANTINE_PATH, DEFAULTS, ENV_OVERRIDES, SUPERSEDED_DEFAULTS, IDENTITY_KEYS, baseline, envApplied, notices;
|
|
1254
|
+
var CONFIG_PATH, QUARANTINE_PATH, FALLBACK_MODEL, DEFAULTS, ENV_OVERRIDES, SUPERSEDED_DEFAULTS, IDENTITY_KEYS, baseline, envApplied, notices;
|
|
1136
1255
|
var init_config = __esm({
|
|
1137
1256
|
"src/config.ts"() {
|
|
1138
1257
|
"use strict";
|
|
1139
1258
|
CONFIG_PATH = join(homedir(), ".clixad", "config.json");
|
|
1140
1259
|
QUARANTINE_PATH = `${CONFIG_PATH}.corrupt`;
|
|
1260
|
+
FALLBACK_MODEL = "gemini-2.5-flash-lite";
|
|
1141
1261
|
DEFAULTS = {
|
|
1142
1262
|
gatewayUrl: "https://clixad.onrender.com",
|
|
1143
1263
|
// Same origin as the gateway: the ad wall is served by the gateway process
|
|
@@ -1145,7 +1265,7 @@ var init_config = __esm({
|
|
|
1145
1265
|
// fields stay separate so either can be pointed elsewhere, but they no longer
|
|
1146
1266
|
// diverge by default.
|
|
1147
1267
|
dashboardUrl: "https://clixad.onrender.com",
|
|
1148
|
-
model:
|
|
1268
|
+
model: FALLBACK_MODEL
|
|
1149
1269
|
};
|
|
1150
1270
|
ENV_OVERRIDES = [
|
|
1151
1271
|
["gatewayUrl", "CLIXAD_GATEWAY_URL"],
|
|
@@ -1188,6 +1308,12 @@ function parseLoginArgs(args) {
|
|
|
1188
1308
|
}
|
|
1189
1309
|
return { invite: invite?.trim() || void 0, email: rest[0] };
|
|
1190
1310
|
}
|
|
1311
|
+
function adoptNewAccountModel(config, created, named) {
|
|
1312
|
+
if (!created || typeof named !== "string" || !/^[A-Za-z0-9._\/-]{1,64}$/.test(named)) return void 0;
|
|
1313
|
+
if (config.model !== FALLBACK_MODEL || named === config.model) return void 0;
|
|
1314
|
+
config.model = named;
|
|
1315
|
+
return named;
|
|
1316
|
+
}
|
|
1191
1317
|
async function performLogin(client, config, args, report, signal) {
|
|
1192
1318
|
if (!args.email) {
|
|
1193
1319
|
const start = await client.deviceStart().catch(() => null);
|
|
@@ -1218,6 +1344,7 @@ async function githubLogin(client, config, start, invite, report, signal) {
|
|
|
1218
1344
|
config.email = poll.email;
|
|
1219
1345
|
config.login = poll.login;
|
|
1220
1346
|
config.tokenGateway = config.gatewayUrl;
|
|
1347
|
+
const model = adoptNewAccountModel(config, poll.created, poll.default_model);
|
|
1221
1348
|
saveConfig(config);
|
|
1222
1349
|
return {
|
|
1223
1350
|
status: "ok",
|
|
@@ -1226,7 +1353,8 @@ async function githubLogin(client, config, start, invite, report, signal) {
|
|
|
1226
1353
|
login: poll.login,
|
|
1227
1354
|
balance: poll.balance,
|
|
1228
1355
|
created: poll.created,
|
|
1229
|
-
signupBonus: poll.signupBonus
|
|
1356
|
+
signupBonus: poll.signupBonus,
|
|
1357
|
+
...model ? { model } : {}
|
|
1230
1358
|
}
|
|
1231
1359
|
};
|
|
1232
1360
|
}
|
|
@@ -1249,20 +1377,24 @@ async function devLogin(client, config, email, invite) {
|
|
|
1249
1377
|
config.email = res.email;
|
|
1250
1378
|
config.tokenGateway = config.gatewayUrl;
|
|
1251
1379
|
delete config.login;
|
|
1380
|
+
const model = adoptNewAccountModel(config, true, res.default_model);
|
|
1252
1381
|
saveConfig(config);
|
|
1253
|
-
return {
|
|
1382
|
+
return {
|
|
1383
|
+
status: "ok",
|
|
1384
|
+
account: { email: res.email, balance: res.balance, created: true, ...model ? { model } : {} }
|
|
1385
|
+
};
|
|
1254
1386
|
}
|
|
1255
1387
|
function sleep2(ms, signal) {
|
|
1256
|
-
return new Promise((
|
|
1388
|
+
return new Promise((resolve3) => {
|
|
1257
1389
|
if (!signal) {
|
|
1258
|
-
setTimeout(
|
|
1390
|
+
setTimeout(resolve3, ms);
|
|
1259
1391
|
return;
|
|
1260
1392
|
}
|
|
1261
|
-
if (signal.aborted) return
|
|
1393
|
+
if (signal.aborted) return resolve3();
|
|
1262
1394
|
const done = () => {
|
|
1263
1395
|
clearTimeout(timer);
|
|
1264
1396
|
signal.removeEventListener("abort", done);
|
|
1265
|
-
|
|
1397
|
+
resolve3();
|
|
1266
1398
|
};
|
|
1267
1399
|
const timer = setTimeout(done, ms);
|
|
1268
1400
|
signal.addEventListener("abort", done, { once: true });
|
|
@@ -1283,6 +1415,49 @@ and the Privacy Policy \u2014 ${PRIVACY_URL}`;
|
|
|
1283
1415
|
}
|
|
1284
1416
|
});
|
|
1285
1417
|
|
|
1418
|
+
// src/free-tier.ts
|
|
1419
|
+
function freeModelToName(free, current) {
|
|
1420
|
+
const left = (id2) => free.turns_left[id2] ?? 0;
|
|
1421
|
+
const usable = (id2) => id2 !== void 0 && free.models.includes(id2) && left(id2) > 0;
|
|
1422
|
+
const id = usable(current) ? current : usable(free.recommended) ? free.recommended : free.models.find(usable);
|
|
1423
|
+
return id === void 0 ? void 0 : { id, turns: left(id) };
|
|
1424
|
+
}
|
|
1425
|
+
function freeTierNotice(o) {
|
|
1426
|
+
const free = o.free;
|
|
1427
|
+
if (!free || !(free.daily_credits > 0)) return void 0;
|
|
1428
|
+
const pick = free.credits_left > 0 ? freeModelToName(free, o.model) : void 0;
|
|
1429
|
+
if (!pick) {
|
|
1430
|
+
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;
|
|
1431
|
+
}
|
|
1432
|
+
if (pick.id === o.model) {
|
|
1433
|
+
return `${pick.id} is free today, ${turns(pick.turns)} left. /model shows the other free models.`;
|
|
1434
|
+
}
|
|
1435
|
+
if (o.balance <= 0) {
|
|
1436
|
+
return `Your balance is empty, but ${pick.id} is still free today (${turns(pick.turns)}). /model switches to it, /earn tops up.`;
|
|
1437
|
+
}
|
|
1438
|
+
return `${free.models.length} models are free today. /model switches, and ${pick.id} has ${turns(pick.turns)} left.`;
|
|
1439
|
+
}
|
|
1440
|
+
function freeHelpLine(free) {
|
|
1441
|
+
if (!free || !(free.daily_credits > 0)) return void 0;
|
|
1442
|
+
if (free.credits_left <= 0) return { label: "free models", desc: "used up today, back at midnight UTC" };
|
|
1443
|
+
return {
|
|
1444
|
+
label: "free today",
|
|
1445
|
+
desc: `${free.models.length} models, ${free.credits_left.toLocaleString("en-US")} free credits left \xB7 /model switches`
|
|
1446
|
+
};
|
|
1447
|
+
}
|
|
1448
|
+
function freeAlternativeLine(freeModels, { rerun = true } = {}) {
|
|
1449
|
+
const first = freeModels.find((m) => m.turnsLeft > 0);
|
|
1450
|
+
if (!first) return void 0;
|
|
1451
|
+
return `${first.id} is still free today (${turns(first.turnsLeft)}). Switch with \`clixad model ${first.id}\`${rerun ? " and run it again" : ""}.`;
|
|
1452
|
+
}
|
|
1453
|
+
var turns;
|
|
1454
|
+
var init_free_tier = __esm({
|
|
1455
|
+
"src/free-tier.ts"() {
|
|
1456
|
+
"use strict";
|
|
1457
|
+
turns = (n) => `about ${n.toLocaleString("en-US")} turn${n === 1 ? "" : "s"}`;
|
|
1458
|
+
}
|
|
1459
|
+
});
|
|
1460
|
+
|
|
1286
1461
|
// src/diff.ts
|
|
1287
1462
|
function splitLines(text) {
|
|
1288
1463
|
const lines = text.split("\n");
|
|
@@ -2381,7 +2556,7 @@ var init_mcp = __esm({
|
|
|
2381
2556
|
if (this.exited) return Promise.reject(new McpError(this.exited));
|
|
2382
2557
|
const id = ++this.seq;
|
|
2383
2558
|
const payload = JSON.stringify({ jsonrpc: "2.0", id, method, params }) + "\n";
|
|
2384
|
-
return new Promise((
|
|
2559
|
+
return new Promise((resolve3, reject) => {
|
|
2385
2560
|
const settle = (fn) => {
|
|
2386
2561
|
clearTimeout(timer);
|
|
2387
2562
|
signal?.removeEventListener("abort", onAbort);
|
|
@@ -2395,7 +2570,7 @@ var init_mcp = __esm({
|
|
|
2395
2570
|
const onAbort = () => settle(() => reject(new McpError("cancelled")));
|
|
2396
2571
|
signal?.addEventListener("abort", onAbort, { once: true });
|
|
2397
2572
|
this.pending.set(id, {
|
|
2398
|
-
resolve: (v) => settle(() =>
|
|
2573
|
+
resolve: (v) => settle(() => resolve3(v)),
|
|
2399
2574
|
reject: (e) => settle(() => reject(e))
|
|
2400
2575
|
});
|
|
2401
2576
|
this.child.stdin?.write(payload, (err) => {
|
|
@@ -3989,7 +4164,7 @@ var init_agent = __esm({
|
|
|
3989
4164
|
import { spawnSync } from "node:child_process";
|
|
3990
4165
|
import { existsSync as existsSync3, readdirSync as readdirSync2, readFileSync as readFileSync4 } from "node:fs";
|
|
3991
4166
|
import { homedir as homedir3, platform } from "node:os";
|
|
3992
|
-
import { join as join4 } from "node:path";
|
|
4167
|
+
import { join as join4, parse, resolve as resolve2 } from "node:path";
|
|
3993
4168
|
function contextFileCandidates(root) {
|
|
3994
4169
|
return [join4(homedir3(), ".clixad", "CLIXAD.md"), join4(root, "AGENTS.md"), join4(root, "CLIXAD.md")];
|
|
3995
4170
|
}
|
|
@@ -4050,13 +4225,21 @@ function topLevel(root) {
|
|
|
4050
4225
|
return void 0;
|
|
4051
4226
|
}
|
|
4052
4227
|
}
|
|
4053
|
-
|
|
4228
|
+
function shouldSuggestInit(root) {
|
|
4229
|
+
const dir = resolve2(root);
|
|
4230
|
+
if (dir === resolve2(homedir3()) || dir === parse(dir).root) return false;
|
|
4231
|
+
if (existsSync3(join4(dir, "CLIXAD.md")) || existsSync3(join4(dir, "AGENTS.md"))) return false;
|
|
4232
|
+
return PROJECT_MARKERS.some((name) => existsSync3(join4(dir, name)));
|
|
4233
|
+
}
|
|
4234
|
+
var MAX_CONTEXT_FILE_CHARS, INIT_PROMPT, INIT_HINT, PROJECT_MARKERS;
|
|
4054
4235
|
var init_context = __esm({
|
|
4055
4236
|
"src/context.ts"() {
|
|
4056
4237
|
"use strict";
|
|
4057
4238
|
init_agent();
|
|
4058
4239
|
MAX_CONTEXT_FILE_CHARS = 2e4;
|
|
4059
4240
|
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.";
|
|
4241
|
+
INIT_HINT = "No CLIXAD.md in this project yet. /init writes one, and every later session here starts out knowing the layout.";
|
|
4242
|
+
PROJECT_MARKERS = [".git", "package.json", "pyproject.toml", "Cargo.toml", "go.mod", "pom.xml", "Gemfile"];
|
|
4060
4243
|
}
|
|
4061
4244
|
});
|
|
4062
4245
|
|
|
@@ -4150,7 +4333,18 @@ var init_permissions = __esm({
|
|
|
4150
4333
|
});
|
|
4151
4334
|
|
|
4152
4335
|
// src/session.ts
|
|
4153
|
-
import {
|
|
4336
|
+
import {
|
|
4337
|
+
closeSync as closeSync2,
|
|
4338
|
+
existsSync as existsSync4,
|
|
4339
|
+
mkdirSync as mkdirSync3,
|
|
4340
|
+
openSync as openSync2,
|
|
4341
|
+
readdirSync as readdirSync3,
|
|
4342
|
+
readFileSync as readFileSync5,
|
|
4343
|
+
readSync,
|
|
4344
|
+
rmSync,
|
|
4345
|
+
statSync as statSync2,
|
|
4346
|
+
writeFileSync as writeFileSync3
|
|
4347
|
+
} from "node:fs";
|
|
4154
4348
|
import { homedir as homedir4 } from "node:os";
|
|
4155
4349
|
import { join as join5 } from "node:path";
|
|
4156
4350
|
function newSessionId() {
|
|
@@ -4187,6 +4381,28 @@ function listSessions(cwd, limit = 20) {
|
|
|
4187
4381
|
metas.sort((a, b) => b.updated.localeCompare(a.updated));
|
|
4188
4382
|
return metas.slice(0, limit);
|
|
4189
4383
|
}
|
|
4384
|
+
function hasSessionIn(cwd) {
|
|
4385
|
+
if (!existsSync4(SESSION_DIR)) return false;
|
|
4386
|
+
const needle = `"cwd":${JSON.stringify(cwd)}`;
|
|
4387
|
+
try {
|
|
4388
|
+
for (const name of readdirSync3(SESSION_DIR)) {
|
|
4389
|
+
if (!name.endsWith(".json")) continue;
|
|
4390
|
+
const path = join5(SESSION_DIR, name);
|
|
4391
|
+
let head = "";
|
|
4392
|
+
const fd = openSync2(path, "r");
|
|
4393
|
+
try {
|
|
4394
|
+
const buf = Buffer.alloc(4096);
|
|
4395
|
+
head = buf.subarray(0, readSync(fd, buf, 0, buf.length, 0)).toString("utf8");
|
|
4396
|
+
} finally {
|
|
4397
|
+
closeSync2(fd);
|
|
4398
|
+
}
|
|
4399
|
+
if (head.includes(needle)) return true;
|
|
4400
|
+
if (!head.includes('"cwd":') && loadSession(name.slice(0, -5))?.cwd === cwd) return true;
|
|
4401
|
+
}
|
|
4402
|
+
} catch {
|
|
4403
|
+
}
|
|
4404
|
+
return false;
|
|
4405
|
+
}
|
|
4190
4406
|
function latestSession(cwd) {
|
|
4191
4407
|
const [meta] = listSessions(cwd, 1);
|
|
4192
4408
|
return meta ? loadSession(meta.id) : void 0;
|
|
@@ -4284,14 +4500,14 @@ function runKimiCode(config, task, opts = {}) {
|
|
|
4284
4500
|
OPENAI_BASE_URL: `${config.gatewayUrl.replace(/\/+$/, "")}/v1`,
|
|
4285
4501
|
OPENAI_API_KEY: config.token
|
|
4286
4502
|
};
|
|
4287
|
-
return new Promise((
|
|
4503
|
+
return new Promise((resolve3) => {
|
|
4288
4504
|
const child = process.platform === "win32" ? spawn5([bin, ...args].map(quoteWin).join(" "), { stdio: "inherit", env, shell: true }) : spawn5(bin, args, { stdio: "inherit", env });
|
|
4289
4505
|
child.on("error", (err) => {
|
|
4290
4506
|
if (err.code === "ENOENT") console.log("\n" + INSTALL_HELP);
|
|
4291
4507
|
else console.error(`failed to launch Kimi CLI: ${err.message}`);
|
|
4292
|
-
|
|
4508
|
+
resolve3(127);
|
|
4293
4509
|
});
|
|
4294
|
-
child.on("exit", (code) =>
|
|
4510
|
+
child.on("exit", (code) => resolve3(code ?? 0));
|
|
4295
4511
|
});
|
|
4296
4512
|
}
|
|
4297
4513
|
function quoteWin(s) {
|
|
@@ -4804,6 +5020,16 @@ function sponsorEnabled({ isTTY, env, configEnabled }) {
|
|
|
4804
5020
|
function adFitsWidth(cols) {
|
|
4805
5021
|
return cols >= AD_MIN_COLS;
|
|
4806
5022
|
}
|
|
5023
|
+
function adFitsFormat(format, cols) {
|
|
5024
|
+
return format === "sidebar_card" ? cols >= SIDEBAR_MIN_COLS : adFitsWidth(cols);
|
|
5025
|
+
}
|
|
5026
|
+
function sidebarCard(line2, cols) {
|
|
5027
|
+
if (!adFitsFormat("sidebar_card", cols)) return null;
|
|
5028
|
+
const text = line2.trim();
|
|
5029
|
+
if (!text) return null;
|
|
5030
|
+
const body = visibleLength(text) <= SIDEBAR_BODY_COLS ? text : `${sliceColumns(text, SIDEBAR_BODY_COLS - 1).trimEnd()}\u2026`;
|
|
5031
|
+
return { rule: SIDEBAR_RULE, label: `${SPONSOR_SIGIL} ${AD_LABEL}`, body };
|
|
5032
|
+
}
|
|
4807
5033
|
function sponsorRow(line2, cols, origin) {
|
|
4808
5034
|
const marker = origin === "house" || cols >= AD_FULL_PREFIX_MIN_COLS ? HOUSE_MARKER_ROW : AD_MARKER_ROW;
|
|
4809
5035
|
const label = origin === "network" ? LABEL_ROW : null;
|
|
@@ -4818,7 +5044,7 @@ function adColorEnabled({ isTTY, env }) {
|
|
|
4818
5044
|
if (env.CI !== void 0 && env.CI !== "") return false;
|
|
4819
5045
|
return true;
|
|
4820
5046
|
}
|
|
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;
|
|
5047
|
+
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
5048
|
var init_render = __esm({
|
|
4823
5049
|
"src/sponsor/render.ts"() {
|
|
4824
5050
|
"use strict";
|
|
@@ -4835,6 +5061,10 @@ var init_render = __esm({
|
|
|
4835
5061
|
SHORT_AD_COLS = 29;
|
|
4836
5062
|
AD_MIN_COLS = visibleLength(AD_MARKER_ROW) + visibleLength(LABEL_ROW) + SHORT_AD_COLS;
|
|
4837
5063
|
AD_FULL_PREFIX_MIN_COLS = visibleLength(HOUSE_MARKER_ROW) + visibleLength(LABEL_ROW) + SHORT_AD_COLS;
|
|
5064
|
+
SIDEBAR_BODY_COLS = SHORT_AD_COLS + 9;
|
|
5065
|
+
SIDEBAR_RULE = "\u2502 ";
|
|
5066
|
+
SIDEBAR_CARD_COLS = visibleLength(SIDEBAR_RULE) + SIDEBAR_BODY_COLS;
|
|
5067
|
+
SIDEBAR_MIN_COLS = 50 + SIDEBAR_CARD_COLS;
|
|
4838
5068
|
}
|
|
4839
5069
|
});
|
|
4840
5070
|
|
|
@@ -4845,7 +5075,7 @@ function sponsorLineText(ad) {
|
|
|
4845
5075
|
if (!text) return void 0;
|
|
4846
5076
|
return text.length > MAX_TEXT_CHARS3 ? text.slice(0, MAX_TEXT_CHARS3).trimEnd() : text;
|
|
4847
5077
|
}
|
|
4848
|
-
function createSponsorSlot(config) {
|
|
5078
|
+
function createSponsorSlot(config, format = "top_bar") {
|
|
4849
5079
|
const slot = new SponsorSlot({
|
|
4850
5080
|
publisherId: config.publisherId,
|
|
4851
5081
|
serveOnly: true,
|
|
@@ -4868,7 +5098,7 @@ function createSponsorSlot(config) {
|
|
|
4868
5098
|
});
|
|
4869
5099
|
return {
|
|
4870
5100
|
async serve(cols, signal) {
|
|
4871
|
-
if (!
|
|
5101
|
+
if (!adFitsFormat(format, cols)) return null;
|
|
4872
5102
|
const ad = await slot.next({
|
|
4873
5103
|
...config.subject ? { subject: config.subject } : {},
|
|
4874
5104
|
...isCategory(config.category) ? { category: config.category } : {}
|
|
@@ -4900,9 +5130,11 @@ var init_network = __esm({
|
|
|
4900
5130
|
function commandLabel(c2) {
|
|
4901
5131
|
return `/${c2.name}${c2.args ? ` ${c2.args}` : ""}`;
|
|
4902
5132
|
}
|
|
4903
|
-
function helpText() {
|
|
4904
|
-
const w = Math.max(...COMMANDS.map((c2) => commandLabel(c2).length));
|
|
4905
|
-
return
|
|
5133
|
+
function helpText(free) {
|
|
5134
|
+
const w = Math.max(...COMMANDS.map((c2) => commandLabel(c2).length), free ? free.label.length : 0);
|
|
5135
|
+
return (free ? ` ${free.label.padEnd(w + 2)}${free.desc}
|
|
5136
|
+
|
|
5137
|
+
` : "") + COMMANDS.map((c2) => ` ${commandLabel(c2).padEnd(w + 2)}${c2.desc}`).join("\n") + "\n\n" + [
|
|
4906
5138
|
" @path attach a file to the message (tab completes)",
|
|
4907
5139
|
" !command run a shell command yourself, no model call",
|
|
4908
5140
|
" shift+tab cycle permission mode \xB7 esc stop the current turn",
|
|
@@ -5387,7 +5619,7 @@ var init_views = __esm({
|
|
|
5387
5619
|
// src/tui/app.tsx
|
|
5388
5620
|
import { useCallback, useEffect, useMemo, useRef, useState, useSyncExternalStore } from "react";
|
|
5389
5621
|
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";
|
|
5622
|
+
import { Fragment, jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
5391
5623
|
function buyDeclarationLines(pack) {
|
|
5392
5624
|
return [
|
|
5393
5625
|
`${pack.label} \u2014 ${pack.credits.toLocaleString("en-US")} credits for $${pack.price_usd.toFixed(2)}`,
|
|
@@ -5430,7 +5662,18 @@ function useTerminalSize() {
|
|
|
5430
5662
|
}, [stdout]);
|
|
5431
5663
|
return useSyncExternalStore(subscribe, getSnapshot, getSnapshot);
|
|
5432
5664
|
}
|
|
5433
|
-
function App({
|
|
5665
|
+
function App({
|
|
5666
|
+
client,
|
|
5667
|
+
config,
|
|
5668
|
+
wallet,
|
|
5669
|
+
streak,
|
|
5670
|
+
session,
|
|
5671
|
+
initialTask,
|
|
5672
|
+
notices: notices2,
|
|
5673
|
+
sponsorServe,
|
|
5674
|
+
sponsorFormats,
|
|
5675
|
+
initHint
|
|
5676
|
+
}) {
|
|
5434
5677
|
const { exit } = useApp();
|
|
5435
5678
|
const { stdout, write: writeToStdout } = useStdout();
|
|
5436
5679
|
const idRef = useRef(1);
|
|
@@ -5466,7 +5709,12 @@ function App({ client, config, wallet, streak, session, initialTask, notices: no
|
|
|
5466
5709
|
const [spent, setSpent] = useState(0);
|
|
5467
5710
|
const [freeTier, setFreeTier] = useState(wallet?.free_tier);
|
|
5468
5711
|
const [model, setModel2] = useState(config.model);
|
|
5712
|
+
const modelRef = useRef(config.model);
|
|
5469
5713
|
const [mode, setMode] = useState("normal");
|
|
5714
|
+
const switchModel = useCallback((id) => {
|
|
5715
|
+
modelRef.current = id;
|
|
5716
|
+
setModel2(id);
|
|
5717
|
+
}, []);
|
|
5470
5718
|
const [hist, setHist] = useState(() => loadHistory());
|
|
5471
5719
|
const [histIdx, setHistIdx] = useState(-1);
|
|
5472
5720
|
const [sel, setSel] = useState(0);
|
|
@@ -5480,7 +5728,12 @@ function App({ client, config, wallet, streak, session, initialTask, notices: no
|
|
|
5480
5728
|
const [startedAt, setStartedAt] = useState(0);
|
|
5481
5729
|
const [quitHint, setQuitHint] = useState(false);
|
|
5482
5730
|
const [busyLabel, setBusyLabel] = useState(WORKING);
|
|
5483
|
-
const [sponsor, setSponsor] = useState(
|
|
5731
|
+
const [sponsor, setSponsor] = useState(
|
|
5732
|
+
null
|
|
5733
|
+
);
|
|
5734
|
+
const [adSlots, setAdSlots] = useState(
|
|
5735
|
+
{}
|
|
5736
|
+
);
|
|
5484
5737
|
const messagesRef = useRef(messages);
|
|
5485
5738
|
messagesRef.current = messages;
|
|
5486
5739
|
const busyRef = useRef(false);
|
|
@@ -5506,10 +5759,15 @@ function App({ client, config, wallet, streak, session, initialTask, notices: no
|
|
|
5506
5759
|
const spinnerRef = useRef(spinnerFrames());
|
|
5507
5760
|
const runningToolRef = useRef(null);
|
|
5508
5761
|
const pendingTaskRef = useRef(null);
|
|
5762
|
+
const pendingFreeRef = useRef([]);
|
|
5763
|
+
const pendingGrantRef = useRef(0);
|
|
5509
5764
|
const mcpRef = useRef(McpHub.empty());
|
|
5510
5765
|
const todosRef = useRef(new TodoList());
|
|
5511
5766
|
const lastTurnRef = useRef(null);
|
|
5512
5767
|
const freeSpentNoticeRef = useRef(false);
|
|
5768
|
+
const emptyNoticeRef = useRef(false);
|
|
5769
|
+
const initHintedRef = useRef(false);
|
|
5770
|
+
const sessionTurnsRef = useRef(0);
|
|
5513
5771
|
const turnCountRef = useRef(config.turns ?? 0);
|
|
5514
5772
|
const deltaBufRef = useRef("");
|
|
5515
5773
|
const deltaTimerRef = useRef(null);
|
|
@@ -5517,36 +5775,54 @@ function App({ client, config, wallet, streak, session, initialTask, notices: no
|
|
|
5517
5775
|
const sponsorIdxRef = useRef(0);
|
|
5518
5776
|
const sponsorPrevRef = useRef(void 0);
|
|
5519
5777
|
const sponsorCallRef = useRef(0);
|
|
5520
|
-
const pendingImpressionRef = useRef(
|
|
5521
|
-
const
|
|
5778
|
+
const pendingImpressionRef = useRef(/* @__PURE__ */ new Map());
|
|
5779
|
+
const drawnRef = useRef(/* @__PURE__ */ new Set());
|
|
5780
|
+
const sponsorSlotRef = useRef(
|
|
5781
|
+
/* @__PURE__ */ new Map()
|
|
5782
|
+
);
|
|
5522
5783
|
const colsRef = useRef(cols);
|
|
5523
5784
|
colsRef.current = cols;
|
|
5785
|
+
const modalOpenRef = useRef(false);
|
|
5524
5786
|
const sponsorConfigRef = useRef(null);
|
|
5525
|
-
const
|
|
5526
|
-
|
|
5527
|
-
|
|
5528
|
-
if (
|
|
5529
|
-
|
|
5530
|
-
|
|
5531
|
-
|
|
5532
|
-
|
|
5533
|
-
|
|
5534
|
-
|
|
5535
|
-
|
|
5787
|
+
const serveFormat = async (format, signal) => {
|
|
5788
|
+
let served;
|
|
5789
|
+
if (sponsorServe) {
|
|
5790
|
+
if (format !== "top_bar" && !sponsorFormats?.includes(format)) return null;
|
|
5791
|
+
served = await sponsorServe(colsRef.current, signal, format);
|
|
5792
|
+
} else {
|
|
5793
|
+
sponsorConfigRef.current ??= client.sponsorConfig(signal);
|
|
5794
|
+
const conf = await sponsorConfigRef.current;
|
|
5795
|
+
if (!conf) return null;
|
|
5796
|
+
const publisherId = format === "top_bar" ? conf.publisherId : conf.formats?.[format];
|
|
5797
|
+
if (!publisherId) return null;
|
|
5798
|
+
let slot = sponsorSlotRef.current.get(format);
|
|
5799
|
+
if (!slot) {
|
|
5800
|
+
slot = createSponsorSlot({ ...conf, publisherId }, format);
|
|
5801
|
+
sponsorSlotRef.current.set(format, slot);
|
|
5536
5802
|
}
|
|
5537
|
-
|
|
5538
|
-
|
|
5539
|
-
|
|
5540
|
-
|
|
5541
|
-
|
|
5542
|
-
|
|
5543
|
-
|
|
5544
|
-
|
|
5545
|
-
|
|
5546
|
-
|
|
5547
|
-
|
|
5548
|
-
|
|
5549
|
-
|
|
5803
|
+
served = await slot.serve(colsRef.current, signal);
|
|
5804
|
+
}
|
|
5805
|
+
if (!served) return null;
|
|
5806
|
+
return {
|
|
5807
|
+
id: served.id,
|
|
5808
|
+
text: served.text,
|
|
5809
|
+
impression: {
|
|
5810
|
+
impressionId: served.impressionId,
|
|
5811
|
+
...format === "top_bar" ? {} : { format },
|
|
5812
|
+
adId: served.id,
|
|
5813
|
+
billable: served.billable,
|
|
5814
|
+
fromCache: served.fromCache,
|
|
5815
|
+
credit: served.credit
|
|
5816
|
+
}
|
|
5817
|
+
};
|
|
5818
|
+
};
|
|
5819
|
+
const remoteSponsorRef = useRef(
|
|
5820
|
+
Object.fromEntries(
|
|
5821
|
+
["top_bar", ...EXTRA_AD_FORMATS].map((format) => [
|
|
5822
|
+
format,
|
|
5823
|
+
adtentionSource((signal) => serveFormat(format, signal))
|
|
5824
|
+
])
|
|
5825
|
+
)
|
|
5550
5826
|
);
|
|
5551
5827
|
const tallyRef = useRef(createTally());
|
|
5552
5828
|
const heightCacheRef = useRef(/* @__PURE__ */ new Map());
|
|
@@ -5593,9 +5869,17 @@ function App({ client, config, wallet, streak, session, initialTask, notices: no
|
|
|
5593
5869
|
if (files.length) push({ kind: "notice", text: ` context: ${files.join(", ")}` });
|
|
5594
5870
|
const streakLine = streak && streak.claimed ? streakLabel(streak) : null;
|
|
5595
5871
|
if (streakLine) push({ kind: "notice", tone: "good", text: ` ${streakLine}` });
|
|
5872
|
+
const freeLine = freeTierNotice({ model: config.model, balance: wallet?.balance ?? 0, free: wallet?.free_tier });
|
|
5873
|
+
if (freeLine) {
|
|
5874
|
+
push({ kind: "notice", tone: (wallet?.balance ?? 0) <= 0 ? "warn" : "good", text: ` ${freeLine}` });
|
|
5875
|
+
}
|
|
5596
5876
|
if (session?.messages.length) {
|
|
5597
5877
|
push({ kind: "notice", text: ` resumed session ${session.id} (${session.messages.length} messages)` });
|
|
5598
5878
|
}
|
|
5879
|
+
if (initHint?.returning) {
|
|
5880
|
+
initHintedRef.current = true;
|
|
5881
|
+
push({ kind: "notice", text: ` ${INIT_HINT}` });
|
|
5882
|
+
}
|
|
5599
5883
|
client.models().then((m) => {
|
|
5600
5884
|
catalogRef.current = m;
|
|
5601
5885
|
searchRef.current = client.capabilities?.web_search === true;
|
|
@@ -5645,6 +5929,7 @@ function App({ client, config, wallet, streak, session, initialTask, notices: no
|
|
|
5645
5929
|
configEnabled: config.sponsor
|
|
5646
5930
|
})) {
|
|
5647
5931
|
setSponsor(null);
|
|
5932
|
+
setAdSlots({});
|
|
5648
5933
|
return;
|
|
5649
5934
|
}
|
|
5650
5935
|
const est = catalogRef.current.find((m) => m.id === modelId)?.est_ads_per_task;
|
|
@@ -5660,31 +5945,47 @@ function App({ client, config, wallet, streak, session, initialTask, notices: no
|
|
|
5660
5945
|
} else {
|
|
5661
5946
|
setSponsor(null);
|
|
5662
5947
|
}
|
|
5663
|
-
|
|
5664
|
-
|
|
5665
|
-
|
|
5666
|
-
|
|
5667
|
-
|
|
5668
|
-
|
|
5669
|
-
|
|
5670
|
-
|
|
5671
|
-
|
|
5948
|
+
for (const format of ["top_bar", ...EXTRA_AD_FORMATS]) {
|
|
5949
|
+
if (!adFitsFormat(format, colsRef.current)) continue;
|
|
5950
|
+
void remoteSponsorRef.current[format].fetch().then((line2) => {
|
|
5951
|
+
if (!line2 || call !== sponsorCallRef.current) return;
|
|
5952
|
+
if (!adFitsFormat(format, colsRef.current)) return;
|
|
5953
|
+
if (format !== "top_bar" && modalOpenRef.current) return;
|
|
5954
|
+
if (format === "top_bar") {
|
|
5955
|
+
if (!sponsorRow(line2.text, colsRef.current, "network")) return;
|
|
5956
|
+
sponsorPrevRef.current = line2.id;
|
|
5957
|
+
setSponsor({ text: line2.text, origin: "network", impressionId: line2.impression.impressionId });
|
|
5958
|
+
} else {
|
|
5959
|
+
setAdSlots((slots) => ({
|
|
5960
|
+
...slots,
|
|
5961
|
+
[format]: { text: line2.text, impressionId: line2.impression.impressionId }
|
|
5962
|
+
}));
|
|
5963
|
+
}
|
|
5964
|
+
recordImpression(tallyRef.current, line2.id);
|
|
5965
|
+
pendingImpressionRef.current.set(format, { call, impression: line2.impression });
|
|
5966
|
+
});
|
|
5967
|
+
}
|
|
5672
5968
|
},
|
|
5673
5969
|
[config.sponsor, stdout, wallet]
|
|
5674
5970
|
);
|
|
5675
5971
|
const reportSponsor = useCallback(() => {
|
|
5676
|
-
const pending = pendingImpressionRef.current;
|
|
5677
|
-
|
|
5678
|
-
|
|
5679
|
-
|
|
5972
|
+
const pending = [...pendingImpressionRef.current.values()];
|
|
5973
|
+
const drawn = drawnRef.current;
|
|
5974
|
+
pendingImpressionRef.current = /* @__PURE__ */ new Map();
|
|
5975
|
+
drawnRef.current = /* @__PURE__ */ new Set();
|
|
5976
|
+
const reports = pending.filter((p) => p.call === sponsorCallRef.current).filter((p) => drawn.has(p.impression.impressionId)).map((p) => p.impression);
|
|
5977
|
+
if (reports.length === 0) return;
|
|
5978
|
+
void (async () => {
|
|
5979
|
+
for (const report of reports) await client.reportImpression(report);
|
|
5980
|
+
})();
|
|
5680
5981
|
}, [client]);
|
|
5681
5982
|
const askUser = useCallback(
|
|
5682
|
-
(req) => new Promise((
|
|
5983
|
+
(req) => new Promise((resolve3) => setAsk({ req, resolve: resolve3 })),
|
|
5683
5984
|
[]
|
|
5684
5985
|
);
|
|
5685
5986
|
const permit = useMemo(() => createPermit(() => permRef.current, askUser), [askUser]);
|
|
5686
5987
|
const approvePlan = useCallback(
|
|
5687
|
-
(proposed) => new Promise((
|
|
5988
|
+
(proposed) => new Promise((resolve3) => {
|
|
5688
5989
|
setPlan({
|
|
5689
5990
|
plan: proposed,
|
|
5690
5991
|
resolve: (decision) => {
|
|
@@ -5692,7 +5993,7 @@ function App({ client, config, wallet, streak, session, initialTask, notices: no
|
|
|
5692
5993
|
permRef.current.mode = decision;
|
|
5693
5994
|
setMode(decision);
|
|
5694
5995
|
}
|
|
5695
|
-
|
|
5996
|
+
resolve3(decision);
|
|
5696
5997
|
}
|
|
5697
5998
|
});
|
|
5698
5999
|
}),
|
|
@@ -5704,7 +6005,7 @@ function App({ client, config, wallet, streak, session, initialTask, notices: no
|
|
|
5704
6005
|
setMode(next);
|
|
5705
6006
|
}, []);
|
|
5706
6007
|
const askQuestion = useCallback(
|
|
5707
|
-
(req) => new Promise((
|
|
6008
|
+
(req) => new Promise((resolve3) => {
|
|
5708
6009
|
setPickerSel(0);
|
|
5709
6010
|
setPicker({
|
|
5710
6011
|
title: req.question,
|
|
@@ -5716,10 +6017,10 @@ function App({ client, config, wallet, streak, session, initialTask, notices: no
|
|
|
5716
6017
|
})),
|
|
5717
6018
|
onPick: (choice) => {
|
|
5718
6019
|
push({ kind: "notice", tone: "good", text: ` answered: ${choice.value}` });
|
|
5719
|
-
|
|
6020
|
+
resolve3(choice.value);
|
|
5720
6021
|
},
|
|
5721
6022
|
onCancel: () => {
|
|
5722
|
-
|
|
6023
|
+
resolve3("nothing \u2014 the user dismissed the question. Do not ask it again.");
|
|
5723
6024
|
stopCurrent();
|
|
5724
6025
|
}
|
|
5725
6026
|
});
|
|
@@ -5798,17 +6099,19 @@ function App({ client, config, wallet, streak, session, initialTask, notices: no
|
|
|
5798
6099
|
);
|
|
5799
6100
|
const runTurn2 = useCallback(
|
|
5800
6101
|
async (task, images = []) => {
|
|
6102
|
+
pendingFreeRef.current = [];
|
|
6103
|
+
const model2 = modelRef.current;
|
|
5801
6104
|
const ac = new AbortController();
|
|
5802
6105
|
abortRef.current = ac;
|
|
5803
6106
|
setBusy(true);
|
|
5804
6107
|
setStartedAt(Date.now());
|
|
5805
6108
|
setLive({ text: "" });
|
|
5806
|
-
nextSponsor(
|
|
6109
|
+
nextSponsor(model2);
|
|
5807
6110
|
let history = messagesRef.current;
|
|
5808
6111
|
try {
|
|
5809
|
-
const window = contextWindow(
|
|
6112
|
+
const window = contextWindow(model2);
|
|
5810
6113
|
if (shouldCompact([{ role: "system", content: contextRef.current.systemPrompt }, ...history], window)) {
|
|
5811
|
-
const res = await compact(client,
|
|
6114
|
+
const res = await compact(client, model2, [
|
|
5812
6115
|
{ role: "system", content: contextRef.current.systemPrompt },
|
|
5813
6116
|
...history
|
|
5814
6117
|
], { signal: ac.signal });
|
|
@@ -5824,7 +6127,7 @@ function App({ client, config, wallet, streak, session, initialTask, notices: no
|
|
|
5824
6127
|
}
|
|
5825
6128
|
}
|
|
5826
6129
|
const result = await runAgent(client, task, {
|
|
5827
|
-
model,
|
|
6130
|
+
model: model2,
|
|
5828
6131
|
root,
|
|
5829
6132
|
history,
|
|
5830
6133
|
attachments: images,
|
|
@@ -5866,7 +6169,7 @@ ${NO_SEARCH_INSTRUCTION}`),
|
|
|
5866
6169
|
started: sessionRef.current.started,
|
|
5867
6170
|
updated: (/* @__PURE__ */ new Date()).toISOString(),
|
|
5868
6171
|
cwd: root,
|
|
5869
|
-
model,
|
|
6172
|
+
model: model2,
|
|
5870
6173
|
title: sessionRef.current.title,
|
|
5871
6174
|
messages: next
|
|
5872
6175
|
});
|
|
@@ -5882,8 +6185,10 @@ ${NO_SEARCH_INSTRUCTION}`),
|
|
|
5882
6185
|
text: result.content,
|
|
5883
6186
|
meta: turnMeta(result)
|
|
5884
6187
|
});
|
|
5885
|
-
|
|
6188
|
+
let spentNotice = false;
|
|
6189
|
+
if (freeTier?.models.includes(model2) && result.creditsWaived === 0 && result.creditsCharged > 0 && (result.freeCreditsLeft ?? freeTier.credits_left) === 0 && !freeSpentNoticeRef.current) {
|
|
5886
6190
|
freeSpentNoticeRef.current = true;
|
|
6191
|
+
spentNotice = true;
|
|
5887
6192
|
push({
|
|
5888
6193
|
kind: "notice",
|
|
5889
6194
|
tone: "warn",
|
|
@@ -5891,9 +6196,18 @@ ${NO_SEARCH_INSTRUCTION}`),
|
|
|
5891
6196
|
The free allowance resets at midnight UTC. /earn tops up before then.`
|
|
5892
6197
|
});
|
|
5893
6198
|
}
|
|
6199
|
+
if (result.creditsCharged > 0 && result.balance <= 0 && !spentNotice && !emptyNoticeRef.current) {
|
|
6200
|
+
emptyNoticeRef.current = true;
|
|
6201
|
+
const on = model2;
|
|
6202
|
+
void client.wallet().then((w) => {
|
|
6203
|
+
setFreeTier(w.free_tier);
|
|
6204
|
+
const line2 = freeTierNotice({ model: on, balance: w.balance, free: w.free_tier });
|
|
6205
|
+
if (line2) push({ kind: "notice", tone: "warn", text: ` ${line2}` });
|
|
6206
|
+
}).catch(() => void 0);
|
|
6207
|
+
}
|
|
5894
6208
|
lastTurnRef.current = {
|
|
5895
6209
|
turn: turnCountRef.current,
|
|
5896
|
-
model,
|
|
6210
|
+
model: model2,
|
|
5897
6211
|
prompt: task,
|
|
5898
6212
|
response: result.content,
|
|
5899
6213
|
steps: result.steps,
|
|
@@ -5906,14 +6220,26 @@ ${NO_SEARCH_INSTRUCTION}`),
|
|
|
5906
6220
|
turnCountRef.current += 1;
|
|
5907
6221
|
config.turns = turnCountRef.current;
|
|
5908
6222
|
saveConfig(config);
|
|
6223
|
+
sessionTurnsRef.current += 1;
|
|
6224
|
+
if (initHint && !initHintedRef.current && sessionTurnsRef.current >= INIT_HINT_AFTER_TURNS && shouldSuggestInit(root)) {
|
|
6225
|
+
initHintedRef.current = true;
|
|
6226
|
+
push({ kind: "notice", text: ` ${INIT_HINT}` });
|
|
6227
|
+
}
|
|
5909
6228
|
}
|
|
5910
6229
|
} catch (err) {
|
|
5911
6230
|
if (err instanceof PaywallError) {
|
|
5912
6231
|
pendingTaskRef.current = { task, images };
|
|
6232
|
+
pendingFreeRef.current = err.freeModels;
|
|
6233
|
+
pendingGrantRef.current = err.grantPerAd;
|
|
6234
|
+
const free = err.freeModels[0];
|
|
5913
6235
|
push({
|
|
5914
6236
|
kind: "notice",
|
|
5915
6237
|
tone: "warn",
|
|
5916
|
-
text: ` Out of credits (balance ${err.balance}, need ~${err.estimatedCost}).
|
|
6238
|
+
text: free ? ` Out of credits on ${model2} (balance ${err.balance}, need ~${err.estimatedCost}).
|
|
6239
|
+
Press enter \u2014 ${free.id} is still free today (~${free.turnsLeft} turn${free.turnsLeft === 1 ? "" : "s"} left), or take an offer instead.
|
|
6240
|
+
Your request is kept either way, so you will not have to type it again.
|
|
6241
|
+
Offers you are screened out of pay nothing; that is normal, just start another.
|
|
6242
|
+
/buy is there too, if you would rather pay than do an offer.` : ` Out of credits (balance ${err.balance}, need ~${err.estimatedCost}).
|
|
5917
6243
|
Press enter to open the ad wall \u2014 offers pay ${grantLabel(err.grantPerAd)}.
|
|
5918
6244
|
I'll continue automatically. Offers you are screened out of pay nothing;
|
|
5919
6245
|
that is normal, just start another.
|
|
@@ -5932,6 +6258,7 @@ ${NO_SEARCH_INSTRUCTION}`),
|
|
|
5932
6258
|
reportSponsor();
|
|
5933
6259
|
sponsorCallRef.current++;
|
|
5934
6260
|
setSponsor(null);
|
|
6261
|
+
setAdSlots({});
|
|
5935
6262
|
}
|
|
5936
6263
|
},
|
|
5937
6264
|
[
|
|
@@ -6002,10 +6329,21 @@ ${NO_SEARCH_INSTRUCTION}`),
|
|
|
6002
6329
|
tone: "good",
|
|
6003
6330
|
text: ` signed in as ${account.login ?? account.email ?? "you"} \xB7 balance ${account.balance.toLocaleString("en-US")} credits` + (account.created ? " (signup bonus)" : "")
|
|
6004
6331
|
});
|
|
6332
|
+
if (account.model) {
|
|
6333
|
+
switchModel(account.model);
|
|
6334
|
+
push({ kind: "notice", text: ` model \u2192 ${account.model} (the default for new accounts)` });
|
|
6335
|
+
}
|
|
6005
6336
|
const claimed = await client.claimStreak(signal);
|
|
6006
6337
|
const line2 = claimed?.claimed ? streakLabel(claimed) : null;
|
|
6007
6338
|
if (claimed?.claimed) setBalance(claimed.balance);
|
|
6008
6339
|
if (line2) push({ kind: "notice", tone: "good", text: ` ${line2}` });
|
|
6340
|
+
const w = await client.wallet(signal).catch(() => void 0);
|
|
6341
|
+
if (w) {
|
|
6342
|
+
setBalance(w.balance);
|
|
6343
|
+
setFreeTier(w.free_tier);
|
|
6344
|
+
const freeLine = freeTierNotice({ model: modelRef.current, balance: w.balance, free: w.free_tier });
|
|
6345
|
+
if (freeLine) push({ kind: "notice", tone: w.balance <= 0 ? "warn" : "good", text: ` ${freeLine}` });
|
|
6346
|
+
}
|
|
6009
6347
|
return;
|
|
6010
6348
|
}
|
|
6011
6349
|
case "closed":
|
|
@@ -6073,6 +6411,46 @@ ${NO_SEARCH_INSTRUCTION}`),
|
|
|
6073
6411
|
push({ kind: "notice", tone: "good", text: " credits added \u2014 continuing" });
|
|
6074
6412
|
if (pending) await runTurn2(pending.task, pending.images);
|
|
6075
6413
|
}, [balance, client, config.dashboardUrl, push, runBusy, runTurn2]);
|
|
6414
|
+
const runPaywallChoice = useCallback(() => {
|
|
6415
|
+
const free = pendingFreeRef.current.filter((m) => m.id !== EARN_ROW);
|
|
6416
|
+
if (free.length === 0) return void runAdWall();
|
|
6417
|
+
const items = [
|
|
6418
|
+
...free.map((m) => ({
|
|
6419
|
+
value: m.id,
|
|
6420
|
+
label: m.id,
|
|
6421
|
+
hint: `free: ~${m.turnsLeft} turn${m.turnsLeft === 1 ? "" : "s"} left today`,
|
|
6422
|
+
current: m.id === model
|
|
6423
|
+
})),
|
|
6424
|
+
{
|
|
6425
|
+
value: EARN_ROW,
|
|
6426
|
+
label: "earn credits instead",
|
|
6427
|
+
// The wall keeps its figure here: this row is the one that costs time,
|
|
6428
|
+
// and what it pays is the only thing that makes the time worth it.
|
|
6429
|
+
hint: `opens the ad wall \u2014 offers pay ${grantLabel(pendingGrantRef.current)}`
|
|
6430
|
+
}
|
|
6431
|
+
];
|
|
6432
|
+
setPickerSel(0);
|
|
6433
|
+
setPicker({
|
|
6434
|
+
title: "Out of credits",
|
|
6435
|
+
subtitle: "Pick a free model to carry on with, or earn credits at the wall.",
|
|
6436
|
+
items,
|
|
6437
|
+
onPick: (choice) => {
|
|
6438
|
+
if (choice.value === EARN_ROW) return void runAdWall();
|
|
6439
|
+
const pending = pendingTaskRef.current;
|
|
6440
|
+
pendingTaskRef.current = null;
|
|
6441
|
+
pendingFreeRef.current = [];
|
|
6442
|
+
config.model = choice.value;
|
|
6443
|
+
saveConfig(config);
|
|
6444
|
+
switchModel(choice.value);
|
|
6445
|
+
push({
|
|
6446
|
+
kind: "notice",
|
|
6447
|
+
tone: "good",
|
|
6448
|
+
text: ` model \u2192 ${choice.value} (free today) \u2014 continuing where you left off`
|
|
6449
|
+
});
|
|
6450
|
+
if (pending) void runTurn2(pending.task, pending.images);
|
|
6451
|
+
}
|
|
6452
|
+
});
|
|
6453
|
+
}, [config, model, push, runAdWall, runTurn2, switchModel]);
|
|
6076
6454
|
const runBuy = useCallback(async () => {
|
|
6077
6455
|
await runBusy(LOADING, async (signal) => {
|
|
6078
6456
|
const { enabled, packs } = await client.creditPacks(signal);
|
|
@@ -6160,7 +6538,7 @@ ${NO_SEARCH_INSTRUCTION}`),
|
|
|
6160
6538
|
exit();
|
|
6161
6539
|
return;
|
|
6162
6540
|
case "help":
|
|
6163
|
-
push({ kind: "notice", text: helpText() });
|
|
6541
|
+
push({ kind: "notice", text: helpText(freeHelpLine(freeTier)) });
|
|
6164
6542
|
return;
|
|
6165
6543
|
// Clears both halves of what "clear" means: the model's context *and*
|
|
6166
6544
|
// the screen. It used to drop only the context, so nothing visibly
|
|
@@ -6216,6 +6594,7 @@ ${NO_SEARCH_INSTRUCTION}`),
|
|
|
6216
6594
|
});
|
|
6217
6595
|
return;
|
|
6218
6596
|
case "init":
|
|
6597
|
+
initHintedRef.current = true;
|
|
6219
6598
|
await runTurn2(INIT_PROMPT);
|
|
6220
6599
|
return;
|
|
6221
6600
|
// Always the picker — an id you have to remember and type is exactly
|
|
@@ -6248,7 +6627,7 @@ ${NO_SEARCH_INSTRUCTION}`),
|
|
|
6248
6627
|
onPick: (choice) => {
|
|
6249
6628
|
config.model = choice.value;
|
|
6250
6629
|
saveConfig(config);
|
|
6251
|
-
|
|
6630
|
+
switchModel(choice.value);
|
|
6252
6631
|
push({ kind: "notice", text: ` model \u2192 ${choice.value}` });
|
|
6253
6632
|
}
|
|
6254
6633
|
});
|
|
@@ -6391,8 +6770,8 @@ ${NO_SEARCH_INSTRUCTION}`),
|
|
|
6391
6770
|
// stop" whenever it is locked.
|
|
6392
6771
|
case "refer":
|
|
6393
6772
|
await runBusy(LOADING, async (signal) => {
|
|
6394
|
-
const
|
|
6395
|
-
push({ kind: "notice", text: referLines(
|
|
6773
|
+
const info2 = await client.referral(signal);
|
|
6774
|
+
push({ kind: "notice", text: referLines(info2).map((line3) => ` ${line3}`).join("\n") });
|
|
6396
6775
|
});
|
|
6397
6776
|
return;
|
|
6398
6777
|
case "buy":
|
|
@@ -6488,7 +6867,7 @@ ${output}` }
|
|
|
6488
6867
|
lastTurnRef.current = null;
|
|
6489
6868
|
const amount = turn.free ? wallet?.rewards?.feedback_credits_free ?? wallet?.rewards?.feedback_credits : wallet?.rewards?.feedback_credits;
|
|
6490
6869
|
const pay = amount ? `Both answers pay ${amount.toLocaleString("en-US")} credits` : "Both answers pay the same";
|
|
6491
|
-
const choice = await new Promise((
|
|
6870
|
+
const choice = await new Promise((resolve3) => {
|
|
6492
6871
|
setPickerSel(0);
|
|
6493
6872
|
setPicker({
|
|
6494
6873
|
title: "Was that last answer any good?",
|
|
@@ -6498,10 +6877,10 @@ ${output}` }
|
|
|
6498
6877
|
{ value: "down", label: "no \u2014 that missed", hint: "" },
|
|
6499
6878
|
{ value: "skip", label: "skip", hint: "nothing is sent, nothing is stored" }
|
|
6500
6879
|
],
|
|
6501
|
-
onPick: (item) =>
|
|
6880
|
+
onPick: (item) => resolve3(item.value),
|
|
6502
6881
|
// Esc and "skip" are the same outcome and must stay that way: a skip
|
|
6503
6882
|
// that cost something is a prompt people stop reading.
|
|
6504
|
-
onCancel: () =>
|
|
6883
|
+
onCancel: () => resolve3(null)
|
|
6505
6884
|
});
|
|
6506
6885
|
});
|
|
6507
6886
|
if (choice !== "up" && choice !== "down") return;
|
|
@@ -6752,7 +7131,7 @@ ${dropped.map((l) => ` \xB7 ${l}`).join("\n")}`
|
|
|
6752
7131
|
if (key.return) {
|
|
6753
7132
|
if (key.meta || key.shift) return setEditor(newline(editor));
|
|
6754
7133
|
if (endsWithContinuation(editor)) return setEditor(continueLine(editor));
|
|
6755
|
-
if (!busy && pendingTaskRef.current && isEmpty(editor)) return void
|
|
7134
|
+
if (!busy && pendingTaskRef.current && isEmpty(editor)) return void runPaywallChoice();
|
|
6756
7135
|
return void submit(toText(editor));
|
|
6757
7136
|
}
|
|
6758
7137
|
if (key.ctrl && ch === "j") return setEditor(newline(editor));
|
|
@@ -6777,6 +7156,10 @@ ${dropped.map((l) => ` \xB7 ${l}`).join("\n")}`
|
|
|
6777
7156
|
const spinner = spinnerRef.current[Math.floor(tick / SPINNER_TICKS_PER_FRAME) % spinnerRef.current.length];
|
|
6778
7157
|
const sponsorLine = busy && sponsor ? sponsorRow(sponsor.text, cols, sponsor.origin) : null;
|
|
6779
7158
|
const adColor = adColorEnabled({ isTTY: stdout.isTTY, env: process.env });
|
|
7159
|
+
const modalOpen = Boolean(picker || ask2 || plan || buy);
|
|
7160
|
+
modalOpenRef.current = modalOpen;
|
|
7161
|
+
const card = busy && !modalOpen && adSlots.sidebar_card ? sidebarCard(adSlots.sidebar_card.text, cols) : null;
|
|
7162
|
+
const statusBarLine = busy && !modalOpen && adSlots.status_bar ? sponsorRow(adSlots.status_bar.text, cols, "network") : null;
|
|
6780
7163
|
const viewport = Math.max(1, rows - 1);
|
|
6781
7164
|
const inputRows = Math.max(1, Math.min(MAX_INPUT_ROWS, editor.lines.length));
|
|
6782
7165
|
const inputFrom = windowStart(editor.row, editor.lines.length, inputRows);
|
|
@@ -6798,8 +7181,18 @@ ${dropped.map((l) => ` \xB7 ${l}`).join("\n")}`
|
|
|
6798
7181
|
const showStatus = grant(2) > 0;
|
|
6799
7182
|
const queueRows = grant(queuedRows.length) > 0 ? queuedRows : [];
|
|
6800
7183
|
const showSponsor = grant(sponsorLine ? 1 : 0) > 0;
|
|
7184
|
+
const showStatusBar = grant(statusBarLine && showStatus && !quitHint && menu.length === 0 ? 1 : 0) > 0;
|
|
7185
|
+
const showCard = busyHeight > 0 && card !== null;
|
|
6801
7186
|
const inputBoxHeight = inputRows + (inputBorder ? 2 : 0);
|
|
6802
|
-
const chromeHeight = inputBoxHeight + (showStatus ? 2 : 0) + (showSponsor ? 1 : 0) + queueRows.length;
|
|
7187
|
+
const chromeHeight = inputBoxHeight + (showStatus ? 2 : 0) + (showSponsor ? 1 : 0) + (showStatusBar ? 1 : 0) + queueRows.length;
|
|
7188
|
+
const drawnKey = [
|
|
7189
|
+
showSponsor && sponsorLine?.origin === "network" ? sponsor?.impressionId : void 0,
|
|
7190
|
+
showCard ? adSlots.sidebar_card?.impressionId : void 0,
|
|
7191
|
+
showStatusBar ? adSlots.status_bar?.impressionId : void 0
|
|
7192
|
+
].filter(Boolean).join(" ");
|
|
7193
|
+
useEffect(() => {
|
|
7194
|
+
for (const id of drawnKey.split(" ")) if (id) drawnRef.current.add(id);
|
|
7195
|
+
}, [drawnKey]);
|
|
6803
7196
|
let budget = Math.max(0, viewport - chromeHeight - menu.length - busyHeight);
|
|
6804
7197
|
const pickerCompact = Boolean(picker) && budget < PICKER_CHROME + 1;
|
|
6805
7198
|
const pickerChrome = pickerCompact ? PICKER_CHROME_COMPACT : PICKER_CHROME;
|
|
@@ -6887,15 +7280,31 @@ ${dropped.map((l) => ` \xB7 ${l}`).join("\n")}`
|
|
|
6887
7280
|
Math.max(1, Math.floor(cols * 0.5)),
|
|
6888
7281
|
menu.reduce((w, c2) => Math.max(w, visibleLength(c2.label)), 0)
|
|
6889
7282
|
);
|
|
7283
|
+
const conversationUsed = useMemo(
|
|
7284
|
+
() => conversationTokens([{ role: "system", content: contextRef.current.systemPrompt }, ...messages]),
|
|
7285
|
+
[messages]
|
|
7286
|
+
);
|
|
6890
7287
|
const contextPct = useMemo(() => {
|
|
6891
7288
|
const window = contextWindow(model);
|
|
6892
7289
|
if (!window) return 0;
|
|
6893
|
-
|
|
6894
|
-
|
|
6895
|
-
|
|
6896
|
-
|
|
6897
|
-
|
|
6898
|
-
|
|
7290
|
+
return conversationUsed / window;
|
|
7291
|
+
}, [contextWindow, conversationUsed, model]);
|
|
7292
|
+
const schemaTokens = useMemo(
|
|
7293
|
+
() => estimateTokens(
|
|
7294
|
+
JSON.stringify(toolSchemaFor(mode, mcpRef.current.toolSchemas(), { webSearch: searchRef.current }))
|
|
7295
|
+
),
|
|
7296
|
+
[mode]
|
|
7297
|
+
);
|
|
7298
|
+
const typed = toText(editor);
|
|
7299
|
+
let estimate;
|
|
7300
|
+
const info = catalogRef.current.find((m) => m.id === model);
|
|
7301
|
+
const shape = client.taskShape;
|
|
7302
|
+
if (!busy && info && shape && typed.trim() !== "" && !/^[/!]/.test(typed.trimStart())) {
|
|
7303
|
+
const range = estimateTask(info, conversationUsed + schemaTokens + estimateTokens(typed), shape);
|
|
7304
|
+
const onFree = Boolean(info.free && freeTier?.models.includes(model));
|
|
7305
|
+
const verdict = taskVerdict(range, { balance, ...onFree ? { freeLeft: freeTier.credits_left } : {} });
|
|
7306
|
+
estimate = estimateClause(range, verdict, !onFree && (freeTier?.credits_left ?? 0) > 0);
|
|
7307
|
+
}
|
|
6899
7308
|
const modeText = ` ${MODE_STYLE[mode].glyph} ${MODE_LABEL[mode]}`;
|
|
6900
7309
|
const statusText = fitRow(
|
|
6901
7310
|
` ${statusLine(
|
|
@@ -6908,12 +7317,19 @@ ${dropped.map((l) => ` \xB7 ${l}`).join("\n")}`
|
|
|
6908
7317
|
// Only while a free model is selected: on a premium one the allowance
|
|
6909
7318
|
// is real but irrelevant, and a countdown that has nothing to do with
|
|
6910
7319
|
// the next turn is a number that teaches people to stop reading the row.
|
|
6911
|
-
...freeTier?.models.includes(model) ? { freeLeft: freeTier.credits_left } : {}
|
|
7320
|
+
...freeTier?.models.includes(model) ? { freeLeft: freeTier.credits_left } : {},
|
|
7321
|
+
...estimate ? { estimate: estimate.variants } : {}
|
|
6912
7322
|
},
|
|
6913
7323
|
Math.max(1, cols - visibleLength(modeText) - 2)
|
|
6914
7324
|
)}`,
|
|
6915
7325
|
Math.max(0, cols - visibleLength(modeText))
|
|
6916
7326
|
);
|
|
7327
|
+
const statusParts = (() => {
|
|
7328
|
+
const shown = estimate?.warn ? estimate.variants.find((v) => statusText.includes(v)) : void 0;
|
|
7329
|
+
if (shown === void 0) return { before: statusText, after: "" };
|
|
7330
|
+
const at = statusText.indexOf(shown);
|
|
7331
|
+
return { before: statusText.slice(0, at), warn: shown, after: statusText.slice(at + shown.length) };
|
|
7332
|
+
})();
|
|
6917
7333
|
const hintText = quitHint ? fitRow(" press ctrl+c again to quit", cols) : ` ${pickHint(
|
|
6918
7334
|
["\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
7335
|
Math.max(1, cols - 2)
|
|
@@ -6923,12 +7339,47 @@ ${dropped.map((l) => ` \xB7 ${l}`).join("\n")}`
|
|
|
6923
7339
|
const room = cols - visibleLength(head);
|
|
6924
7340
|
return { key: item.value, selected: i === sel, head, hint: room > 1 ? fitRow(item.hint, room) : "" };
|
|
6925
7341
|
});
|
|
6926
|
-
const
|
|
7342
|
+
const busyCols = showCard ? cols - SIDEBAR_CARD_COLS : cols;
|
|
7343
|
+
const busyText = fitRow(
|
|
7344
|
+
`${busyLabel} ${elapsed}s \xB7 esc to stop`,
|
|
7345
|
+
Math.max(1, busyCols - visibleLength(`${spinner} `))
|
|
7346
|
+
);
|
|
6927
7347
|
return /* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", children: [
|
|
6928
7348
|
/* @__PURE__ */ jsx2(Static, { items: entries, children: (entry) => /* @__PURE__ */ jsx2(EntryView, { entry }, entry.id) }, staticEpoch),
|
|
6929
7349
|
spacer > 0 ? /* @__PURE__ */ jsx2(Box2, { height: spacer }) : null,
|
|
6930
7350
|
liveBlock ? /* @__PURE__ */ jsx2(Box2, { marginTop: 1, children: /* @__PURE__ */ jsx2(Text2, { children: liveBlock }) }) : null,
|
|
6931
|
-
busyHeight > 0
|
|
7351
|
+
busyHeight > 0 && showCard && card ? (
|
|
7352
|
+
/*
|
|
7353
|
+
The working line with the sidebar card beside it. Two columns, two
|
|
7354
|
+
rows: the working line and the blank row under it on the left, the
|
|
7355
|
+
card on the right. The blank row is the one the block below would
|
|
7356
|
+
otherwise have spent on its margin, so the frame is exactly as tall
|
|
7357
|
+
with the card as without it — the input box does not move when a card
|
|
7358
|
+
arrives or goes, and the budget above never had to count it.
|
|
7359
|
+
*/
|
|
7360
|
+
/* @__PURE__ */ jsxs2(Box2, { marginTop: 1, children: [
|
|
7361
|
+
/* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", width: busyCols, children: [
|
|
7362
|
+
/* @__PURE__ */ jsxs2(Box2, { children: [
|
|
7363
|
+
/* @__PURE__ */ jsxs2(Text2, { color: MANGO3, children: [
|
|
7364
|
+
spinner,
|
|
7365
|
+
" "
|
|
7366
|
+
] }),
|
|
7367
|
+
/* @__PURE__ */ jsx2(Text2, { dimColor: true, wrap: "truncate-end", children: busyText })
|
|
7368
|
+
] }),
|
|
7369
|
+
/* @__PURE__ */ jsx2(Text2, { children: " " })
|
|
7370
|
+
] }),
|
|
7371
|
+
/* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", width: SIDEBAR_CARD_COLS, children: [
|
|
7372
|
+
/* @__PURE__ */ jsxs2(Box2, { children: [
|
|
7373
|
+
/* @__PURE__ */ jsx2(Text2, { dimColor: true, children: card.rule }),
|
|
7374
|
+
/* @__PURE__ */ jsx2(Text2, { ...adColor ? { color: AD_MARK } : { dimColor: true }, wrap: "truncate-end", children: card.label })
|
|
7375
|
+
] }),
|
|
7376
|
+
/* @__PURE__ */ jsxs2(Box2, { children: [
|
|
7377
|
+
/* @__PURE__ */ jsx2(Text2, { dimColor: true, children: card.rule }),
|
|
7378
|
+
/* @__PURE__ */ jsx2(Text2, { ...adColor ? { color: AD_TEXT } : { dimColor: true }, wrap: "truncate-end", children: card.body })
|
|
7379
|
+
] })
|
|
7380
|
+
] })
|
|
7381
|
+
] })
|
|
7382
|
+
) : busyHeight > 0 ? /* @__PURE__ */ jsxs2(Box2, { marginTop: 1, children: [
|
|
6932
7383
|
/* @__PURE__ */ jsxs2(Text2, { color: MANGO3, children: [
|
|
6933
7384
|
spinner,
|
|
6934
7385
|
" "
|
|
@@ -6968,20 +7419,9 @@ ${dropped.map((l) => ` \xB7 ${l}`).join("\n")}`
|
|
|
6968
7419
|
pickerCompact ? null : /* @__PURE__ */ jsx2(Box2, { height: 1 }),
|
|
6969
7420
|
/* @__PURE__ */ jsx2(Text2, { dimColor: true, children: pickerHint })
|
|
6970
7421
|
] }) : null,
|
|
6971
|
-
/* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", marginTop: 1, children: [
|
|
7422
|
+
/* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", marginTop: showCard ? 0 : 1, children: [
|
|
6972
7423
|
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,
|
|
7424
|
+
showSponsor && sponsorLine ? /* @__PURE__ */ jsx2(SponsorRowView, { row: sponsorLine, adColor }) : null,
|
|
6985
7425
|
inputBorder ? /* @__PURE__ */ jsxs2(Box2, { borderStyle: "round", borderColor: busy ? MANGO3 : MANGO_BRIGHT, paddingX: 1, children: [
|
|
6986
7426
|
/* @__PURE__ */ jsx2(Text2, { color: MANGO_BRIGHT, children: "\u276F " }),
|
|
6987
7427
|
/* @__PURE__ */ jsx2(Text2, { children: renderInput(editor, inputFrom, inputRows) })
|
|
@@ -6995,18 +7435,30 @@ ${dropped.map((l) => ` \xB7 ${l}`).join("\n")}`
|
|
|
6995
7435
|
] }, row.key)),
|
|
6996
7436
|
!showStatus ? null : quitHint || menu.length > 0 ? /* @__PURE__ */ jsx2(Text2, { dimColor: true, wrap: "truncate-end", children: hintText }) : /* @__PURE__ */ jsxs2(Box2, { children: [
|
|
6997
7437
|
/* @__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
|
-
|
|
7438
|
+
statusParts.warn === void 0 ? /* @__PURE__ */ jsx2(Text2, { dimColor: true, wrap: "truncate-end", children: statusText }) : /* @__PURE__ */ jsxs2(Fragment, { children: [
|
|
7439
|
+
/* @__PURE__ */ jsx2(Text2, { dimColor: true, children: statusParts.before }),
|
|
7440
|
+
/* @__PURE__ */ jsx2(Text2, { color: "yellow", children: statusParts.warn }),
|
|
7441
|
+
/* @__PURE__ */ jsx2(Text2, { dimColor: true, wrap: "truncate-end", children: statusParts.after })
|
|
7442
|
+
] })
|
|
7443
|
+
] }),
|
|
7444
|
+
showStatusBar && statusBarLine ? /* @__PURE__ */ jsx2(SponsorRowView, { row: statusBarLine, adColor }) : null
|
|
7000
7445
|
] })
|
|
7001
7446
|
] });
|
|
7002
7447
|
}
|
|
7448
|
+
function SponsorRowView({ row, adColor }) {
|
|
7449
|
+
return /* @__PURE__ */ jsxs2(Box2, { children: [
|
|
7450
|
+
/* @__PURE__ */ jsx2(Text2, { dimColor: true, wrap: "truncate-end", children: row.marker }),
|
|
7451
|
+
row.label ? /* @__PURE__ */ jsx2(Text2, { ...adColor ? { color: AD_MARK } : { dimColor: true }, wrap: "truncate-end", children: row.label }) : null,
|
|
7452
|
+
/* @__PURE__ */ jsx2(Text2, { ...adColor && row.origin === "network" ? { color: AD_TEXT } : { dimColor: true }, wrap: "truncate-end", children: row.body })
|
|
7453
|
+
] });
|
|
7454
|
+
}
|
|
7003
7455
|
function sleep4(ms, signal) {
|
|
7004
|
-
return new Promise((
|
|
7005
|
-
if (signal.aborted) return
|
|
7456
|
+
return new Promise((resolve3) => {
|
|
7457
|
+
if (signal.aborted) return resolve3();
|
|
7006
7458
|
const done = () => {
|
|
7007
7459
|
clearTimeout(timer);
|
|
7008
7460
|
signal.removeEventListener("abort", done);
|
|
7009
|
-
|
|
7461
|
+
resolve3();
|
|
7010
7462
|
};
|
|
7011
7463
|
const timer = setTimeout(done, ms);
|
|
7012
7464
|
signal.addEventListener("abort", done, { once: true });
|
|
@@ -7030,9 +7482,9 @@ function renderInput(state, from, rows) {
|
|
|
7030
7482
|
function modelHint(m, free) {
|
|
7031
7483
|
const effort = adsPerTaskLabel(m.est_ads_per_task);
|
|
7032
7484
|
if (!m.free || !free?.models.includes(m.id)) return effort;
|
|
7033
|
-
const
|
|
7034
|
-
if (
|
|
7035
|
-
return `free: ~${
|
|
7485
|
+
const turns2 = free.turns_left[m.id] ?? 0;
|
|
7486
|
+
if (turns2 <= 0) return `free: none left today \xB7 ${effort}`;
|
|
7487
|
+
return `free: ~${turns2} turn${turns2 === 1 ? "" : "s"} left \xB7 ${effort}`;
|
|
7036
7488
|
}
|
|
7037
7489
|
function turnMeta(result) {
|
|
7038
7490
|
const n = (v) => v.toLocaleString("en-US");
|
|
@@ -7045,6 +7497,14 @@ function turnMeta(result) {
|
|
|
7045
7497
|
return `[free \xB7 ${n(result.creditsWaived)} credits waived${left}]`;
|
|
7046
7498
|
}
|
|
7047
7499
|
function statusLine(o, cols) {
|
|
7500
|
+
const phrasings = o.estimate === void 0 ? [] : typeof o.estimate === "string" ? [o.estimate] : [...o.estimate];
|
|
7501
|
+
for (const estimate of [...phrasings, void 0]) {
|
|
7502
|
+
const line2 = statusRow(o, estimate, cols);
|
|
7503
|
+
if (estimate === void 0 || visibleLength(line2) <= cols) return fitRow(line2, cols);
|
|
7504
|
+
}
|
|
7505
|
+
return fitRow(statusRow(o, void 0, cols), cols);
|
|
7506
|
+
}
|
|
7507
|
+
function statusRow(o, estimate, cols) {
|
|
7048
7508
|
const burn = o.spent > 0 && o.minutes >= 1 ? `${Math.round(o.spent / o.minutes).toLocaleString("en-US")} cr/min` : void 0;
|
|
7049
7509
|
const clauses = [
|
|
7050
7510
|
{ text: o.model, rank: KEEP_ALWAYS },
|
|
@@ -7056,6 +7516,7 @@ function statusLine(o, cols) {
|
|
|
7056
7516
|
text: o.freeLeft === void 0 ? void 0 : `free ${o.freeLeft.toLocaleString("en-US")}`,
|
|
7057
7517
|
rank: 4
|
|
7058
7518
|
},
|
|
7519
|
+
{ text: estimate, rank: 5 },
|
|
7059
7520
|
{ text: o.spent > 0 ? `\u2212${o.spent.toLocaleString("en-US")}` : void 0, rank: 2 },
|
|
7060
7521
|
{ text: burn, rank: 1 },
|
|
7061
7522
|
{
|
|
@@ -7067,7 +7528,7 @@ function statusLine(o, cols) {
|
|
|
7067
7528
|
const join7 = (minRank) => clauses.filter((c2) => c2.rank >= minRank).map((c2) => c2.text).join(" \xB7 ");
|
|
7068
7529
|
let line2 = join7(0);
|
|
7069
7530
|
for (let minRank = 1; minRank <= 5 && visibleLength(line2) > cols; minRank++) line2 = join7(minRank);
|
|
7070
|
-
return
|
|
7531
|
+
return line2;
|
|
7071
7532
|
}
|
|
7072
7533
|
function windowStart(sel, total, size) {
|
|
7073
7534
|
if (total <= size) return 0;
|
|
@@ -7118,7 +7579,7 @@ function tailLines(text, max) {
|
|
|
7118
7579
|
const lines = text.split("\n");
|
|
7119
7580
|
return lines.length <= max ? text : lines.slice(-max).join("\n");
|
|
7120
7581
|
}
|
|
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;
|
|
7582
|
+
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
7583
|
var init_app = __esm({
|
|
7123
7584
|
"src/tui/app.tsx"() {
|
|
7124
7585
|
"use strict";
|
|
@@ -7133,6 +7594,8 @@ var init_app = __esm({
|
|
|
7133
7594
|
init_context();
|
|
7134
7595
|
init_mentions();
|
|
7135
7596
|
init_compact();
|
|
7597
|
+
init_estimate();
|
|
7598
|
+
init_free_tier();
|
|
7136
7599
|
init_kimi();
|
|
7137
7600
|
init_browser();
|
|
7138
7601
|
init_version();
|
|
@@ -7143,6 +7606,7 @@ var init_app = __esm({
|
|
|
7143
7606
|
init_counter();
|
|
7144
7607
|
init_provider();
|
|
7145
7608
|
init_network();
|
|
7609
|
+
init_formats();
|
|
7146
7610
|
init_render();
|
|
7147
7611
|
init_commands();
|
|
7148
7612
|
init_editor();
|
|
@@ -7195,6 +7659,8 @@ var init_app = __esm({
|
|
|
7195
7659
|
EXPAND_MAX_LINES = 400;
|
|
7196
7660
|
FALLBACK_ROWS = 24;
|
|
7197
7661
|
FALLBACK_COLS = 80;
|
|
7662
|
+
INIT_HINT_AFTER_TURNS = 3;
|
|
7663
|
+
EARN_ROW = "\0earn";
|
|
7198
7664
|
CONTEXT_NOTICE_AT = 0.6;
|
|
7199
7665
|
KEEP_ALWAYS = Number.POSITIVE_INFINITY;
|
|
7200
7666
|
}
|
|
@@ -7228,6 +7694,7 @@ var init_tui = __esm({
|
|
|
7228
7694
|
init_client();
|
|
7229
7695
|
init_config();
|
|
7230
7696
|
init_login();
|
|
7697
|
+
init_free_tier();
|
|
7231
7698
|
init_agent();
|
|
7232
7699
|
init_tools();
|
|
7233
7700
|
init_context();
|
|
@@ -7366,6 +7833,7 @@ async function login(client, config, args) {
|
|
|
7366
7833
|
\u2713 logged in as ${who}`) + suffix);
|
|
7367
7834
|
const bonus = account.created ? c.dim(account.signupBonus === void 0 ? " (signup bonus)" : ` (signup bonus ${credits(account.signupBonus)})`) : "";
|
|
7368
7835
|
console.log(` balance: ${c.bold(credits(account.balance))} credits${bonus}`);
|
|
7836
|
+
if (account.model) console.log(` model: ${c.bold(account.model)} ${c.dim("(the default for new accounts)")}`);
|
|
7369
7837
|
await showStreak(client);
|
|
7370
7838
|
console.log(c.dim(` token stored in ${configPath()}`));
|
|
7371
7839
|
return;
|
|
@@ -7429,8 +7897,8 @@ async function listModels(client) {
|
|
|
7429
7897
|
console.log(c.bold("Models (credit prices per 1M tokens)"));
|
|
7430
7898
|
for (const m of models) {
|
|
7431
7899
|
const tag = m.ad_fundable ? "" : c.dim(" [paid/BYOK]");
|
|
7432
|
-
const
|
|
7433
|
-
const freeTag = !m.free ? "" :
|
|
7900
|
+
const turns2 = free?.models.includes(m.id) ? free.turns_left[m.id] ?? 0 : void 0;
|
|
7901
|
+
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
7902
|
console.log(
|
|
7435
7903
|
` ${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
7904
|
);
|
|
@@ -7584,6 +8052,8 @@ ${NO_SEARCH_INSTRUCTION}`),
|
|
|
7584
8052
|
if (err instanceof PaywallError) {
|
|
7585
8053
|
console.log(c.yellow(`
|
|
7586
8054
|
Out of credits (balance ${err.balance}, need ~${err.estimatedCost}).`));
|
|
8055
|
+
const freeLine = freeAlternativeLine(err.freeModels);
|
|
8056
|
+
if (freeLine) console.log(c.green(freeLine));
|
|
7587
8057
|
console.log(
|
|
7588
8058
|
c.yellow(
|
|
7589
8059
|
`Run \`clixad earn\` to open the offerwall \u2014 offers pay ${grantLabel(err.grantPerAd)}.`
|
|
@@ -7636,6 +8106,10 @@ async function codeCmd(client, config, rest) {
|
|
|
7636
8106
|
);
|
|
7637
8107
|
if (w.balance <= 0) {
|
|
7638
8108
|
console.log(c.yellow(" You're out of credits \u2014 run `clixad earn` first, or the agent will hit a paywall."));
|
|
8109
|
+
const pick = w.free_tier && w.free_tier.credits_left > 0 ? freeModelToName(w.free_tier, config.model) : void 0;
|
|
8110
|
+
if (pick && pick.id !== config.model) {
|
|
8111
|
+
console.log(c.green(` ${freeAlternativeLine([{ id: pick.id, turnsLeft: pick.turns }], { rerun: false })}`));
|
|
8112
|
+
}
|
|
7639
8113
|
}
|
|
7640
8114
|
}
|
|
7641
8115
|
console.log(c.dim(" launching Kimi CLI (Clixad-configured)\u2026\n"));
|
|
@@ -7677,6 +8151,8 @@ async function repl(client, config, opts = {}) {
|
|
|
7677
8151
|
}
|
|
7678
8152
|
const streak = await client.claimStreak();
|
|
7679
8153
|
const wallet = await safeWallet(client);
|
|
8154
|
+
const cwd = process.cwd();
|
|
8155
|
+
const initHint = shouldSuggestInit(cwd) ? { returning: hasSessionIn(cwd) } : void 0;
|
|
7680
8156
|
clearScreen();
|
|
7681
8157
|
const { startTui: startTui2 } = await Promise.resolve().then(() => (init_tui(), tui_exports));
|
|
7682
8158
|
await startTui2({
|
|
@@ -7686,7 +8162,8 @@ async function repl(client, config, opts = {}) {
|
|
|
7686
8162
|
streak,
|
|
7687
8163
|
session,
|
|
7688
8164
|
initialTask: opts.task,
|
|
7689
|
-
...opts.notices?.length ? { notices: opts.notices } : {}
|
|
8165
|
+
...opts.notices?.length ? { notices: opts.notices } : {},
|
|
8166
|
+
...initHint ? { initHint } : {}
|
|
7690
8167
|
});
|
|
7691
8168
|
}
|
|
7692
8169
|
async function runTurn(client, config, messages) {
|
|
@@ -7702,6 +8179,8 @@ async function runTurn(client, config, messages) {
|
|
|
7702
8179
|
process.stdout.write("\n");
|
|
7703
8180
|
if (err instanceof PaywallError) {
|
|
7704
8181
|
console.log(c.yellow(`Out of credits (balance ${err.balance}, need ~${err.estimatedCost}).`));
|
|
8182
|
+
const freeLine = freeAlternativeLine(err.freeModels);
|
|
8183
|
+
if (freeLine) console.log(c.green(freeLine));
|
|
7705
8184
|
console.log(
|
|
7706
8185
|
c.yellow(`Run \`clixad earn\` to open the offerwall \u2014 offers pay ${grantLabel(err.grantPerAd)}.`)
|
|
7707
8186
|
);
|
|
@@ -7735,7 +8214,7 @@ function printHelp() {
|
|
|
7735
8214
|
${c.cyan("login")} [email] create/attach an account (${c.dim("--invite <code>")} if you have one)
|
|
7736
8215
|
${c.cyan("logout")} forget the stored token
|
|
7737
8216
|
${c.cyan("whoami")} show account + balance
|
|
7738
|
-
${c.cyan("models")} list models with credit prices
|
|
8217
|
+
${c.cyan("models")} list models with credit prices, and which are free today
|
|
7739
8218
|
${c.cyan("model")} <id> set default model
|
|
7740
8219
|
${c.cyan("wallet")} balance, ads today, ledger
|
|
7741
8220
|
${c.cyan("earn")} open the ad wall to earn credits
|