clixad 0.0.1-beta.36 → 0.0.1-beta.38

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 CHANGED
@@ -15,7 +15,7 @@ cover it, for example `task ≈ 560–3.1k · free` or `task ≈ 7.5k–47k · n
15
15
  estimate from the size of the conversation and how tasks usually run; what is charged is what the
16
16
  model actually used.
17
17
 
18
- The same account also works in the browser, at <https://clixad.onrender.com/chat>: sign in with the
18
+ The same account also works in the browser, at <https://app.clixad.io/chat>: sign in with the
19
19
  GitHub account you use here. A model, a reasoning setting, your name and instructions set there or on
20
20
  its account page hold here too (from 0.0.1-beta.31), and `/model` and `/effort` here hold there.
21
21
 
@@ -275,13 +275,11 @@ function accessColumn(m, premium) {
275
275
  function premiumLocked(premium) {
276
276
  return premium !== void 0 && !premium.unlocked && premium.credits <= 0;
277
277
  }
278
- function premiumUnlockHint(unlockUsd, streak) {
279
- const base = typeof unlockUsd === "number" && unlockUsd > 0 ? `premium: a survey or ${usd(unlockUsd)} unlocks it` : "premium: a survey unlocks it";
280
- const s = usableStreak(streak);
281
- return s && s.day < s.min_day ? `${base}, or a ${s.min_day}-day streak` : base;
278
+ function premiumUnlockHint(unlockUsd) {
279
+ return typeof unlockUsd === "number" && unlockUsd > 0 ? `premium: an offer-wall survey or ${usd(unlockUsd)} unlocks it` : "premium: an offer-wall survey unlocks it";
282
280
  }
283
281
  function premiumHeadline(unlockUsd) {
284
- return typeof unlockUsd === "number" && unlockUsd > 0 ? `Premium: complete a survey or unlock from ${usd(unlockUsd)}.` : "Premium: complete a survey to unlock it.";
282
+ return typeof unlockUsd === "number" && unlockUsd > 0 ? `Premium: complete an offer-wall survey or buy credits from ${usd(unlockUsd)}.` : "Premium: complete an offer-wall survey to unlock it.";
285
283
  }
286
284
  function usd(v) {
287
285
  return Number.isInteger(v) ? `$${v}` : `$${v.toFixed(2)}`;
@@ -296,33 +294,6 @@ function parseSpendable(raw) {
296
294
  if (!ok(r.budget) || !ok(r.premium) || !ok(r.budget_after_reset)) return void 0;
297
295
  return { budget: r.budget, premium: r.premium, budgetAfterReset: r.budget_after_reset };
298
296
  }
299
- function usableStreak(streak) {
300
- if (typeof streak !== "object" || streak === null) return void 0;
301
- const s = streak;
302
- const num = (v) => typeof v === "number" && Number.isFinite(v) ? v : void 0;
303
- const day = num(s.day);
304
- const minDay = num(s.min_day);
305
- const daily = num(s.daily_credits);
306
- const left = num(s.credits_left);
307
- if (day === void 0 || minDay === void 0 || daily === void 0 || left === void 0 || daily <= 0) {
308
- return void 0;
309
- }
310
- return { day, min_day: minDay, daily_credits: daily, credits_left: left };
311
- }
312
- function premiumStreakLine(streak) {
313
- const s = usableStreak(streak);
314
- if (!s) return null;
315
- const n = (x) => x.toLocaleString("en-US");
316
- if (s.day < s.min_day) {
317
- const wait = s.min_day - s.day;
318
- const when = wait === 1 ? "tomorrow" : wait === 2 ? "the day after tomorrow" : `in ${wait} days`;
319
- return `From day ${s.min_day} of your login streak, ${n(s.daily_credits)} bonus credits a day work on premium models too. You are on day ${s.day}, so that starts ${when} if you open Clixad every day.`;
320
- }
321
- if (s.credits_left <= 0) {
322
- return `Today's ${n(s.daily_credits)} bonus credits for premium models are used up. They come back at 00:00 UTC while your login streak holds.`;
323
- }
324
- return `Your login streak lets ${n(s.credits_left)} more bonus credits go to premium models today.`;
325
- }
326
297
  function adsPerTaskLabel(estAdsPerTask) {
327
298
  const effort = estAdsPerTask >= 1 ? `~${estAdsPerTask} ads/task` : "<1 ad/task";
328
299
  return `${effort} on completion (median)`;
@@ -340,18 +311,7 @@ function streakLabel(streak) {
340
311
  if (!streak.claimed) return `daily streak: ${where} \xB7 today's already counted`;
341
312
  const tomorrow = streak.schedule[Math.min(streak.day, streak.length - 1)];
342
313
  const next = tomorrow === void 0 ? "" : ` \xB7 tomorrow ${n(tomorrow)}`;
343
- return `daily streak: ${where} \xB7 +${n(streak.credits)} credits${next}${streakPremiumClause(streak)}`;
344
- }
345
- function streakPremiumClause(streak) {
346
- const p = streak.premium;
347
- if (!p) return "";
348
- return streak.day < p.minDay ? ` \xB7 bonus credits on premium from day ${p.minDay}` : ` \xB7 ${p.dailyCredits.toLocaleString("en-US")} bonus credits a day on premium`;
349
- }
350
- function streakPremium(raw) {
351
- if (typeof raw !== "object" || raw === null) return {};
352
- const p = raw;
353
- if (typeof p.min_day !== "number" || typeof p.daily_credits !== "number" || p.daily_credits <= 0) return {};
354
- return { premium: { minDay: p.min_day, dailyCredits: p.daily_credits } };
314
+ return `daily streak: ${where} \xB7 +${n(streak.credits)} credits${next}`;
355
315
  }
356
316
  function referLines(info) {
357
317
  const n = (x) => x.toLocaleString("en-US");
@@ -481,13 +441,14 @@ var SignupClosedError = class extends Error {
481
441
  waitlistUrl;
482
442
  };
483
443
  var PaywallError = class extends Error {
484
- constructor(balance, estimatedCost, grantPerAd, freeModels = [], cheapestPackUsd = null) {
444
+ constructor(balance, estimatedCost, grantPerAd, freeModels = [], cheapestPackUsd = null, ownSurvey = null) {
485
445
  super("Out of credits");
486
446
  this.balance = balance;
487
447
  this.estimatedCost = estimatedCost;
488
448
  this.grantPerAd = grantPerAd;
489
449
  this.freeModels = freeModels;
490
450
  this.cheapestPackUsd = cheapestPackUsd;
451
+ this.ownSurvey = ownSurvey;
491
452
  this.name = "PaywallError";
492
453
  }
493
454
  balance;
@@ -495,9 +456,10 @@ var PaywallError = class extends Error {
495
456
  grantPerAd;
496
457
  freeModels;
497
458
  cheapestPackUsd;
459
+ ownSurvey;
498
460
  };
499
461
  var PremiumLockedError = class extends Error {
500
- constructor(message, model, neededCredits, premiumCredits, unlockUsd, grantPerAd, freeModels, streak, spendable) {
462
+ constructor(message, model, neededCredits, premiumCredits, unlockUsd, grantPerAd, freeModels, spendable) {
501
463
  super(message);
502
464
  this.model = model;
503
465
  this.neededCredits = neededCredits;
@@ -505,7 +467,6 @@ var PremiumLockedError = class extends Error {
505
467
  this.unlockUsd = unlockUsd;
506
468
  this.grantPerAd = grantPerAd;
507
469
  this.freeModels = freeModels;
508
- this.streak = streak;
509
470
  this.spendable = spendable;
510
471
  this.name = "PremiumLockedError";
511
472
  }
@@ -515,7 +476,6 @@ var PremiumLockedError = class extends Error {
515
476
  unlockUsd;
516
477
  grantPerAd;
517
478
  freeModels;
518
- streak;
519
479
  spendable;
520
480
  };
521
481
  var LimitRefusalError = class extends Error {
@@ -560,7 +520,6 @@ function premiumLockedError(message, model, body, spendable) {
560
520
  typeof body?.unlock_usd === "number" && body.unlock_usd > 0 ? body.unlock_usd : null,
561
521
  num(body?.grant_per_ad),
562
522
  freeAlternatives(body?.free_models),
563
- usableStreak(body?.streak),
564
523
  spendable
565
524
  );
566
525
  }
@@ -576,9 +535,16 @@ function paywallError(body) {
576
535
  body.estimated_cost,
577
536
  body.grant_per_ad,
578
537
  freeAlternatives(body.free_models),
579
- typeof pack === "number" && Number.isFinite(pack) && pack > 0 ? pack : null
538
+ typeof pack === "number" && Number.isFinite(pack) && pack > 0 ? pack : null,
539
+ ownSurveyOffer(body.own_survey)
580
540
  );
581
541
  }
542
+ function ownSurveyOffer(raw) {
543
+ if (typeof raw !== "object" || raw === null) return null;
544
+ const o = raw;
545
+ const ok = (v) => typeof v === "number" && Number.isFinite(v) && v > 0;
546
+ return ok(o.credits) && ok(o.left_today) ? { credits: o.credits, leftToday: Math.floor(o.left_today) } : null;
547
+ }
582
548
  function freeAlternatives(list) {
583
549
  return Array.isArray(list) ? list.filter(
584
550
  (m) => !!m && typeof m.id === "string" && m.id.length > 0 && Number.isFinite(m.turns_left)
@@ -876,8 +842,7 @@ var GatewayClient = class {
876
842
  claimed: body.claimed === true,
877
843
  balance: typeof body.balance === "number" ? body.balance : 0,
878
844
  schedule: Array.isArray(body.schedule) ? body.schedule : [],
879
- length: typeof body.length === "number" ? body.length : 0,
880
- ...streakPremium(body.premium)
845
+ length: typeof body.length === "number" ? body.length : 0
881
846
  };
882
847
  } catch {
883
848
  return null;
@@ -1558,12 +1523,14 @@ var CONFIG_PATH = join(homedir(), ".clixad", "config.json");
1558
1523
  var QUARANTINE_PATH = `${CONFIG_PATH}.corrupt`;
1559
1524
  var FALLBACK_MODEL = "gemini-2.5-flash-lite";
1560
1525
  var DEFAULTS = {
1526
+ // Where the token was minted and is bound (`tokenGateway`), so it stays put:
1527
+ // moving it would sign every install out. app.clixad.io is the same server.
1561
1528
  gatewayUrl: "https://clixad.onrender.com",
1562
- // Same origin as the gateway: the ad wall is served by the gateway process
1563
- // itself now (apps/gateway/src/dashboard.ts), not by a second service. The two
1564
- // fields stay separate so either can be pointed elsewhere, but they no longer
1565
- // diverge by default.
1566
- dashboardUrl: "https://clixad.onrender.com",
1529
+ // The ad wall is served by the gateway process itself (apps/gateway/src/
1530
+ // dashboard.ts), at its public address, app.clixad.io since 2026-10-20. A
1531
+ // browser opened here is signed in by the handoff code, so the two fields
1532
+ // may name different hosts of one gateway.
1533
+ dashboardUrl: "https://app.clixad.io",
1567
1534
  model: FALLBACK_MODEL
1568
1535
  };
1569
1536
  var ENV_OVERRIDES = [
@@ -1572,7 +1539,9 @@ var ENV_OVERRIDES = [
1572
1539
  ];
1573
1540
  var SUPERSEDED_DEFAULTS = {
1574
1541
  gatewayUrl: ["http://127.0.0.1:8787", "http://localhost:8787"],
1575
- dashboardUrl: ["http://127.0.0.1:8788", "http://localhost:8788"]
1542
+ // clixad.onrender.com still answers, so an install that kept it would work;
1543
+ // it moves because the earn page's address is app.clixad.io (2026-10-20).
1544
+ dashboardUrl: ["http://127.0.0.1:8788", "http://localhost:8788", "https://clixad.onrender.com"]
1576
1545
  };
1577
1546
  var IDENTITY_KEYS = ["token", "userId", "email", "login", "tokenGateway"];
1578
1547
  function sameEndpoint(a, b) {
@@ -6265,7 +6234,6 @@ export {
6265
6234
  usd,
6266
6235
  packsFromClause,
6267
6236
  parseSpendable,
6268
- premiumStreakLine,
6269
6237
  adsPerTaskLabel,
6270
6238
  grantLabel,
6271
6239
  streakLabel,
package/dist/clixad.mjs CHANGED
@@ -32,7 +32,6 @@ import {
32
32
  performLogin,
33
33
  premiumHeadline,
34
34
  premiumLocked,
35
- premiumStreakLine,
36
35
  redeemMessage,
37
36
  referLines,
38
37
  runAgent,
@@ -47,7 +46,7 @@ import {
47
46
  takeConfigNotices,
48
47
  toolSchemaFor,
49
48
  usd
50
- } from "./chunk-5I5QG3FG.mjs";
49
+ } from "./chunk-HB4BPQH4.mjs";
51
50
 
52
51
  // src/tui/load.ts
53
52
  import * as nodeModule from "node:module";
@@ -126,7 +125,7 @@ async function withProductionReact(load) {
126
125
  }
127
126
  function loadTui() {
128
127
  boundInkCaches();
129
- return withProductionReact(() => import("./tui-XJFTG3ZM.mjs"));
128
+ return withProductionReact(() => import("./tui-57KDGV6V.mjs"));
130
129
  }
131
130
 
132
131
  // src/title.ts
@@ -336,7 +335,7 @@ async function listModels(client) {
336
335
  function printPremiumRefusal(err) {
337
336
  console.log(c.yellow(premiumHeadline(err.unlockUsd)));
338
337
  console.log(
339
- c.yellow(`${err.model} is a premium model. It runs on credits you earned on the offer walls or bought.`)
338
+ c.yellow(`${err.model} is a premium model. It runs on credits from offer-wall surveys or purchases.`)
340
339
  );
341
340
  if (err.premiumCredits > 0) {
342
341
  console.log(
@@ -345,11 +344,11 @@ function printPremiumRefusal(err) {
345
344
  )
346
345
  );
347
346
  }
348
- const streakLine = premiumStreakLine(err.streak);
349
- if (streakLine) console.log(c.yellow(streakLine));
350
347
  console.log(c.yellow(`Run \`clixad earn\` for a survey on the offer walls. Offers pay ${grantLabel(err.grantPerAd)}.`));
351
348
  if (err.unlockUsd) {
352
- console.log(c.yellow(`Or unlock premium from ${usd(err.unlockUsd)}: start \`clixad\` and type /buy.`));
349
+ console.log(
350
+ c.yellow(`Or buy credits from ${usd(err.unlockUsd)}, which work on every model: start \`clixad\` and type /buy.`)
351
+ );
353
352
  }
354
353
  const freeLine = freeAlternativeLine(err.freeModels);
355
354
  console.log(c.green(freeLine ?? "Your bonus credits still work on budget models: `clixad models` lists them."));
@@ -375,11 +374,9 @@ async function setModel(client, config, model) {
375
374
  console.log(c.yellow(premiumHeadline(client.premiumUnlockUsd)));
376
375
  console.log(
377
376
  c.yellow(
378
- `${model} runs on credits you earned on the offer walls or bought. Run \`clixad earn\` for a survey` + (client.premiumUnlockUsd ? `, or start \`clixad\` and type /buy.` : ".")
377
+ `${model} runs on credits from offer-wall surveys or purchases. Run \`clixad earn\` for a survey on the offer walls` + (client.premiumUnlockUsd ? `, or start \`clixad\` and type /buy.` : ".")
379
378
  )
380
379
  );
381
- const streakLine = premiumStreakLine(w?.premium?.streak);
382
- if (streakLine) console.log(c.yellow(streakLine));
383
380
  }
384
381
  }
385
382
  }
@@ -60,7 +60,6 @@ import {
60
60
  preferencesInstruction,
61
61
  premiumHeadline,
62
62
  premiumLocked,
63
- premiumStreakLine,
64
63
  premiumUnlockHint,
65
64
  redeemMessage,
66
65
  referLines,
@@ -85,7 +84,7 @@ import {
85
84
  usd,
86
85
  visibleLength,
87
86
  welcomeBanner
88
- } from "./chunk-5I5QG3FG.mjs";
87
+ } from "./chunk-HB4BPQH4.mjs";
89
88
 
90
89
  // src/tui/index.tsx
91
90
  import "react";
@@ -1806,17 +1805,15 @@ ${preferencesText}` : ""),
1806
1805
  if (err instanceof PremiumLockedError) {
1807
1806
  pendingTaskRef.current = { task, images };
1808
1807
  pendingPremiumRef.current = err;
1809
- setPremium({ unlocked: false, credits: err.premiumCredits, ...err.streak ? { streak: err.streak } : {} });
1808
+ setPremium({ unlocked: false, credits: err.premiumCredits });
1810
1809
  const short = err.premiumCredits > 0 ? ` This turn needs about ${err.neededCredits.toLocaleString("en-US")} of those credits, and you have ${err.premiumCredits.toLocaleString("en-US")}.
1811
1810
  ` : "";
1812
- const streakLine = premiumStreakLine(err.streak);
1813
1811
  push({
1814
1812
  kind: "notice",
1815
1813
  tone: "warn",
1816
1814
  text: ` ${premiumHeadline(err.unlockUsd)}
1817
- ${err.model} is a premium model. It runs on credits you earned on the offer walls or bought.
1818
- ` + short + (streakLine ? ` ${streakLine}
1819
- ` : "") + " Your bonus credits (the signup bonus, our own surveys) still work on budget models.\n Press enter to unlock it or to switch to a budget model. Your request is kept."
1815
+ ${err.model} is a premium model. It runs on credits from offer-wall surveys or purchases.
1816
+ ` + short + " Your bonus credits (the signup bonus, our own surveys) still work on budget models.\n Press enter to unlock it or to switch to a budget model. Your request is kept."
1820
1817
  });
1821
1818
  } else if (err instanceof PaywallError) {
1822
1819
  pendingTaskRef.current = { task, images };
@@ -1824,15 +1821,17 @@ ${preferencesText}` : ""),
1824
1821
  pendingGrantRef.current = err.grantPerAd;
1825
1822
  pendingBalanceRef.current = err.balance;
1826
1823
  const free = err.freeModels[0];
1824
+ const own = err.ownSurvey ? ` Our own surveys pay ${err.ownSurvey.credits.toLocaleString("en-US")} credits each and nobody is screened out (${err.ownSurvey.leftToday} left today). They are first on the wall.
1825
+ ` : "";
1827
1826
  push({
1828
1827
  kind: "notice",
1829
1828
  tone: "warn",
1830
1829
  text: free ? ` ${shortfallHeadline(err, model2)}
1831
- Press enter \u2014 ${free.id} is still free today (~${free.turnsLeft} turn${free.turnsLeft === 1 ? "" : "s"} left), or take an offer instead.
1830
+ ` + own + ` Press enter \u2014 ${free.id} is still free today (~${free.turnsLeft} turn${free.turnsLeft === 1 ? "" : "s"} left), or take an offer instead.
1832
1831
  Your request is kept either way, so you will not have to type it again.
1833
1832
  Offers you are screened out of pay nothing; that is normal, just start another.
1834
1833
  /buy is there too${packsFromClause(err.cheapestPackUsd)}, if you would rather pay than do an offer.` : ` ${shortfallHeadline(err, model2)}
1835
- Press enter to open the ad wall \u2014 offers pay ${grantLabel(err.grantPerAd)}.
1834
+ ` + own + ` Press enter to open the ad wall \u2014 offers pay ${grantLabel(err.grantPerAd)}.
1836
1835
  I'll continue automatically. Offers you are screened out of pay nothing;
1837
1836
  that is normal, just start another.
1838
1837
  ` + // A wallet that is short rather than empty has a third way on,
@@ -1984,6 +1983,8 @@ ${preferencesText}` : ""),
1984
1983
  const runAdWall = useCallback(async () => {
1985
1984
  const pending = pendingTaskRef.current;
1986
1985
  pendingTaskRef.current = null;
1986
+ const premiumNeed = pendingPremiumRef.current;
1987
+ let toldBonus = false;
1987
1988
  let before = balance ?? (await client.wallet().catch(() => void 0))?.balance;
1988
1989
  const code = await client.handoffCode();
1989
1990
  const base = config.dashboardUrl.replace(/\/+$/, "");
@@ -2007,7 +2008,23 @@ ${preferencesText}` : ""),
2007
2008
  setBalance(w.balance);
2008
2009
  } else if (w && before !== void 0 && w.balance > before) {
2009
2010
  setBalance(w.balance);
2010
- credited = true;
2011
+ if (!premiumNeed) {
2012
+ credited = true;
2013
+ continue;
2014
+ }
2015
+ const p = (await client.wallet(signal, { premium: true }).catch(() => void 0))?.premium;
2016
+ if (!p || p.unlocked || p.credits >= premiumNeed.neededCredits) {
2017
+ credited = true;
2018
+ } else {
2019
+ before = w.balance;
2020
+ if (!toldBonus) {
2021
+ toldBonus = true;
2022
+ push({
2023
+ kind: "notice",
2024
+ text: " bonus credits landed. They pay for budget models; this premium request is still\n waiting for credits from an offer-wall survey."
2025
+ });
2026
+ }
2027
+ }
2011
2028
  }
2012
2029
  }
2013
2030
  stopped = signal.aborted;
@@ -2104,14 +2121,16 @@ ${preferencesText}` : ""),
2104
2121
  const items = [
2105
2122
  {
2106
2123
  value: EARN_ROW,
2107
- label: "complete a survey",
2108
- hint: `opens the offer walls, offers pay ${grantLabel(refusal.grantPerAd)}`
2124
+ label: "complete an offer-wall survey",
2125
+ // The label already says where; the hint is what an offer pays, and
2126
+ // at 100 columns the longer phrasing cut the figure in half.
2127
+ hint: `offers pay ${grantLabel(refusal.grantPerAd)}`
2109
2128
  },
2110
2129
  ...refusal.unlockUsd ? [
2111
2130
  {
2112
2131
  value: BUY_ROW,
2113
- label: `unlock from ${usd(refusal.unlockUsd)}`,
2114
- hint: "any pack, and premium then takes all your credits"
2132
+ label: `buy credits from ${usd(refusal.unlockUsd)}`,
2133
+ hint: "bought credits work on every model"
2115
2134
  }
2116
2135
  ] : [],
2117
2136
  ...refusal.freeModels.filter((m) => m.id !== EARN_ROW && m.id !== BUY_ROW).map((m) => ({
@@ -2298,7 +2317,7 @@ ${preferencesText}` : ""),
2298
2317
  const items = models.map((m) => ({
2299
2318
  value: m.id,
2300
2319
  label: m.id,
2301
- hint: accessColumn(m, premium2) + (m.access === "premium" && premiumLocked(premium2) ? premiumUnlockHint(unlockUsd, premium2?.streak) : modelHint(m, free)),
2320
+ hint: accessColumn(m, premium2) + (m.access === "premium" && premiumLocked(premium2) ? premiumUnlockHint(unlockUsd) : modelHint(m, free)),
2302
2321
  current: m.id === model
2303
2322
  }));
2304
2323
  const preselect = items.findIndex((i) => i.value === arg);
@@ -2314,14 +2333,12 @@ ${preferencesText}` : ""),
2314
2333
  push({ kind: "notice", text: ` model \u2192 ${choice.value}` });
2315
2334
  const picked = models.find((m) => m.id === choice.value);
2316
2335
  if (picked?.access === "premium" && premiumLocked(premium2)) {
2317
- const streakLine = premiumStreakLine(premium2?.streak);
2318
2336
  push({
2319
2337
  kind: "notice",
2320
2338
  tone: "warn",
2321
2339
  text: ` ${premiumHeadline(unlockUsd)}
2322
- ${choice.value} runs on credits you earned on the offer walls or bought.
2323
- ` + (streakLine ? ` ${streakLine}
2324
- ` : "") + " /earn opens the offer walls" + (unlockUsd ? `, /buy unlocks premium from ${usd(unlockUsd)}.` : ".") + " Your bonus credits work on budget models."
2340
+ ${choice.value} runs on credits from offer-wall surveys or purchases.
2341
+ /earn opens the offer walls` + (unlockUsd ? `, /buy sells credits from ${usd(unlockUsd)} that work on every model.` : ".") + " Your bonus credits work on budget models."
2325
2342
  });
2326
2343
  }
2327
2344
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "clixad",
3
- "version": "0.0.1-beta.36",
3
+ "version": "0.0.1-beta.38",
4
4
  "description": "Free AI coding agent in your terminal, funded by rewarded ads.",
5
5
  "license": "UNLICENSED",
6
6
  "type": "module",