clixad 0.0.1-beta.23 → 0.0.1-beta.25

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.
Files changed (2) hide show
  1. package/dist/clixad.mjs +259 -28
  2. package/package.json +1 -1
package/dist/clixad.mjs CHANGED
@@ -94,14 +94,62 @@ var init_estimate = __esm({
94
94
  });
95
95
 
96
96
  // src/sponsor/formats.ts
97
- var EXTRA_AD_FORMATS;
97
+ function isAdFormat(value) {
98
+ return typeof value === "string" && AD_FORMATS.includes(value);
99
+ }
100
+ var AD_FORMATS, EXTRA_AD_FORMATS;
98
101
  var init_formats = __esm({
99
102
  "src/sponsor/formats.ts"() {
100
103
  "use strict";
104
+ AD_FORMATS = ["top_bar", "sidebar_card", "status_bar"];
101
105
  EXTRA_AD_FORMATS = ["sidebar_card", "status_bar"];
102
106
  }
103
107
  });
104
108
 
109
+ // src/sponsor/direct.ts
110
+ function sanitizeAdText(raw) {
111
+ return raw.replace(/\u001b\[[0-?]*[ -/]*[@-~]/g, "").replace(/\u001b\][^\u0007\u001b]*(?:\u0007|\u001b\\)?/g, "").replace(/[\u0000-\u001f\u007f-\u009f]/g, " ").replace(/[‪-‮⁦-⁩‎‏؜]/g, "").replace(/[​-‍⁠­]/g, "").replace(/\s+/g, " ").trim();
112
+ }
113
+ function parseDirectLines(body) {
114
+ const raw = body?.lines;
115
+ if (!Array.isArray(raw)) return [];
116
+ const out = [];
117
+ const seen = /* @__PURE__ */ new Set();
118
+ for (const item of raw) {
119
+ if (!item || typeof item !== "object") continue;
120
+ const e = item;
121
+ if (!isAdFormat(e.format) || seen.has(e.format)) continue;
122
+ if (e.hold === true) {
123
+ out.push({ format: e.format, hold: true });
124
+ seen.add(e.format);
125
+ continue;
126
+ }
127
+ if (typeof e.text !== "string" || typeof e.serve_token !== "string" || typeof e.click_url !== "string") continue;
128
+ const text = sanitizeAdText(e.text).slice(0, MAX_TEXT);
129
+ if (!text || !e.serve_token || e.serve_token.length > 2e3) continue;
130
+ let clickUrl;
131
+ try {
132
+ const url = new URL(e.click_url);
133
+ if (url.protocol !== "https:" && url.protocol !== "http:") continue;
134
+ clickUrl = url.toString();
135
+ } catch {
136
+ continue;
137
+ }
138
+ out.push({ format: e.format, text, serveToken: e.serve_token, clickUrl });
139
+ seen.add(e.format);
140
+ }
141
+ return out;
142
+ }
143
+ var DIRECT_ASK_TIMEOUT_MS, MAX_TEXT;
144
+ var init_direct = __esm({
145
+ "src/sponsor/direct.ts"() {
146
+ "use strict";
147
+ init_formats();
148
+ DIRECT_ASK_TIMEOUT_MS = 1500;
149
+ MAX_TEXT = 200;
150
+ }
151
+ });
152
+
105
153
  // src/edge.ts
106
154
  function detectEdgeBlock(status, headers, body) {
107
155
  if (status !== 403) return void 0;
@@ -327,6 +375,13 @@ function extraFormatIds(raw) {
327
375
  function imagePart(image) {
328
376
  return { type: "image_url", image_url: { url: `data:${image.mimeType};base64,${image.data}` } };
329
377
  }
378
+ function withoutReasoning(messages) {
379
+ return messages.map((message) => {
380
+ if (!message.reasoning_details) return message;
381
+ const { reasoning_details: _dropped, ...rest } = message;
382
+ return rest;
383
+ });
384
+ }
330
385
  function messageText(message) {
331
386
  const content = message.content;
332
387
  if (typeof content === "string") return content;
@@ -447,6 +502,26 @@ function accumulateToolCall(calls, part, fallbackIndex) {
447
502
  if (part.function?.arguments) current.function.arguments += part.function.arguments;
448
503
  calls.set(index, current);
449
504
  }
505
+ function mergeReasoningDetails(acc, delta) {
506
+ if (!Array.isArray(delta)) return;
507
+ for (const piece of delta) {
508
+ if (!piece || typeof piece !== "object") continue;
509
+ const part = piece;
510
+ const existing = typeof part.index === "number" ? acc.find((block) => block.index === part.index) : void 0;
511
+ if (!existing) {
512
+ acc.push({ ...part });
513
+ continue;
514
+ }
515
+ for (const [key, value] of Object.entries(part)) {
516
+ if (value === null || value === void 0) continue;
517
+ if ((key === "text" || key === "summary" || key === "data") && typeof value === "string") {
518
+ existing[key] = `${typeof existing[key] === "string" ? existing[key] : ""}${value}`;
519
+ } else {
520
+ existing[key] = value;
521
+ }
522
+ }
523
+ }
524
+ }
450
525
  async function* parseSSE(body) {
451
526
  const reader = body.getReader();
452
527
  const decoder = new TextDecoder();
@@ -471,6 +546,7 @@ var init_client = __esm({
471
546
  "use strict";
472
547
  init_estimate();
473
548
  init_formats();
549
+ init_direct();
474
550
  init_edge();
475
551
  CHAT_ENCODED_CONTENT_TYPE = "application/vnd.clixad.chat+gzip";
476
552
  LEDGER_REASONS = {
@@ -618,6 +694,7 @@ var init_client = __esm({
618
694
  return { status: "closed", message: closed.message, waitlistUrl: closed.waitlist_url };
619
695
  }
620
696
  if (!res.ok && !("status" in data && data.status)) {
697
+ if (res.status >= 500 || res.status === 429) return { status: "unavailable", error: `http ${res.status}` };
621
698
  return { status: "error", error: `http ${res.status}` };
622
699
  }
623
700
  return data;
@@ -954,6 +1031,51 @@ var init_client = __esm({
954
1031
  return 0;
955
1032
  }
956
1033
  }
1034
+ /**
1035
+ * Ask for booked lines (step 2): one ask per model call, naming the slots
1036
+ * that fit the terminal now. See `sponsor/direct.ts` for the contract.
1037
+ *
1038
+ * Never throws, for `sponsorConfig`'s reason. A 404 means a gateway that
1039
+ * does not know the route, so `supported: false` tells the caller to stop
1040
+ * asking for the rest of the session; every other failure (a timeout, a 429,
1041
+ * a 5xx, a malformed body) is simply "nothing booked" this time.
1042
+ */
1043
+ async directSponsor(formats, cols, signal) {
1044
+ try {
1045
+ const res = await gatewayFetch(`${this.config.gatewayUrl}/v1/sponsor/direct`, {
1046
+ method: "POST",
1047
+ headers: this.headers(),
1048
+ ...signal ? { signal } : {},
1049
+ body: JSON.stringify({ formats, cols })
1050
+ });
1051
+ if (res.status === 404) return { supported: false, lines: [] };
1052
+ if (!res.ok) return { supported: true, lines: [] };
1053
+ return { supported: true, lines: parseDirectLines(await res.json()) };
1054
+ } catch {
1055
+ return { supported: true, lines: [] };
1056
+ }
1057
+ }
1058
+ /**
1059
+ * Report the booked lines a turn drew, in one call, after the turn is over.
1060
+ * Never to `/v1/sponsor/impression`, which would credit them as ADtention's.
1061
+ * Never throws; answers what was credited, for the tally and for tests.
1062
+ */
1063
+ async reportDirectImpressions(impressions, signal) {
1064
+ if (impressions.length === 0) return 0;
1065
+ try {
1066
+ const res = await gatewayFetch(`${this.config.gatewayUrl}/v1/sponsor/direct/impression`, {
1067
+ method: "POST",
1068
+ headers: this.headers(),
1069
+ ...signal ? { signal } : {},
1070
+ body: JSON.stringify({ impressions: impressions.map((i) => ({ format: i.format, serve_token: i.serveToken })) })
1071
+ });
1072
+ if (!res.ok) return 0;
1073
+ const body = await res.json();
1074
+ return typeof body.credited === "number" ? body.credited : 0;
1075
+ } catch {
1076
+ return 0;
1077
+ }
1078
+ }
957
1079
  /**
958
1080
  * Search the web, through the gateway.
959
1081
  *
@@ -1150,6 +1272,7 @@ var init_client = __esm({
1150
1272
  let balance = 0;
1151
1273
  let streamError;
1152
1274
  const calls = /* @__PURE__ */ new Map();
1275
+ const reasoning = [];
1153
1276
  for await (const evt of parseSSE(res.body)) {
1154
1277
  if (evt === "[DONE]") break;
1155
1278
  let json;
@@ -1167,6 +1290,7 @@ var init_client = __esm({
1167
1290
  for (const [index, part] of (choice?.delta?.tool_calls ?? []).entries()) {
1168
1291
  accumulateToolCall(calls, part, index);
1169
1292
  }
1293
+ mergeReasoningDetails(reasoning, choice?.delta?.reasoning_details);
1170
1294
  if (choice?.finish_reason) finishReason = choice.finish_reason;
1171
1295
  if (json.error) streamError = json.error.message || "the model stream failed";
1172
1296
  if (json.clixad) {
@@ -1179,6 +1303,7 @@ var init_client = __esm({
1179
1303
  const message = { role: "assistant", content: content || null };
1180
1304
  if (calls.size > 0) {
1181
1305
  message.tool_calls = [...calls.entries()].sort(([a], [b]) => a - b).map(([, c2]) => c2);
1306
+ if (reasoning.length) message.reasoning_details = reasoning;
1182
1307
  }
1183
1308
  return { message, finishReason, creditsCharged, balance, ...free };
1184
1309
  }
@@ -1413,11 +1538,24 @@ async function githubLogin(client, config, start, invite, report, signal) {
1413
1538
  openBrowser(start.verification_uri);
1414
1539
  const deadline = Date.now() + start.expires_in * 1e3;
1415
1540
  let interval = Math.max(start.interval, 1);
1541
+ let unavailableSince;
1416
1542
  while (Date.now() < deadline) {
1417
1543
  await sleep2(interval * 1e3, signal);
1418
1544
  if (signal?.aborted) return { status: "aborted" };
1419
1545
  report.tick?.();
1420
- const poll = await client.devicePoll(start.session, invite);
1546
+ let poll;
1547
+ try {
1548
+ poll = await client.devicePoll(start.session, invite);
1549
+ } catch (err) {
1550
+ if (!(err instanceof GatewayUnreachableError)) throw err;
1551
+ poll = { status: "unavailable", error: err.message };
1552
+ }
1553
+ if (poll.status === "unavailable") {
1554
+ unavailableSince ??= Date.now();
1555
+ if (Date.now() - unavailableSince > UNAVAILABLE_GIVE_UP_MS) return { status: "failed", error: poll.error };
1556
+ continue;
1557
+ }
1558
+ unavailableSince = void 0;
1421
1559
  if (poll.status === "pending") {
1422
1560
  if (poll.interval) interval = poll.interval;
1423
1561
  continue;
@@ -1445,10 +1583,19 @@ async function githubLogin(client, config, start, invite, report, signal) {
1445
1583
  }
1446
1584
  };
1447
1585
  }
1448
- return { status: "failed", error: poll.error ?? poll.status };
1586
+ return { status: "failed", error: describePollError(poll.error ?? poll.status) };
1449
1587
  }
1450
1588
  return { status: "timeout" };
1451
1589
  }
1590
+ function describePollError(error) {
1591
+ if (error === "unknown_session") {
1592
+ return "the server restarted while you were signing in, so this code no longer works. Sign in again.";
1593
+ }
1594
+ if (error === "login_unavailable") {
1595
+ return "GitHub approved the sign-in, but the server could not finish it. Try again in a minute.";
1596
+ }
1597
+ return error;
1598
+ }
1452
1599
  async function devLogin(client, config, email, invite) {
1453
1600
  let res;
1454
1601
  try {
@@ -1487,7 +1634,7 @@ function sleep2(ms, signal) {
1487
1634
  signal.addEventListener("abort", done, { once: true });
1488
1635
  });
1489
1636
  }
1490
- var TERMS_URL, PRIVACY_URL, TERMS_NOTICE;
1637
+ var TERMS_URL, PRIVACY_URL, TERMS_NOTICE, UNAVAILABLE_GIVE_UP_MS;
1491
1638
  var init_login = __esm({
1492
1639
  "src/login.ts"() {
1493
1640
  "use strict";
@@ -1499,6 +1646,7 @@ var init_login = __esm({
1499
1646
  TERMS_NOTICE = `By creating an account you confirm that you are at least 18 and that you agree to
1500
1647
  the Terms \u2014 ${TERMS_URL}
1501
1648
  and the Privacy Policy \u2014 ${PRIVACY_URL}`;
1649
+ UNAVAILABLE_GIVE_UP_MS = 18e4;
1502
1650
  }
1503
1651
  });
1504
1652
 
@@ -4060,6 +4208,19 @@ ${NO_SEARCH_INSTRUCTION}`,
4060
4208
 
4061
4209
  (the sub-agent hit its step limit \u2014 this answer may be incomplete)` : answer;
4062
4210
  }
4211
+ function emitUsage(turn) {
4212
+ emit({
4213
+ type: "usage",
4214
+ creditsCharged: turn.creditsCharged,
4215
+ balance: turn.balance,
4216
+ ...turn.free ? {
4217
+ free: true,
4218
+ creditsWaived: turn.creditsWaived ?? 0,
4219
+ freeCreditsLeft: turn.freeCreditsLeft ?? 0
4220
+ } : {}
4221
+ });
4222
+ }
4223
+ let retriedEmpty = false;
4063
4224
  for (let step = 0; step < maxSteps; step++) {
4064
4225
  if (opts.signal?.aborted) return stop("aborted");
4065
4226
  let turn;
@@ -4077,23 +4238,27 @@ ${NO_SEARCH_INSTRUCTION}`,
4077
4238
  creditsWaived += turn.creditsWaived ?? 0;
4078
4239
  if (turn.freeCreditsLeft !== void 0) freeCreditsLeft = turn.freeCreditsLeft;
4079
4240
  balance = turn.balance;
4080
- emit({
4081
- type: "usage",
4082
- creditsCharged: turn.creditsCharged,
4083
- balance: turn.balance,
4084
- ...turn.free ? {
4085
- free: true,
4086
- creditsWaived: turn.creditsWaived ?? 0,
4087
- freeCreditsLeft: turn.freeCreditsLeft ?? 0
4088
- } : {}
4089
- });
4090
- messages.push(turn.message);
4241
+ emitUsage(turn);
4091
4242
  const content = messageText(turn.message);
4092
- if (content) lastText = content;
4093
4243
  const calls = turn.message.tool_calls ?? [];
4244
+ if (calls.length === 0 && !content.trim() && !retriedEmpty) {
4245
+ retriedEmpty = true;
4246
+ continue;
4247
+ }
4248
+ retriedEmpty = false;
4249
+ messages.push(turn.message);
4250
+ if (content) lastText = content;
4094
4251
  if (calls.length === 0) {
4095
4252
  emit({ type: "final", content, creditsCharged, balance });
4096
- return { content, messages, creditsCharged, balance, creditsWaived, freeCreditsLeft, steps: step + 1 };
4253
+ return {
4254
+ content,
4255
+ messages: withoutReasoning(messages),
4256
+ creditsCharged,
4257
+ balance,
4258
+ creditsWaived,
4259
+ freeCreditsLeft,
4260
+ steps: step + 1
4261
+ };
4097
4262
  }
4098
4263
  if (content) emit({ type: "message", content });
4099
4264
  pendingImages = [];
@@ -4117,7 +4282,7 @@ ${NO_SEARCH_INSTRUCTION}`,
4117
4282
  emit({ type: "max_steps" });
4118
4283
  return {
4119
4284
  content: lastText || "(stopped: reached max steps)",
4120
- messages,
4285
+ messages: withoutReasoning(messages),
4121
4286
  creditsCharged,
4122
4287
  balance,
4123
4288
  creditsWaived,
@@ -4127,7 +4292,16 @@ ${NO_SEARCH_INSTRUCTION}`,
4127
4292
  };
4128
4293
  function stop(reason) {
4129
4294
  emit({ type: reason });
4130
- return { content: lastText, messages, creditsCharged, balance, creditsWaived, freeCreditsLeft, steps: 0, stopped: reason };
4295
+ return {
4296
+ content: lastText,
4297
+ messages: withoutReasoning(messages),
4298
+ creditsCharged,
4299
+ balance,
4300
+ creditsWaived,
4301
+ freeCreditsLeft,
4302
+ steps: 0,
4303
+ stopped: reason
4304
+ };
4131
4305
  }
4132
4306
  }
4133
4307
  function toolMessage(call, content) {
@@ -4230,7 +4404,7 @@ var init_agent = __esm({
4230
4404
  init_mcp();
4231
4405
  init_client();
4232
4406
  SUBAGENT_MAX_STEPS = 12;
4233
- SYSTEM_PROMPT = "You are Clixad, a terminal coding agent. You can read, search, write and edit files and run shell commands, all confined to the user's workspace. Find your way around with glob and grep before reading whole files. Prefer edit_file over write_file for existing files. Make minimal correct edits, verify them when a test or build command is available, and stop when the task is complete. Keep your final message short: say what you did and why.\n\nNot every message is a task. If the user asks a question, or asks you to say something, answer in plain text and call no tools at all. Reach for a file or a command only when the request is actually about one, and when a message is ambiguous, prefer the reading of it that changes nothing.\n\nA tool call that was refused did not happen. Never describe it as done, and do not repeat it on a later message unless the user asks for it again.\n\nReach outside the workspace when the answer is outside it: web_search for current documentation, releases and error messages, then web_fetch to read the page. Do not guess at an API you could look up.\n\nStart anything that does not exit on its own \u2014 dev servers, watchers, tailed logs \u2014 with run_command background: true, and read it with read_output. A foreground command is killed after its timeout.\n\nWhen finding something out means opening many files, give it to task instead: the sub-agent reads them in its own context and reports back, which keeps this conversation short. Use todo_write for work with several distinct steps, and keep it current as you go.";
4407
+ SYSTEM_PROMPT = "You are Clixad, a terminal coding agent. You can read, search, write and edit files and run shell commands, all confined to the user's workspace. When someone asks who or what you are, answer that you are Clixad, in their language; do not introduce yourself as the underlying language model or as a product of the company that trained it. If asked which model you run on, say it is the one selected with /model, shown under the input box. Find your way around with glob and grep before reading whole files. Prefer edit_file over write_file for existing files. Make minimal correct edits, verify them when a test or build command is available, and stop when the task is complete. Keep your final message short: say what you did and why.\n\nNot every message is a task. If the user asks a question, or asks you to say something, answer in plain text and call no tools at all. Reach for a file or a command only when the request is actually about one, and when a message is ambiguous, prefer the reading of it that changes nothing.\n\nA tool call that was refused did not happen. Never describe it as done, and do not repeat it on a later message unless the user asks for it again.\n\nReach outside the workspace when the answer is outside it: web_search for current documentation, releases and error messages, then web_fetch to read the page. Do not guess at an API you could look up.\n\nStart anything that does not exit on its own \u2014 dev servers, watchers, tailed logs \u2014 with run_command background: true, and read it with read_output. A foreground command is killed after its timeout.\n\nWhen finding something out means opening many files, give it to task instead: the sub-agent reads them in its own context and reports back, which keeps this conversation short. Use todo_write for work with several distinct steps, and keep it current as you go.";
4234
4408
  NO_SEARCH_INSTRUCTION = [
4235
4409
  "# Web search is not available on this gateway",
4236
4410
  "There is no web_search tool. web_fetch works normally and needs no key, so fetch a URL directly",
@@ -4620,7 +4794,7 @@ var init_kimi = __esm({
4620
4794
  "gemini-3.8-flash": 1048576,
4621
4795
  "kimi-k2.7-code": 262144,
4622
4796
  "claude-haiku-4.5": 2e5,
4623
- "claude-sonnet-5": 1e6,
4797
+ "claude-sonnet-5.5": 1e6,
4624
4798
  "gpt-6-sol": 105e4,
4625
4799
  "kimi-k3": 1048576,
4626
4800
  "claude-opus-5.5": 1e6,
@@ -4634,7 +4808,9 @@ var init_kimi = __esm({
4634
4808
  "gpt-5.2": 105e4,
4635
4809
  "claude-opus-5": 1e6,
4636
4810
  "gpt-5.5": 105e4,
4637
- "gpt-5.2-pro": 105e4
4811
+ "gpt-5.2-pro": 105e4,
4812
+ // Retired on 2026-09-29 for Claude Sonnet 5.5, same window.
4813
+ "claude-sonnet-5": 1e6
4638
4814
  };
4639
4815
  DEFAULT_CONTEXT_TOKENS = 256e3;
4640
4816
  INSTALL_HELP = `Kimi CLI is not installed (or not on PATH).
@@ -5751,7 +5927,7 @@ var init_views = __esm({
5751
5927
  });
5752
5928
 
5753
5929
  // src/tui/app.tsx
5754
- import { useCallback, useEffect, useMemo, useRef, useState, useSyncExternalStore } from "react";
5930
+ import { useCallback, useEffect, useLayoutEffect, useMemo, useRef, useState, useSyncExternalStore } from "react";
5755
5931
  import { Box as Box2, Static, Text as Text2, useApp, useInput, usePaste, useStdout } from "ink";
5756
5932
  import { Fragment, jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
5757
5933
  function buyDeclarationLines(pack) {
@@ -5807,6 +5983,7 @@ function App({
5807
5983
  notices: notices2,
5808
5984
  sponsorServe,
5809
5985
  sponsorFormats,
5986
+ sponsorDirect,
5810
5987
  initHint
5811
5988
  }) {
5812
5989
  const { exit } = useApp();
@@ -5915,6 +6092,8 @@ function App({
5915
6092
  const sponsorPrevRef = useRef(void 0);
5916
6093
  const sponsorCallRef = useRef(0);
5917
6094
  const pendingImpressionRef = useRef(/* @__PURE__ */ new Map());
6095
+ const pendingDirectRef = useRef(/* @__PURE__ */ new Map());
6096
+ const directSupportedRef = useRef(true);
5918
6097
  const drawnRef = useRef(/* @__PURE__ */ new Set());
5919
6098
  const sponsorSlotRef = useRef(
5920
6099
  /* @__PURE__ */ new Map()
@@ -5965,6 +6144,9 @@ function App({
5965
6144
  );
5966
6145
  const tallyRef = useRef(createTally());
5967
6146
  const heightCacheRef = useRef(/* @__PURE__ */ new Map());
6147
+ const screenRef = useRef(
6148
+ null
6149
+ );
5968
6150
  const push = useCallback((e) => {
5969
6151
  const id = idRef.current++;
5970
6152
  setEntries((prev) => [...prev, { ...e, id }]);
@@ -6123,8 +6305,7 @@ function App({
6123
6305
  } else {
6124
6306
  setSponsor(null);
6125
6307
  }
6126
- for (const format of ["top_bar", ...EXTRA_AD_FORMATS]) {
6127
- if (!adFitsFormat(format, colsRef.current)) continue;
6308
+ const fetchNetwork = (format) => {
6128
6309
  void remoteSponsorRef.current[format].fetch().then((line2) => {
6129
6310
  if (!line2 || call !== sponsorCallRef.current) return;
6130
6311
  if (!adFitsFormat(format, colsRef.current)) return;
@@ -6142,15 +6323,55 @@ function App({
6142
6323
  recordImpression(tallyRef.current, line2.id);
6143
6324
  pendingImpressionRef.current.set(format, { call, impression: line2.impression });
6144
6325
  });
6145
- }
6326
+ };
6327
+ const paintDirect = (line2) => {
6328
+ const { format } = line2;
6329
+ if (call !== sponsorCallRef.current) return;
6330
+ if (!adFitsFormat(format, colsRef.current)) return;
6331
+ if (format !== "top_bar" && modalOpenRef.current) return;
6332
+ const drawnId = `direct:${call}:${format}`;
6333
+ if (format === "top_bar") {
6334
+ if (!sponsorRow(line2.text, colsRef.current, "network")) return;
6335
+ setSponsor({ text: line2.text, origin: "network", impressionId: drawnId });
6336
+ } else {
6337
+ setAdSlots((slots) => ({ ...slots, [format]: { text: line2.text, impressionId: drawnId } }));
6338
+ }
6339
+ pendingDirectRef.current.set(format, { call, serveToken: line2.serveToken, drawnId });
6340
+ };
6341
+ const fitting = AD_FORMATS.filter((format) => adFitsFormat(format, colsRef.current));
6342
+ void (async () => {
6343
+ let direct = [];
6344
+ if (directSupportedRef.current && fitting.length > 0) {
6345
+ const ask3 = sponsorDirect ?? ((formats, cols2, signal) => client.directSponsor(formats, cols2, signal));
6346
+ let answer = { supported: true, lines: [] };
6347
+ try {
6348
+ answer = await ask3(fitting, colsRef.current, AbortSignal.timeout(DIRECT_ASK_TIMEOUT_MS));
6349
+ } catch {
6350
+ }
6351
+ if (!answer.supported) directSupportedRef.current = false;
6352
+ direct = answer.lines;
6353
+ }
6354
+ if (call !== sponsorCallRef.current) return;
6355
+ for (const format of ["top_bar", ...EXTRA_AD_FORMATS]) {
6356
+ if (!adFitsFormat(format, colsRef.current)) continue;
6357
+ const booked = direct.find((line2) => line2.format === format);
6358
+ if (booked && "hold" in booked) continue;
6359
+ if (booked) paintDirect(booked);
6360
+ else fetchNetwork(format);
6361
+ }
6362
+ })();
6146
6363
  },
6147
- [config.sponsor, stdout, wallet]
6364
+ [config.sponsor, stdout, wallet, client, sponsorDirect]
6148
6365
  );
6149
6366
  const reportSponsor = useCallback(() => {
6150
6367
  const pending = [...pendingImpressionRef.current.values()];
6368
+ const pendingDirect = [...pendingDirectRef.current.entries()];
6151
6369
  const drawn = drawnRef.current;
6152
6370
  pendingImpressionRef.current = /* @__PURE__ */ new Map();
6371
+ pendingDirectRef.current = /* @__PURE__ */ new Map();
6153
6372
  drawnRef.current = /* @__PURE__ */ new Set();
6373
+ const directReports = pendingDirect.filter(([, p]) => p.call === sponsorCallRef.current && drawn.has(p.drawnId)).map(([format, p]) => ({ format, serveToken: p.serveToken }));
6374
+ if (directReports.length > 0) void client.reportDirectImpressions(directReports);
6154
6375
  const reports = pending.filter((p) => p.call === sponsorCallRef.current).filter((p) => drawn.has(p.impression.impressionId)).map((p) => p.impression);
6155
6376
  if (reports.length === 0) return;
6156
6377
  void (async () => {
@@ -7469,7 +7690,16 @@ ${dropped.map((l) => ` \xB7 ${l}`).join("\n")}`
7469
7690
  }, 0);
7470
7691
  }, [entries, cols]);
7471
7692
  const used = chromeHeight + menu.length + pickerHeight + askHeight + planHeight + buyHeight + busyHeight + liveHeight;
7472
- const spacer = Math.max(0, viewport - printed - used);
7693
+ const lastScreen = screenRef.current;
7694
+ const freshScreen = !lastScreen || lastScreen.epoch !== staticEpoch || lastScreen.rows !== rows || lastScreen.cols !== cols || printed < lastScreen.printed;
7695
+ const onScreen = Math.max(
7696
+ 0,
7697
+ Math.min(freshScreen ? printed : lastScreen.onScreen + (printed - lastScreen.printed), viewport - used)
7698
+ );
7699
+ const spacer = Math.max(0, viewport - onScreen - used);
7700
+ useLayoutEffect(() => {
7701
+ screenRef.current = { epoch: staticEpoch, rows, cols, printed, onScreen };
7702
+ });
7473
7703
  const labelW = Math.min(
7474
7704
  Math.max(1, Math.floor(cols * 0.5)),
7475
7705
  menu.reduce((w, c2) => Math.max(w, visibleLength(c2.label)), 0)
@@ -7806,6 +8036,7 @@ var init_app = __esm({
7806
8036
  init_provider();
7807
8037
  init_network();
7808
8038
  init_formats();
8039
+ init_direct();
7809
8040
  init_render();
7810
8041
  init_commands();
7811
8042
  init_editor();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "clixad",
3
- "version": "0.0.1-beta.23",
3
+ "version": "0.0.1-beta.25",
4
4
  "description": "Free AI coding agent in your terminal, funded by rewarded ads.",
5
5
  "license": "UNLICENSED",
6
6
  "type": "module",