clixad 0.0.1-beta.46 → 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
  }
@@ -62,7 +66,8 @@ function readServedLine(e) {
62
66
  } catch {
63
67
  return null;
64
68
  }
65
- return { format: e.format, text, serveToken: e.serve_token, clickUrl };
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 } : {} };
66
71
  }
67
72
  var MAX_LINK_TEXT = 40;
68
73
  function shortLinkText(clickUrl) {
@@ -1140,7 +1145,13 @@ var GatewayClient = class {
1140
1145
  if (!res.ok) return { supported: true, lines: [], failed: true };
1141
1146
  const body = await res.json();
1142
1147
  const fallbacks = parseFallbackLines(body);
1143
- return { supported: true, lines: parseDirectLines(body), ...fallbacks.length > 0 ? { fallbacks } : {} };
1148
+ const measure = parseMeasure(body);
1149
+ return {
1150
+ supported: true,
1151
+ lines: parseDirectLines(body),
1152
+ ...fallbacks.length > 0 ? { fallbacks } : {},
1153
+ ...measure ? { measure } : {}
1154
+ };
1144
1155
  } catch {
1145
1156
  return { supported: true, lines: [], failed: true };
1146
1157
  }
@@ -1157,7 +1168,14 @@ var GatewayClient = class {
1157
1168
  method: "POST",
1158
1169
  headers: this.headers(),
1159
1170
  ...signal ? { signal } : {},
1160
- 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
+ })
1161
1179
  });
1162
1180
  if (!res.ok) return 0;
1163
1181
  const body = await res.json();
@@ -1166,6 +1184,27 @@ var GatewayClient = class {
1166
1184
  return 0;
1167
1185
  }
1168
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
+ }
1169
1208
  /**
1170
1209
  * Report the fallback lines a turn drew, in one call, after the turn is
1171
1210
  * over. They are counted and earn nothing, because nobody pays for them being
package/dist/clixad.mjs CHANGED
@@ -54,7 +54,7 @@ import {
54
54
  takeConfigNotices,
55
55
  toolSchemaFor,
56
56
  usd
57
- } from "./chunk-5GX5GUR4.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-2AEK7HJV.mjs"));
136
+ return withProductionReact(() => import("./tui-US36BPP3.mjs"));
137
137
  }
138
138
 
139
139
  // src/title.ts
@@ -105,7 +105,7 @@ import {
105
105
  visibleLength,
106
106
  webSearchCostLine,
107
107
  welcomeBanner
108
- } from "./chunk-5GX5GUR4.mjs";
108
+ } from "./chunk-TKGMAY4C.mjs";
109
109
 
110
110
  // src/tui/index.tsx
111
111
  import "react";
@@ -287,19 +287,25 @@ function sidebarCard(line2, cols, linkText) {
287
287
  const link = linkText && visibleLength(label) + visibleLength(LINK_SEPARATOR) + visibleLength(linkText) <= SIDEBAR_BODY_COLS ? linkText : null;
288
288
  return { rule: SIDEBAR_RULE, label, link, body };
289
289
  }
290
- function sponsorRow(line2, cols, origin, linkText) {
290
+ function sponsorRow(line2, cols, origin, linkText, linkRequired = false) {
291
291
  const marker = origin === "house" || cols >= AD_FULL_PREFIX_MIN_COLS ? HOUSE_MARKER_ROW : AD_MARKER_ROW;
292
292
  const label = origin === "network" ? LABEL_ROW : null;
293
293
  if (origin === "network" && !adFitsWidth(cols)) return null;
294
294
  const room = cols - visibleLength(marker) - (label ? visibleLength(label) : 0);
295
295
  if (room < MIN_BODY_COLS) return null;
296
296
  if (origin === "network" && linkText) {
297
- const whole = visibleLength(line2) + visibleLength(LINK_SEPARATOR) + visibleLength(linkText);
297
+ const linkCols = visibleLength(LINK_SEPARATOR) + visibleLength(linkText);
298
+ const whole = visibleLength(line2) + linkCols;
298
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
+ }
299
304
  }
300
305
  const body = visibleLength(line2) <= room ? line2 : `${sliceColumns(line2, room - 1).trimEnd()}\u2026`;
301
306
  return { marker, label, body, link: null, origin };
302
307
  }
308
+ var MIN_LINKED_COPY_COLS = 12;
303
309
  function adColorEnabled({ isTTY, env }) {
304
310
  if (!colorEnabled({ isTTY, env })) return false;
305
311
  if (env.CI !== void 0 && env.CI !== "") return false;
@@ -356,6 +362,46 @@ function createSponsorSlot(config, format = "top_bar") {
356
362
  };
357
363
  }
358
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
+
359
405
  // src/sponsor/hyperlink.ts
360
406
  function version(v) {
361
407
  const m = /^(\d+)(?:\.(\d+))?/.exec(v ?? "");
@@ -2018,6 +2064,9 @@ function App({
2018
2064
  const directSupportedRef = useRef(true);
2019
2065
  const knownBookedRef = useRef(/* @__PURE__ */ new Set());
2020
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);
2021
2070
  const sponsorSlotRef = useRef(
2022
2071
  /* @__PURE__ */ new Map()
2023
2072
  );
@@ -2260,6 +2309,9 @@ function App({
2260
2309
  const nextSponsor = useCallback(
2261
2310
  (modelId) => {
2262
2311
  const call = ++sponsorCallRef.current;
2312
+ visibilityRef.current.reset();
2313
+ linkModeRef.current = /* @__PURE__ */ new Map();
2314
+ measureRef.current = null;
2263
2315
  if (!sponsorEnabled({
2264
2316
  isTTY: stdout.isTTY,
2265
2317
  env: process.env,
@@ -2312,11 +2364,14 @@ function App({
2312
2364
  if (format !== "top_bar" && modalOpenRef.current) return;
2313
2365
  const drawnId = `direct:${call}:${format}`;
2314
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 };
2315
2370
  if (format === "top_bar") {
2316
- if (!sponsorRow(line2.text, colsRef.current, "network")) return;
2317
- 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 });
2318
2373
  } else {
2319
- setAdSlots((slots) => ({ ...slots, [format]: { text: line2.text, impressionId: drawnId, link } }));
2374
+ setAdSlots((slots) => ({ ...slots, [format]: { text: line2.text, impressionId: drawnId, ...extra } }));
2320
2375
  }
2321
2376
  pendingDirectRef.current.set(format, { call, serveToken: line2.serveToken, drawnId });
2322
2377
  };
@@ -2354,6 +2409,9 @@ function App({
2354
2409
  failed2 = answer.failed === true;
2355
2410
  direct = answer.lines;
2356
2411
  fallbacks = answer.fallbacks ?? [];
2412
+ if (answer.measure && call === sponsorCallRef.current) {
2413
+ measureRef.current = { call, token: answer.measure, answeredAt: Date.now(), formats: [...fitting] };
2414
+ }
2357
2415
  if (!failed2) knownBookedRef.current = new Set(direct.map((line2) => line2.format));
2358
2416
  }
2359
2417
  if (call !== sponsorCallRef.current) return;
@@ -2374,12 +2432,29 @@ function App({
2374
2432
  const pendingDirect = [...pendingDirectRef.current.entries()];
2375
2433
  const pendingFallback = [...pendingFallbackRef.current.entries()];
2376
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();
2377
2439
  pendingImpressionRef.current = /* @__PURE__ */ new Map();
2378
2440
  pendingDirectRef.current = /* @__PURE__ */ new Map();
2379
2441
  pendingFallbackRef.current = /* @__PURE__ */ new Map();
2380
2442
  drawnRef.current = /* @__PURE__ */ new Set();
2381
- 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
+ }));
2382
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
+ }
2383
2458
  const fallbackReports = pendingFallback.filter(([, p]) => p.call === sponsorCallRef.current && drawn.has(p.drawnId)).map(([format, p]) => ({ format, serveToken: p.serveToken }));
2384
2459
  if (fallbackReports.length > 0) void client.reportAffiliateImpressions(fallbackReports);
2385
2460
  const reports = pending.filter((p) => p.call === sponsorCallRef.current).filter((p) => drawn.has(p.impression.impressionId)).map((p) => p.impression);
@@ -4023,13 +4098,13 @@ ${dropped.map((l) => ` \xB7 ${l}`).join("\n")}`
4023
4098
  });
4024
4099
  const elapsed = busy && startedAt ? Math.floor((Date.now() - startedAt) / 1e3) : 0;
4025
4100
  const spinner = spinnerRef.current[Math.floor(tick / SPINNER_TICKS_PER_FRAME) % spinnerRef.current.length];
4026
- const sponsorLine = busy && sponsor ? sponsorRow(sponsor.text, cols, sponsor.origin, sponsor.linkText) : null;
4101
+ const sponsorLine = busy && sponsor ? sponsorRow(sponsor.text, cols, sponsor.origin, sponsor.linkText, sponsor.linkRequired === true) : null;
4027
4102
  const adColor = adColorEnabled({ isTTY: stdout.isTTY, env: process.env });
4028
4103
  const linkable = hyperlinksEnabled({ isTTY: stdout.isTTY, env: process.env });
4029
4104
  const modalOpen = Boolean(picker || ask || plan || buy);
4030
4105
  modalOpenRef.current = modalOpen;
4031
4106
  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;
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;
4033
4108
  const viewport = Math.max(1, rows - 1);
4034
4109
  const inputRows = Math.max(1, Math.min(MAX_INPUT_ROWS, editor.lines.length));
4035
4110
  const inputFrom = windowStart(editor.row, editor.lines.length, inputRows);
@@ -4060,9 +4135,24 @@ ${dropped.map((l) => ` \xB7 ${l}`).join("\n")}`
4060
4135
  showCard ? adSlots.sidebar_card?.impressionId : void 0,
4061
4136
  showStatusBar ? adSlots.status_bar?.impressionId : void 0
4062
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
+ }
4063
4152
  useEffect(() => {
4064
4153
  for (const id of drawnKey.split(" ")) if (id) drawnRef.current.add(id);
4065
- }, [drawnKey]);
4154
+ visibilityRef.current.observe([...drawnKey.split(" "), ...placesKey.split(" ")].filter(Boolean), Date.now());
4155
+ }, [drawnKey, placesKey]);
4066
4156
  let budget = Math.max(0, viewport - chromeHeight - menu.length - busyHeight);
4067
4157
  const pickerCompact = Boolean(picker) && budget < PICKER_CHROME + 1;
4068
4158
  const pickerChrome = pickerCompact ? PICKER_CHROME_COMPACT : PICKER_CHROME;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "clixad",
3
- "version": "0.0.1-beta.46",
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",