clixad 0.0.1-beta.45 → 0.0.1-beta.46

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.
@@ -30,22 +30,53 @@ function parseDirectLines(body) {
30
30
  seen.add(e.format);
31
31
  continue;
32
32
  }
33
- if (typeof e.text !== "string" || typeof e.serve_token !== "string" || typeof e.click_url !== "string") continue;
34
- const text = sanitizeAdText(e.text).slice(0, MAX_TEXT);
35
- if (!text || !e.serve_token || e.serve_token.length > 2e3) continue;
36
- let clickUrl;
37
- try {
38
- const url = new URL(e.click_url);
39
- if (url.protocol !== "https:" && url.protocol !== "http:") continue;
40
- clickUrl = url.toString();
41
- } catch {
42
- continue;
43
- }
44
- out.push({ format: e.format, text, serveToken: e.serve_token, clickUrl });
33
+ const line = readServedLine(e);
34
+ if (!line) continue;
35
+ out.push(line);
45
36
  seen.add(e.format);
46
37
  }
47
38
  return out;
48
39
  }
40
+ function parseFallbackLines(body) {
41
+ const raw = body?.fallbacks;
42
+ if (!Array.isArray(raw)) return [];
43
+ const out = [];
44
+ for (const item of raw) {
45
+ if (!item || typeof item !== "object") continue;
46
+ const line = readServedLine(item);
47
+ if (!line || out.some((o) => o.format === line.format)) continue;
48
+ out.push(line);
49
+ }
50
+ return out;
51
+ }
52
+ function readServedLine(e) {
53
+ if (!isAdFormat(e.format)) return null;
54
+ if (typeof e.text !== "string" || typeof e.serve_token !== "string" || typeof e.click_url !== "string") return null;
55
+ const text = sanitizeAdText(e.text).slice(0, MAX_TEXT);
56
+ if (!text || !e.serve_token || e.serve_token.length > 2e3) return null;
57
+ let clickUrl;
58
+ try {
59
+ const url = new URL(e.click_url);
60
+ if (url.protocol !== "https:" && url.protocol !== "http:") return null;
61
+ clickUrl = url.toString();
62
+ } catch {
63
+ return null;
64
+ }
65
+ return { format: e.format, text, serveToken: e.serve_token, clickUrl };
66
+ }
67
+ var MAX_LINK_TEXT = 40;
68
+ function shortLinkText(clickUrl) {
69
+ let url;
70
+ try {
71
+ url = new URL(clickUrl);
72
+ } catch {
73
+ return void 0;
74
+ }
75
+ if (url.protocol !== "https:" || url.search || url.hash || url.username || url.password) return void 0;
76
+ const text = `${url.host}${url.pathname === "/" ? "" : url.pathname}`;
77
+ if (text.length > MAX_LINK_TEXT || !/^[\x21-\x7e]+$/.test(text)) return void 0;
78
+ return text;
79
+ }
49
80
 
50
81
  // src/edge.ts
51
82
  function detectEdgeBlock(status, headers, body) {
@@ -1107,7 +1138,9 @@ var GatewayClient = class {
1107
1138
  });
1108
1139
  if (res.status === 404) return { supported: false, lines: [] };
1109
1140
  if (!res.ok) return { supported: true, lines: [], failed: true };
1110
- return { supported: true, lines: parseDirectLines(await res.json()) };
1141
+ const body = await res.json();
1142
+ const fallbacks = parseFallbackLines(body);
1143
+ return { supported: true, lines: parseDirectLines(body), ...fallbacks.length > 0 ? { fallbacks } : {} };
1111
1144
  } catch {
1112
1145
  return { supported: true, lines: [], failed: true };
1113
1146
  }
@@ -1133,6 +1166,27 @@ var GatewayClient = class {
1133
1166
  return 0;
1134
1167
  }
1135
1168
  }
1169
+ /**
1170
+ * Report the fallback lines a turn drew, in one call, after the turn is
1171
+ * over. They are counted and earn nothing, because nobody pays for them being
1172
+ * shown, so this answers how many were counted. Never throws.
1173
+ */
1174
+ async reportAffiliateImpressions(impressions, signal) {
1175
+ if (impressions.length === 0) return 0;
1176
+ try {
1177
+ const res = await gatewayFetch(`${this.config.gatewayUrl}/v1/sponsor/affiliate/impression`, {
1178
+ method: "POST",
1179
+ headers: this.headers(),
1180
+ ...signal ? { signal } : {},
1181
+ body: JSON.stringify({ impressions: impressions.map((i) => ({ format: i.format, serve_token: i.serveToken })) })
1182
+ });
1183
+ if (!res.ok) return 0;
1184
+ const body = await res.json();
1185
+ return typeof body.counted === "number" ? body.counted : 0;
1186
+ } catch {
1187
+ return 0;
1188
+ }
1189
+ }
1136
1190
  /**
1137
1191
  * Search the web, through the gateway.
1138
1192
  *
@@ -6573,6 +6627,7 @@ export {
6573
6627
  AD_FORMATS,
6574
6628
  EXTRA_AD_FORMATS,
6575
6629
  DIRECT_ASK_TIMEOUT_MS,
6630
+ shortLinkText,
6576
6631
  accessColumn,
6577
6632
  premiumLocked,
6578
6633
  premiumUnlockHint,
package/dist/clixad.mjs CHANGED
@@ -54,7 +54,7 @@ import {
54
54
  takeConfigNotices,
55
55
  toolSchemaFor,
56
56
  usd
57
- } from "./chunk-Y55DBUP6.mjs";
57
+ } from "./chunk-5GX5GUR4.mjs";
58
58
 
59
59
  // src/tui/load.ts
60
60
  import * as nodeModule from "node:module";
@@ -133,7 +133,7 @@ async function withProductionReact(load) {
133
133
  }
134
134
  function loadTui() {
135
135
  boundInkCaches();
136
- return withProductionReact(() => import("./tui-KH7DNN65.mjs"));
136
+ return withProductionReact(() => import("./tui-2AEK7HJV.mjs"));
137
137
  }
138
138
 
139
139
  // src/title.ts
@@ -89,6 +89,7 @@ import {
89
89
  savedEffort,
90
90
  shells,
91
91
  shortCredits,
92
+ shortLinkText,
92
93
  shortfallHeadline,
93
94
  shouldCompact,
94
95
  shouldSuggestInit,
@@ -104,7 +105,7 @@ import {
104
105
  visibleLength,
105
106
  webSearchCostLine,
106
107
  welcomeBanner
107
- } from "./chunk-Y55DBUP6.mjs";
108
+ } from "./chunk-5GX5GUR4.mjs";
108
109
 
109
110
  // src/tui/index.tsx
110
111
  import "react";
@@ -276,21 +277,28 @@ var SIDEBAR_MIN_COLS = 50 + SIDEBAR_CARD_COLS;
276
277
  function adFitsFormat(format, cols) {
277
278
  return format === "sidebar_card" ? cols >= SIDEBAR_MIN_COLS : adFitsWidth(cols);
278
279
  }
279
- function sidebarCard(line2, cols) {
280
+ var LINK_SEPARATOR = " \xB7 ";
281
+ function sidebarCard(line2, cols, linkText) {
280
282
  if (!adFitsFormat("sidebar_card", cols)) return null;
281
283
  const text = line2.trim();
282
284
  if (!text) return null;
283
285
  const body = visibleLength(text) <= SIDEBAR_BODY_COLS ? text : `${sliceColumns(text, SIDEBAR_BODY_COLS - 1).trimEnd()}\u2026`;
284
- return { rule: SIDEBAR_RULE, label: `${SPONSOR_SIGIL} ${AD_LABEL}`, body };
286
+ const label = `${SPONSOR_SIGIL} ${AD_LABEL}`;
287
+ const link = linkText && visibleLength(label) + visibleLength(LINK_SEPARATOR) + visibleLength(linkText) <= SIDEBAR_BODY_COLS ? linkText : null;
288
+ return { rule: SIDEBAR_RULE, label, link, body };
285
289
  }
286
- function sponsorRow(line2, cols, origin) {
290
+ function sponsorRow(line2, cols, origin, linkText) {
287
291
  const marker = origin === "house" || cols >= AD_FULL_PREFIX_MIN_COLS ? HOUSE_MARKER_ROW : AD_MARKER_ROW;
288
292
  const label = origin === "network" ? LABEL_ROW : null;
289
293
  if (origin === "network" && !adFitsWidth(cols)) return null;
290
294
  const room = cols - visibleLength(marker) - (label ? visibleLength(label) : 0);
291
295
  if (room < MIN_BODY_COLS) return null;
296
+ if (origin === "network" && linkText) {
297
+ const whole = visibleLength(line2) + visibleLength(LINK_SEPARATOR) + visibleLength(linkText);
298
+ if (whole <= room) return { marker, label, body: line2, link: linkText, origin };
299
+ }
292
300
  const body = visibleLength(line2) <= room ? line2 : `${sliceColumns(line2, room - 1).trimEnd()}\u2026`;
293
- return { marker, label, body, origin };
301
+ return { marker, label, body, link: null, origin };
294
302
  }
295
303
  function adColorEnabled({ isTTY, env }) {
296
304
  if (!colorEnabled({ isTTY, env })) return false;
@@ -1880,12 +1888,8 @@ function App({
1880
1888
  const [startedAt, setStartedAt] = useState(0);
1881
1889
  const [quitHint, setQuitHint] = useState(false);
1882
1890
  const [busyLabel, setBusyLabel] = useState(WORKING);
1883
- const [sponsor, setSponsor] = useState(
1884
- null
1885
- );
1886
- const [adSlots, setAdSlots] = useState(
1887
- {}
1888
- );
1891
+ const [sponsor, setSponsor] = useState(null);
1892
+ const [adSlots, setAdSlots] = useState({});
1889
1893
  const messagesRef = useRef(messages);
1890
1894
  messagesRef.current = messages;
1891
1895
  const busyRef = useRef(false);
@@ -2010,6 +2014,7 @@ function App({
2010
2014
  const sponsorCallRef = useRef(0);
2011
2015
  const pendingImpressionRef = useRef(/* @__PURE__ */ new Map());
2012
2016
  const pendingDirectRef = useRef(/* @__PURE__ */ new Map());
2017
+ const pendingFallbackRef = useRef(/* @__PURE__ */ new Map());
2013
2018
  const directSupportedRef = useRef(true);
2014
2019
  const knownBookedRef = useRef(/* @__PURE__ */ new Set());
2015
2020
  const drawnRef = useRef(/* @__PURE__ */ new Set());
@@ -2277,9 +2282,13 @@ function App({
2277
2282
  } else {
2278
2283
  setSponsor(null);
2279
2284
  }
2280
- const fetchNetwork = (format) => {
2285
+ const fetchNetwork = (format, fallback) => {
2281
2286
  void remoteSponsorRef.current[format].fetch().then((line2) => {
2282
- if (!line2 || call !== sponsorCallRef.current) return;
2287
+ if (call !== sponsorCallRef.current) return;
2288
+ if (!line2) {
2289
+ if (fallback) paintFallback(fallback);
2290
+ return;
2291
+ }
2283
2292
  if (!adFitsFormat(format, colsRef.current)) return;
2284
2293
  if (format !== "top_bar" && modalOpenRef.current) return;
2285
2294
  if (format === "top_bar") {
@@ -2311,9 +2320,27 @@ function App({
2311
2320
  }
2312
2321
  pendingDirectRef.current.set(format, { call, serveToken: line2.serveToken, drawnId });
2313
2322
  };
2323
+ const paintFallback = (line2) => {
2324
+ const { format } = line2;
2325
+ if (call !== sponsorCallRef.current) return;
2326
+ if (!adFitsFormat(format, colsRef.current)) return;
2327
+ if (format !== "top_bar" && modalOpenRef.current) return;
2328
+ const drawnId = `fallback:${call}:${format}`;
2329
+ const link = line2.clickUrl;
2330
+ const linkText = shortLinkText(link);
2331
+ const extra = linkText ? { link, linkText } : { link };
2332
+ if (format === "top_bar") {
2333
+ if (!sponsorRow(line2.text, colsRef.current, "network", linkText)) return;
2334
+ setSponsor({ text: line2.text, origin: "network", impressionId: drawnId, ...extra });
2335
+ } else {
2336
+ setAdSlots((slots) => ({ ...slots, [format]: { text: line2.text, impressionId: drawnId, ...extra } }));
2337
+ }
2338
+ pendingFallbackRef.current.set(format, { call, serveToken: line2.serveToken, drawnId });
2339
+ };
2314
2340
  const fitting = AD_FORMATS.filter((format) => adFitsFormat(format, colsRef.current));
2315
2341
  void (async () => {
2316
2342
  let direct = [];
2343
+ let fallbacks = [];
2317
2344
  let failed2 = false;
2318
2345
  if (directSupportedRef.current && fitting.length > 0) {
2319
2346
  const ask2 = sponsorDirect ?? ((formats, cols2, signal) => client.directSponsor(formats, cols2, signal));
@@ -2326,6 +2353,7 @@ function App({
2326
2353
  if (!answer.supported) directSupportedRef.current = false;
2327
2354
  failed2 = answer.failed === true;
2328
2355
  direct = answer.lines;
2356
+ fallbacks = answer.fallbacks ?? [];
2329
2357
  if (!failed2) knownBookedRef.current = new Set(direct.map((line2) => line2.format));
2330
2358
  }
2331
2359
  if (call !== sponsorCallRef.current) return;
@@ -2335,7 +2363,7 @@ function App({
2335
2363
  if (booked && "hold" in booked) continue;
2336
2364
  if (booked) paintDirect(booked);
2337
2365
  else if (failed2 && knownBookedRef.current.has(format)) continue;
2338
- else fetchNetwork(format);
2366
+ else fetchNetwork(format, fallbacks.find((line2) => line2.format === format));
2339
2367
  }
2340
2368
  })();
2341
2369
  },
@@ -2344,12 +2372,16 @@ function App({
2344
2372
  const reportSponsor = useCallback(() => {
2345
2373
  const pending = [...pendingImpressionRef.current.values()];
2346
2374
  const pendingDirect = [...pendingDirectRef.current.entries()];
2375
+ const pendingFallback = [...pendingFallbackRef.current.entries()];
2347
2376
  const drawn = drawnRef.current;
2348
2377
  pendingImpressionRef.current = /* @__PURE__ */ new Map();
2349
2378
  pendingDirectRef.current = /* @__PURE__ */ new Map();
2379
+ pendingFallbackRef.current = /* @__PURE__ */ new Map();
2350
2380
  drawnRef.current = /* @__PURE__ */ new Set();
2351
2381
  const directReports = pendingDirect.filter(([, p]) => p.call === sponsorCallRef.current && drawn.has(p.drawnId)).map(([format, p]) => ({ format, serveToken: p.serveToken }));
2352
2382
  if (directReports.length > 0) void client.reportDirectImpressions(directReports);
2383
+ const fallbackReports = pendingFallback.filter(([, p]) => p.call === sponsorCallRef.current && drawn.has(p.drawnId)).map(([format, p]) => ({ format, serveToken: p.serveToken }));
2384
+ if (fallbackReports.length > 0) void client.reportAffiliateImpressions(fallbackReports);
2353
2385
  const reports = pending.filter((p) => p.call === sponsorCallRef.current).filter((p) => drawn.has(p.impression.impressionId)).map((p) => p.impression);
2354
2386
  if (reports.length === 0) return;
2355
2387
  void (async () => {
@@ -3991,13 +4023,13 @@ ${dropped.map((l) => ` \xB7 ${l}`).join("\n")}`
3991
4023
  });
3992
4024
  const elapsed = busy && startedAt ? Math.floor((Date.now() - startedAt) / 1e3) : 0;
3993
4025
  const spinner = spinnerRef.current[Math.floor(tick / SPINNER_TICKS_PER_FRAME) % spinnerRef.current.length];
3994
- const sponsorLine = busy && sponsor ? sponsorRow(sponsor.text, cols, sponsor.origin) : null;
4026
+ const sponsorLine = busy && sponsor ? sponsorRow(sponsor.text, cols, sponsor.origin, sponsor.linkText) : null;
3995
4027
  const adColor = adColorEnabled({ isTTY: stdout.isTTY, env: process.env });
3996
4028
  const linkable = hyperlinksEnabled({ isTTY: stdout.isTTY, env: process.env });
3997
4029
  const modalOpen = Boolean(picker || ask || plan || buy);
3998
4030
  modalOpenRef.current = modalOpen;
3999
- const card = busy && !modalOpen && adSlots.sidebar_card ? sidebarCard(adSlots.sidebar_card.text, cols) : null;
4000
- const statusBarLine = busy && !modalOpen && adSlots.status_bar ? sponsorRow(adSlots.status_bar.text, cols, "network") : null;
4031
+ const card = busy && !modalOpen && adSlots.sidebar_card ? sidebarCard(adSlots.sidebar_card.text, cols, adSlots.sidebar_card.linkText) : null;
4032
+ const statusBarLine = busy && !modalOpen && adSlots.status_bar ? sponsorRow(adSlots.status_bar.text, cols, "network", adSlots.status_bar.linkText) : null;
4001
4033
  const viewport = Math.max(1, rows - 1);
4002
4034
  const inputRows = Math.max(1, Math.min(MAX_INPUT_ROWS, editor.lines.length));
4003
4035
  const inputFrom = windowStart(editor.row, editor.lines.length, inputRows);
@@ -4276,7 +4308,11 @@ ${dropped.map((l) => ` \xB7 ${l}`).join("\n")}`
4276
4308
  /* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", width: SIDEBAR_CARD_COLS, children: [
4277
4309
  /* @__PURE__ */ jsxs2(Box2, { children: [
4278
4310
  /* @__PURE__ */ jsx2(Text2, { dimColor: true, children: card.rule }),
4279
- /* @__PURE__ */ jsx2(Text2, { ...adColor ? { color: AD_MARK } : { dimColor: true }, wrap: "truncate-end", children: card.label })
4311
+ /* @__PURE__ */ jsx2(Text2, { ...adColor ? { color: AD_MARK } : { dimColor: true }, wrap: "truncate-end", children: card.label }),
4312
+ card.link ? /* @__PURE__ */ jsxs2(Text2, { ...adColor ? { color: AD_TEXT } : { dimColor: true }, wrap: "truncate-end", children: [
4313
+ LINK_SEPARATOR,
4314
+ /* @__PURE__ */ jsx2(Text2, { underline: adColor, children: linkable && adSlots.sidebar_card?.link ? osc8(card.link, adSlots.sidebar_card.link) : card.link })
4315
+ ] }) : null
4280
4316
  ] }),
4281
4317
  /* @__PURE__ */ jsxs2(Box2, { children: [
4282
4318
  /* @__PURE__ */ jsx2(Text2, { dimColor: true, children: card.rule }),
@@ -4365,7 +4401,13 @@ function SponsorRowView({ row, adColor, link }) {
4365
4401
  return /* @__PURE__ */ jsxs2(Box2, { children: [
4366
4402
  /* @__PURE__ */ jsx2(Text2, { dimColor: true, wrap: "truncate-end", children: row.marker }),
4367
4403
  row.label ? /* @__PURE__ */ jsx2(Text2, { ...adColor ? { color: AD_MARK } : { dimColor: true }, wrap: "truncate-end", children: row.label }) : null,
4368
- /* @__PURE__ */ jsx2(Text2, { ...adColor && row.origin === "network" ? { color: AD_TEXT } : { dimColor: true }, wrap: "truncate-end", children: link && row.origin === "network" ? osc8(row.body, link) : row.body })
4404
+ /* @__PURE__ */ jsxs2(Text2, { ...adColor && row.origin === "network" ? { color: AD_TEXT } : { dimColor: true }, wrap: "truncate-end", children: [
4405
+ link && row.origin === "network" ? osc8(row.body, link) : row.body,
4406
+ row.link ? /* @__PURE__ */ jsxs2(Fragment, { children: [
4407
+ LINK_SEPARATOR,
4408
+ /* @__PURE__ */ jsx2(Text2, { underline: adColor, children: link ? osc8(row.link, link) : row.link })
4409
+ ] }) : null
4410
+ ] })
4369
4411
  ] });
4370
4412
  }
4371
4413
  function sleep(ms, signal) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "clixad",
3
- "version": "0.0.1-beta.45",
3
+ "version": "0.0.1-beta.46",
4
4
  "description": "Free AI coding agent in your terminal, funded by rewarded ads.",
5
5
  "license": "Apache-2.0",
6
6
  "type": "module",