clixad 0.0.1-beta.45 → 0.0.1-beta.47

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.
@@ -13,6 +13,10 @@ function isAdFormat(value) {
13
13
  // src/sponsor/direct.ts
14
14
  var DIRECT_ASK_TIMEOUT_MS = 1500;
15
15
  var MAX_TEXT = 200;
16
+ function parseMeasure(body) {
17
+ const raw = body?.measure;
18
+ return typeof raw === "string" && raw.length > 0 && raw.length <= 2e3 ? raw : void 0;
19
+ }
16
20
  function sanitizeAdText(raw) {
17
21
  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();
18
22
  }
@@ -30,22 +34,54 @@ function parseDirectLines(body) {
30
34
  seen.add(e.format);
31
35
  continue;
32
36
  }
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 });
37
+ const line = readServedLine(e);
38
+ if (!line) continue;
39
+ out.push(line);
45
40
  seen.add(e.format);
46
41
  }
47
42
  return out;
48
43
  }
44
+ function parseFallbackLines(body) {
45
+ const raw = body?.fallbacks;
46
+ if (!Array.isArray(raw)) return [];
47
+ const out = [];
48
+ for (const item of raw) {
49
+ if (!item || typeof item !== "object") continue;
50
+ const line = readServedLine(item);
51
+ if (!line || out.some((o) => o.format === line.format)) continue;
52
+ out.push(line);
53
+ }
54
+ return out;
55
+ }
56
+ function readServedLine(e) {
57
+ if (!isAdFormat(e.format)) return null;
58
+ if (typeof e.text !== "string" || typeof e.serve_token !== "string" || typeof e.click_url !== "string") return null;
59
+ const text = sanitizeAdText(e.text).slice(0, MAX_TEXT);
60
+ if (!text || !e.serve_token || e.serve_token.length > 2e3) return null;
61
+ let clickUrl;
62
+ try {
63
+ const url = new URL(e.click_url);
64
+ if (url.protocol !== "https:" && url.protocol !== "http:") return null;
65
+ clickUrl = url.toString();
66
+ } catch {
67
+ return null;
68
+ }
69
+ const shortUrl = typeof e.short_url === "string" && shortLinkText(e.short_url) ? e.short_url : void 0;
70
+ return { format: e.format, text, serveToken: e.serve_token, clickUrl, ...shortUrl ? { shortUrl } : {} };
71
+ }
72
+ var MAX_LINK_TEXT = 40;
73
+ function shortLinkText(clickUrl) {
74
+ let url;
75
+ try {
76
+ url = new URL(clickUrl);
77
+ } catch {
78
+ return void 0;
79
+ }
80
+ if (url.protocol !== "https:" || url.search || url.hash || url.username || url.password) return void 0;
81
+ const text = `${url.host}${url.pathname === "/" ? "" : url.pathname}`;
82
+ if (text.length > MAX_LINK_TEXT || !/^[\x21-\x7e]+$/.test(text)) return void 0;
83
+ return text;
84
+ }
49
85
 
50
86
  // src/edge.ts
51
87
  function detectEdgeBlock(status, headers, body) {
@@ -1107,7 +1143,15 @@ var GatewayClient = class {
1107
1143
  });
1108
1144
  if (res.status === 404) return { supported: false, lines: [] };
1109
1145
  if (!res.ok) return { supported: true, lines: [], failed: true };
1110
- return { supported: true, lines: parseDirectLines(await res.json()) };
1146
+ const body = await res.json();
1147
+ const fallbacks = parseFallbackLines(body);
1148
+ const measure = parseMeasure(body);
1149
+ return {
1150
+ supported: true,
1151
+ lines: parseDirectLines(body),
1152
+ ...fallbacks.length > 0 ? { fallbacks } : {},
1153
+ ...measure ? { measure } : {}
1154
+ };
1111
1155
  } catch {
1112
1156
  return { supported: true, lines: [], failed: true };
1113
1157
  }
@@ -1124,7 +1168,14 @@ var GatewayClient = class {
1124
1168
  method: "POST",
1125
1169
  headers: this.headers(),
1126
1170
  ...signal ? { signal } : {},
1127
- body: JSON.stringify({ impressions: impressions.map((i) => ({ format: i.format, serve_token: i.serveToken })) })
1171
+ body: JSON.stringify({
1172
+ impressions: impressions.map((i) => ({
1173
+ format: i.format,
1174
+ serve_token: i.serveToken,
1175
+ ...i.visibleMs !== void 0 ? { visible_ms: Math.round(i.visibleMs) } : {},
1176
+ ...i.link ? { link: i.link } : {}
1177
+ }))
1178
+ })
1128
1179
  });
1129
1180
  if (!res.ok) return 0;
1130
1181
  const body = await res.json();
@@ -1133,6 +1184,48 @@ var GatewayClient = class {
1133
1184
  return 0;
1134
1185
  }
1135
1186
  }
1187
+ /**
1188
+ * Report how long each place a turn asked about stayed on screen with a
1189
+ * line in it, with the turn's measure token: what a booked week is
1190
+ * forecast on. Never throws; answers how many places counted.
1191
+ */
1192
+ async reportSponsorViews(measure, places, signal) {
1193
+ if (places.length === 0) return 0;
1194
+ try {
1195
+ const res = await gatewayFetch(`${this.config.gatewayUrl}/v1/sponsor/direct/views`, {
1196
+ method: "POST",
1197
+ headers: this.headers(),
1198
+ ...signal ? { signal } : {},
1199
+ body: JSON.stringify({ measure, places: places.map((p) => ({ format: p.format, visible_ms: Math.round(p.visibleMs) })) })
1200
+ });
1201
+ if (!res.ok) return 0;
1202
+ const body = await res.json();
1203
+ return typeof body.counted === "number" ? body.counted : 0;
1204
+ } catch {
1205
+ return 0;
1206
+ }
1207
+ }
1208
+ /**
1209
+ * Report the fallback lines a turn drew, in one call, after the turn is
1210
+ * over. They are counted and earn nothing, because nobody pays for them being
1211
+ * shown, so this answers how many were counted. Never throws.
1212
+ */
1213
+ async reportAffiliateImpressions(impressions, signal) {
1214
+ if (impressions.length === 0) return 0;
1215
+ try {
1216
+ const res = await gatewayFetch(`${this.config.gatewayUrl}/v1/sponsor/affiliate/impression`, {
1217
+ method: "POST",
1218
+ headers: this.headers(),
1219
+ ...signal ? { signal } : {},
1220
+ body: JSON.stringify({ impressions: impressions.map((i) => ({ format: i.format, serve_token: i.serveToken })) })
1221
+ });
1222
+ if (!res.ok) return 0;
1223
+ const body = await res.json();
1224
+ return typeof body.counted === "number" ? body.counted : 0;
1225
+ } catch {
1226
+ return 0;
1227
+ }
1228
+ }
1136
1229
  /**
1137
1230
  * Search the web, through the gateway.
1138
1231
  *
@@ -6573,6 +6666,7 @@ export {
6573
6666
  AD_FORMATS,
6574
6667
  EXTRA_AD_FORMATS,
6575
6668
  DIRECT_ASK_TIMEOUT_MS,
6669
+ shortLinkText,
6576
6670
  accessColumn,
6577
6671
  premiumLocked,
6578
6672
  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-TKGMAY4C.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-US36BPP3.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-TKGMAY4C.mjs";
108
109
 
109
110
  // src/tui/index.tsx
110
111
  import "react";
@@ -276,22 +277,35 @@ 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, linkRequired = false) {
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 linkCols = visibleLength(LINK_SEPARATOR) + visibleLength(linkText);
298
+ const whole = visibleLength(line2) + linkCols;
299
+ if (whole <= room) return { marker, label, body: line2, link: linkText, origin };
300
+ if (linkRequired && room - linkCols >= MIN_LINKED_COPY_COLS) {
301
+ const copy = room - linkCols;
302
+ return { marker, label, body: `${sliceColumns(line2, copy - 1).trimEnd()}\u2026`, link: linkText, origin };
303
+ }
304
+ }
292
305
  const body = visibleLength(line2) <= room ? line2 : `${sliceColumns(line2, room - 1).trimEnd()}\u2026`;
293
- return { marker, label, body, origin };
306
+ return { marker, label, body, link: null, origin };
294
307
  }
308
+ var MIN_LINKED_COPY_COLS = 12;
295
309
  function adColorEnabled({ isTTY, env }) {
296
310
  if (!colorEnabled({ isTTY, env })) return false;
297
311
  if (env.CI !== void 0 && env.CI !== "") return false;
@@ -348,6 +362,46 @@ function createSponsorSlot(config, format = "top_bar") {
348
362
  };
349
363
  }
350
364
 
365
+ // src/sponsor/visibility.ts
366
+ var VisibilityClock = class {
367
+ tracks = /* @__PURE__ */ new Map();
368
+ /** The keys on screen as of `now`. Every key not in `onScreen` is off screen from `now`. */
369
+ observe(onScreen, now) {
370
+ const visible = new Set(onScreen);
371
+ for (const [key, track] of this.tracks) {
372
+ if (track.since !== null && !visible.has(key)) {
373
+ track.done.push([track.since, now]);
374
+ track.since = null;
375
+ }
376
+ }
377
+ for (const key of visible) {
378
+ const track = this.tracks.get(key) ?? { since: null, done: [] };
379
+ if (track.since === null) track.since = now;
380
+ this.tracks.set(key, track);
381
+ }
382
+ }
383
+ /**
384
+ * The longest unbroken stretch `key` was on screen up to `now`, counting
385
+ * only time from `from` on (a place is measured from the moment a booked
386
+ * line could first have been drawn in it). Zero for a key never seen.
387
+ */
388
+ longest(key, now, from = Number.NEGATIVE_INFINITY) {
389
+ const track = this.tracks.get(key);
390
+ if (!track) return 0;
391
+ const stretches = track.since === null ? track.done : [...track.done, [track.since, now]];
392
+ let best = 0;
393
+ for (const [start, end] of stretches) best = Math.max(best, Math.min(end, now) - Math.max(start, from));
394
+ return Math.max(0, best);
395
+ }
396
+ /** Forget everything: a new turn measures from nothing. */
397
+ reset() {
398
+ this.tracks.clear();
399
+ }
400
+ };
401
+ function placeKey(format) {
402
+ return `place:${format}`;
403
+ }
404
+
351
405
  // src/sponsor/hyperlink.ts
352
406
  function version(v) {
353
407
  const m = /^(\d+)(?:\.(\d+))?/.exec(v ?? "");
@@ -1880,12 +1934,8 @@ function App({
1880
1934
  const [startedAt, setStartedAt] = useState(0);
1881
1935
  const [quitHint, setQuitHint] = useState(false);
1882
1936
  const [busyLabel, setBusyLabel] = useState(WORKING);
1883
- const [sponsor, setSponsor] = useState(
1884
- null
1885
- );
1886
- const [adSlots, setAdSlots] = useState(
1887
- {}
1888
- );
1937
+ const [sponsor, setSponsor] = useState(null);
1938
+ const [adSlots, setAdSlots] = useState({});
1889
1939
  const messagesRef = useRef(messages);
1890
1940
  messagesRef.current = messages;
1891
1941
  const busyRef = useRef(false);
@@ -2010,9 +2060,13 @@ function App({
2010
2060
  const sponsorCallRef = useRef(0);
2011
2061
  const pendingImpressionRef = useRef(/* @__PURE__ */ new Map());
2012
2062
  const pendingDirectRef = useRef(/* @__PURE__ */ new Map());
2063
+ const pendingFallbackRef = useRef(/* @__PURE__ */ new Map());
2013
2064
  const directSupportedRef = useRef(true);
2014
2065
  const knownBookedRef = useRef(/* @__PURE__ */ new Set());
2015
2066
  const drawnRef = useRef(/* @__PURE__ */ new Set());
2067
+ const visibilityRef = useRef(new VisibilityClock());
2068
+ const linkModeRef = useRef(/* @__PURE__ */ new Map());
2069
+ const measureRef = useRef(null);
2016
2070
  const sponsorSlotRef = useRef(
2017
2071
  /* @__PURE__ */ new Map()
2018
2072
  );
@@ -2255,6 +2309,9 @@ function App({
2255
2309
  const nextSponsor = useCallback(
2256
2310
  (modelId) => {
2257
2311
  const call = ++sponsorCallRef.current;
2312
+ visibilityRef.current.reset();
2313
+ linkModeRef.current = /* @__PURE__ */ new Map();
2314
+ measureRef.current = null;
2258
2315
  if (!sponsorEnabled({
2259
2316
  isTTY: stdout.isTTY,
2260
2317
  env: process.env,
@@ -2277,9 +2334,13 @@ function App({
2277
2334
  } else {
2278
2335
  setSponsor(null);
2279
2336
  }
2280
- const fetchNetwork = (format) => {
2337
+ const fetchNetwork = (format, fallback) => {
2281
2338
  void remoteSponsorRef.current[format].fetch().then((line2) => {
2282
- if (!line2 || call !== sponsorCallRef.current) return;
2339
+ if (call !== sponsorCallRef.current) return;
2340
+ if (!line2) {
2341
+ if (fallback) paintFallback(fallback);
2342
+ return;
2343
+ }
2283
2344
  if (!adFitsFormat(format, colsRef.current)) return;
2284
2345
  if (format !== "top_bar" && modalOpenRef.current) return;
2285
2346
  if (format === "top_bar") {
@@ -2303,17 +2364,38 @@ function App({
2303
2364
  if (format !== "top_bar" && modalOpenRef.current) return;
2304
2365
  const drawnId = `direct:${call}:${format}`;
2305
2366
  const link = line2.clickUrl;
2367
+ const linkable2 = hyperlinksEnabled({ isTTY: stdout.isTTY, env: process.env });
2368
+ const linkText = !linkable2 && line2.shortUrl ? shortLinkText(line2.shortUrl) : void 0;
2369
+ const extra = linkText ? { link, linkText, linkRequired: true } : { link };
2306
2370
  if (format === "top_bar") {
2307
- if (!sponsorRow(line2.text, colsRef.current, "network")) return;
2308
- setSponsor({ text: line2.text, origin: "network", impressionId: drawnId, link });
2371
+ if (!sponsorRow(line2.text, colsRef.current, "network", linkText, true)) return;
2372
+ setSponsor({ text: line2.text, origin: "network", impressionId: drawnId, ...extra });
2309
2373
  } else {
2310
- setAdSlots((slots) => ({ ...slots, [format]: { text: line2.text, impressionId: drawnId, link } }));
2374
+ setAdSlots((slots) => ({ ...slots, [format]: { text: line2.text, impressionId: drawnId, ...extra } }));
2311
2375
  }
2312
2376
  pendingDirectRef.current.set(format, { call, serveToken: line2.serveToken, drawnId });
2313
2377
  };
2378
+ const paintFallback = (line2) => {
2379
+ const { format } = line2;
2380
+ if (call !== sponsorCallRef.current) return;
2381
+ if (!adFitsFormat(format, colsRef.current)) return;
2382
+ if (format !== "top_bar" && modalOpenRef.current) return;
2383
+ const drawnId = `fallback:${call}:${format}`;
2384
+ const link = line2.clickUrl;
2385
+ const linkText = shortLinkText(link);
2386
+ const extra = linkText ? { link, linkText } : { link };
2387
+ if (format === "top_bar") {
2388
+ if (!sponsorRow(line2.text, colsRef.current, "network", linkText)) return;
2389
+ setSponsor({ text: line2.text, origin: "network", impressionId: drawnId, ...extra });
2390
+ } else {
2391
+ setAdSlots((slots) => ({ ...slots, [format]: { text: line2.text, impressionId: drawnId, ...extra } }));
2392
+ }
2393
+ pendingFallbackRef.current.set(format, { call, serveToken: line2.serveToken, drawnId });
2394
+ };
2314
2395
  const fitting = AD_FORMATS.filter((format) => adFitsFormat(format, colsRef.current));
2315
2396
  void (async () => {
2316
2397
  let direct = [];
2398
+ let fallbacks = [];
2317
2399
  let failed2 = false;
2318
2400
  if (directSupportedRef.current && fitting.length > 0) {
2319
2401
  const ask2 = sponsorDirect ?? ((formats, cols2, signal) => client.directSponsor(formats, cols2, signal));
@@ -2326,6 +2408,10 @@ function App({
2326
2408
  if (!answer.supported) directSupportedRef.current = false;
2327
2409
  failed2 = answer.failed === true;
2328
2410
  direct = answer.lines;
2411
+ fallbacks = answer.fallbacks ?? [];
2412
+ if (answer.measure && call === sponsorCallRef.current) {
2413
+ measureRef.current = { call, token: answer.measure, answeredAt: Date.now(), formats: [...fitting] };
2414
+ }
2329
2415
  if (!failed2) knownBookedRef.current = new Set(direct.map((line2) => line2.format));
2330
2416
  }
2331
2417
  if (call !== sponsorCallRef.current) return;
@@ -2335,7 +2421,7 @@ function App({
2335
2421
  if (booked && "hold" in booked) continue;
2336
2422
  if (booked) paintDirect(booked);
2337
2423
  else if (failed2 && knownBookedRef.current.has(format)) continue;
2338
- else fetchNetwork(format);
2424
+ else fetchNetwork(format, fallbacks.find((line2) => line2.format === format));
2339
2425
  }
2340
2426
  })();
2341
2427
  },
@@ -2344,12 +2430,33 @@ function App({
2344
2430
  const reportSponsor = useCallback(() => {
2345
2431
  const pending = [...pendingImpressionRef.current.values()];
2346
2432
  const pendingDirect = [...pendingDirectRef.current.entries()];
2433
+ const pendingFallback = [...pendingFallbackRef.current.entries()];
2347
2434
  const drawn = drawnRef.current;
2435
+ const clock = visibilityRef.current;
2436
+ const linkModes = linkModeRef.current;
2437
+ const measure = measureRef.current;
2438
+ const at = Date.now();
2348
2439
  pendingImpressionRef.current = /* @__PURE__ */ new Map();
2349
2440
  pendingDirectRef.current = /* @__PURE__ */ new Map();
2441
+ pendingFallbackRef.current = /* @__PURE__ */ new Map();
2350
2442
  drawnRef.current = /* @__PURE__ */ new Set();
2351
- const directReports = pendingDirect.filter(([, p]) => p.call === sponsorCallRef.current && drawn.has(p.drawnId)).map(([format, p]) => ({ format, serveToken: p.serveToken }));
2443
+ measureRef.current = null;
2444
+ const directReports = pendingDirect.filter(([, p]) => p.call === sponsorCallRef.current && drawn.has(p.drawnId)).map(([format, p]) => ({
2445
+ format,
2446
+ serveToken: p.serveToken,
2447
+ visibleMs: clock.longest(p.drawnId, at),
2448
+ link: linkModes.get(p.drawnId) ?? "none"
2449
+ }));
2352
2450
  if (directReports.length > 0) void client.reportDirectImpressions(directReports);
2451
+ if (measure && measure.call === sponsorCallRef.current) {
2452
+ const places = measure.formats.map((format) => ({
2453
+ format,
2454
+ visibleMs: clock.longest(placeKey(format), at, measure.answeredAt)
2455
+ }));
2456
+ void client.reportSponsorViews(measure.token, places);
2457
+ }
2458
+ const fallbackReports = pendingFallback.filter(([, p]) => p.call === sponsorCallRef.current && drawn.has(p.drawnId)).map(([format, p]) => ({ format, serveToken: p.serveToken }));
2459
+ if (fallbackReports.length > 0) void client.reportAffiliateImpressions(fallbackReports);
2353
2460
  const reports = pending.filter((p) => p.call === sponsorCallRef.current).filter((p) => drawn.has(p.impression.impressionId)).map((p) => p.impression);
2354
2461
  if (reports.length === 0) return;
2355
2462
  void (async () => {
@@ -3991,13 +4098,13 @@ ${dropped.map((l) => ` \xB7 ${l}`).join("\n")}`
3991
4098
  });
3992
4099
  const elapsed = busy && startedAt ? Math.floor((Date.now() - startedAt) / 1e3) : 0;
3993
4100
  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;
4101
+ const sponsorLine = busy && sponsor ? sponsorRow(sponsor.text, cols, sponsor.origin, sponsor.linkText, sponsor.linkRequired === true) : null;
3995
4102
  const adColor = adColorEnabled({ isTTY: stdout.isTTY, env: process.env });
3996
4103
  const linkable = hyperlinksEnabled({ isTTY: stdout.isTTY, env: process.env });
3997
4104
  const modalOpen = Boolean(picker || ask || plan || buy);
3998
4105
  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;
4106
+ const card = busy && !modalOpen && adSlots.sidebar_card ? sidebarCard(adSlots.sidebar_card.text, cols, adSlots.sidebar_card.linkText) : null;
4107
+ const statusBarLine = busy && !modalOpen && adSlots.status_bar ? sponsorRow(adSlots.status_bar.text, cols, "network", adSlots.status_bar.linkText, adSlots.status_bar.linkRequired === true) : null;
4001
4108
  const viewport = Math.max(1, rows - 1);
4002
4109
  const inputRows = Math.max(1, Math.min(MAX_INPUT_ROWS, editor.lines.length));
4003
4110
  const inputFrom = windowStart(editor.row, editor.lines.length, inputRows);
@@ -4028,9 +4135,24 @@ ${dropped.map((l) => ` \xB7 ${l}`).join("\n")}`
4028
4135
  showCard ? adSlots.sidebar_card?.impressionId : void 0,
4029
4136
  showStatusBar ? adSlots.status_bar?.impressionId : void 0
4030
4137
  ].filter(Boolean).join(" ");
4138
+ const placesKey = [
4139
+ showSponsor && sponsorLine ? placeKey("top_bar") : void 0,
4140
+ showCard ? placeKey("sidebar_card") : void 0,
4141
+ showStatusBar ? placeKey("status_bar") : void 0
4142
+ ].filter(Boolean).join(" ");
4143
+ if (showSponsor && sponsorLine && sponsor?.impressionId?.startsWith("direct:")) {
4144
+ linkModeRef.current.set(sponsor.impressionId, linkable ? "link" : sponsorLine.link ? "address" : "none");
4145
+ }
4146
+ if (showCard && card && adSlots.sidebar_card?.impressionId.startsWith("direct:")) {
4147
+ linkModeRef.current.set(adSlots.sidebar_card.impressionId, linkable ? "link" : card.link ? "address" : "none");
4148
+ }
4149
+ if (showStatusBar && statusBarLine && adSlots.status_bar?.impressionId.startsWith("direct:")) {
4150
+ linkModeRef.current.set(adSlots.status_bar.impressionId, linkable ? "link" : statusBarLine.link ? "address" : "none");
4151
+ }
4031
4152
  useEffect(() => {
4032
4153
  for (const id of drawnKey.split(" ")) if (id) drawnRef.current.add(id);
4033
- }, [drawnKey]);
4154
+ visibilityRef.current.observe([...drawnKey.split(" "), ...placesKey.split(" ")].filter(Boolean), Date.now());
4155
+ }, [drawnKey, placesKey]);
4034
4156
  let budget = Math.max(0, viewport - chromeHeight - menu.length - busyHeight);
4035
4157
  const pickerCompact = Boolean(picker) && budget < PICKER_CHROME + 1;
4036
4158
  const pickerChrome = pickerCompact ? PICKER_CHROME_COMPACT : PICKER_CHROME;
@@ -4276,7 +4398,11 @@ ${dropped.map((l) => ` \xB7 ${l}`).join("\n")}`
4276
4398
  /* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", width: SIDEBAR_CARD_COLS, children: [
4277
4399
  /* @__PURE__ */ jsxs2(Box2, { children: [
4278
4400
  /* @__PURE__ */ jsx2(Text2, { dimColor: true, children: card.rule }),
4279
- /* @__PURE__ */ jsx2(Text2, { ...adColor ? { color: AD_MARK } : { dimColor: true }, wrap: "truncate-end", children: card.label })
4401
+ /* @__PURE__ */ jsx2(Text2, { ...adColor ? { color: AD_MARK } : { dimColor: true }, wrap: "truncate-end", children: card.label }),
4402
+ card.link ? /* @__PURE__ */ jsxs2(Text2, { ...adColor ? { color: AD_TEXT } : { dimColor: true }, wrap: "truncate-end", children: [
4403
+ LINK_SEPARATOR,
4404
+ /* @__PURE__ */ jsx2(Text2, { underline: adColor, children: linkable && adSlots.sidebar_card?.link ? osc8(card.link, adSlots.sidebar_card.link) : card.link })
4405
+ ] }) : null
4280
4406
  ] }),
4281
4407
  /* @__PURE__ */ jsxs2(Box2, { children: [
4282
4408
  /* @__PURE__ */ jsx2(Text2, { dimColor: true, children: card.rule }),
@@ -4365,7 +4491,13 @@ function SponsorRowView({ row, adColor, link }) {
4365
4491
  return /* @__PURE__ */ jsxs2(Box2, { children: [
4366
4492
  /* @__PURE__ */ jsx2(Text2, { dimColor: true, wrap: "truncate-end", children: row.marker }),
4367
4493
  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 })
4494
+ /* @__PURE__ */ jsxs2(Text2, { ...adColor && row.origin === "network" ? { color: AD_TEXT } : { dimColor: true }, wrap: "truncate-end", children: [
4495
+ link && row.origin === "network" ? osc8(row.body, link) : row.body,
4496
+ row.link ? /* @__PURE__ */ jsxs2(Fragment, { children: [
4497
+ LINK_SEPARATOR,
4498
+ /* @__PURE__ */ jsx2(Text2, { underline: adColor, children: link ? osc8(row.link, link) : row.link })
4499
+ ] }) : null
4500
+ ] })
4369
4501
  ] });
4370
4502
  }
4371
4503
  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.47",
4
4
  "description": "Free AI coding agent in your terminal, funded by rewarded ads.",
5
5
  "license": "Apache-2.0",
6
6
  "type": "module",