clixad 0.0.1-beta.4 → 0.0.1-beta.6

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 +269 -114
  2. package/package.json +1 -1
package/dist/clixad.mjs CHANGED
@@ -88,6 +88,7 @@ var init_client = __esm({
88
88
  LEDGER_REASONS = {
89
89
  signup_bonus: "signup bonus",
90
90
  ad_reward: "offer reward",
91
+ ad_screenout_bonus: "screenout bonus (didn't qualify)",
91
92
  ad_reversal: "offer reward reversed by the provider",
92
93
  purchase: "credits purchased",
93
94
  usage: "model usage",
@@ -315,12 +316,25 @@ var init_client = __esm({
315
316
  import { chmodSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
316
317
  import { homedir } from "node:os";
317
318
  import { dirname, join } from "node:path";
319
+ function sameEndpoint(a, b) {
320
+ const norm = (raw) => {
321
+ try {
322
+ const url = new URL(raw);
323
+ const host = url.hostname === "localhost" ? "127.0.0.1" : url.hostname;
324
+ return `${url.protocol}//${host}:${url.port}${url.pathname.replace(/\/+$/, "")}`;
325
+ } catch {
326
+ return raw.trim().replace(/\/+$/, "");
327
+ }
328
+ };
329
+ return norm(a) === norm(b);
330
+ }
318
331
  function loadConfig() {
319
332
  let stored = {};
320
333
  try {
321
334
  stored = JSON.parse(readFileSync(CONFIG_PATH, "utf8"));
322
335
  } catch {
323
336
  }
337
+ const mintedAgainst = stored.gatewayUrl;
324
338
  for (const [key, stale] of Object.entries(SUPERSEDED_DEFAULTS)) {
325
339
  const value = stored[key];
326
340
  if (typeof value === "string" && stale.includes(value) && value !== DEFAULTS[key]) {
@@ -332,6 +346,10 @@ function loadConfig() {
332
346
  const value = process.env[envVar];
333
347
  if (value) config[key] = value;
334
348
  }
349
+ const migratedGateway = mintedAgainst !== void 0 && stored.gatewayUrl === void 0;
350
+ if (migratedGateway && !sameEndpoint(mintedAgainst, config.gatewayUrl)) {
351
+ for (const key of IDENTITY_KEYS) delete config[key];
352
+ }
335
353
  return config;
336
354
  }
337
355
  function saveConfig(config) {
@@ -345,7 +363,7 @@ function saveConfig(config) {
345
363
  function configPath() {
346
364
  return CONFIG_PATH;
347
365
  }
348
- var CONFIG_PATH, DEFAULTS, ENV_OVERRIDES, SUPERSEDED_DEFAULTS;
366
+ var CONFIG_PATH, DEFAULTS, ENV_OVERRIDES, SUPERSEDED_DEFAULTS, IDENTITY_KEYS;
349
367
  var init_config = __esm({
350
368
  "src/config.ts"() {
351
369
  "use strict";
@@ -367,6 +385,121 @@ var init_config = __esm({
367
385
  gatewayUrl: ["http://127.0.0.1:8787", "http://localhost:8787"],
368
386
  dashboardUrl: ["http://127.0.0.1:8788", "http://localhost:8788"]
369
387
  };
388
+ IDENTITY_KEYS = ["token", "userId", "email", "login"];
389
+ }
390
+ });
391
+
392
+ // src/browser.ts
393
+ import { spawn } from "node:child_process";
394
+ function openBrowser(url) {
395
+ const cmd = process.platform === "win32" ? "cmd" : process.platform === "darwin" ? "open" : "xdg-open";
396
+ const args = process.platform === "win32" ? ["/c", "start", "", url] : [url];
397
+ const child = spawn(cmd, args, { detached: true, stdio: "ignore", windowsHide: true });
398
+ child.on("error", () => void 0);
399
+ child.unref();
400
+ }
401
+ var init_browser = __esm({
402
+ "src/browser.ts"() {
403
+ "use strict";
404
+ }
405
+ });
406
+
407
+ // src/login.ts
408
+ function parseLoginArgs(args) {
409
+ let invite;
410
+ const rest = [];
411
+ for (let i = 0; i < args.length; i++) {
412
+ const a = args[i];
413
+ if (a === "--invite") invite = args[++i];
414
+ else if (a.startsWith("--invite=")) invite = a.slice("--invite=".length);
415
+ else rest.push(a);
416
+ }
417
+ return { invite: invite?.trim() || void 0, email: rest[0] };
418
+ }
419
+ async function performLogin(client, config, args, report, signal) {
420
+ if (!args.email) {
421
+ const start = await client.deviceStart().catch(() => null);
422
+ if (start) return githubLogin(client, config, start, args.invite, report, signal);
423
+ }
424
+ return devLogin(client, config, args.email, args.invite);
425
+ }
426
+ async function githubLogin(client, config, start, invite, report, signal) {
427
+ report.verify(start);
428
+ openBrowser(start.verification_uri);
429
+ const deadline = Date.now() + start.expires_in * 1e3;
430
+ let interval = Math.max(start.interval, 1);
431
+ while (Date.now() < deadline) {
432
+ await sleep(interval * 1e3, signal);
433
+ if (signal?.aborted) return { status: "aborted" };
434
+ report.tick?.();
435
+ const poll = await client.devicePoll(start.session, invite);
436
+ if (poll.status === "pending") {
437
+ if (poll.interval) interval = poll.interval;
438
+ continue;
439
+ }
440
+ if (poll.status === "closed") {
441
+ return { status: "closed", message: poll.message, hadInvite: Boolean(invite) };
442
+ }
443
+ if (poll.status === "complete") {
444
+ config.token = poll.token;
445
+ config.userId = poll.userId;
446
+ config.email = poll.email;
447
+ config.login = poll.login;
448
+ saveConfig(config);
449
+ return {
450
+ status: "ok",
451
+ account: {
452
+ email: poll.email,
453
+ login: poll.login,
454
+ balance: poll.balance,
455
+ created: poll.created,
456
+ signupBonus: poll.signupBonus
457
+ }
458
+ };
459
+ }
460
+ return { status: "failed", error: poll.error ?? poll.status };
461
+ }
462
+ return { status: "timeout" };
463
+ }
464
+ async function devLogin(client, config, email, invite) {
465
+ let res;
466
+ try {
467
+ res = await client.signupDev(email, invite);
468
+ } catch (err) {
469
+ if (err instanceof SignupClosedError) {
470
+ return { status: "closed", message: err.message, hadInvite: Boolean(invite) };
471
+ }
472
+ throw err;
473
+ }
474
+ config.token = res.token;
475
+ config.userId = res.userId;
476
+ config.email = res.email;
477
+ delete config.login;
478
+ saveConfig(config);
479
+ return { status: "ok", account: { email: res.email, balance: res.balance, created: true } };
480
+ }
481
+ function sleep(ms, signal) {
482
+ return new Promise((resolve2) => {
483
+ if (!signal) {
484
+ setTimeout(resolve2, ms);
485
+ return;
486
+ }
487
+ if (signal.aborted) return resolve2();
488
+ const done = () => {
489
+ clearTimeout(timer);
490
+ signal.removeEventListener("abort", done);
491
+ resolve2();
492
+ };
493
+ const timer = setTimeout(done, ms);
494
+ signal.addEventListener("abort", done, { once: true });
495
+ });
496
+ }
497
+ var init_login = __esm({
498
+ "src/login.ts"() {
499
+ "use strict";
500
+ init_client();
501
+ init_browser();
502
+ init_config();
370
503
  }
371
504
  });
372
505
 
@@ -493,7 +626,7 @@ var init_diff = __esm({
493
626
  });
494
627
 
495
628
  // src/tools.ts
496
- import { spawn } from "node:child_process";
629
+ import { spawn as spawn2 } from "node:child_process";
497
630
  import { existsSync, mkdirSync as mkdirSync2, readdirSync, readFileSync as readFileSync2, statSync, writeFileSync as writeFileSync2 } from "node:fs";
498
631
  import { isAbsolute, relative, resolve, dirname as dirname2, join as join2, sep } from "node:path";
499
632
  function safeResolve(root, p) {
@@ -531,7 +664,7 @@ function killTree(child) {
531
664
  return;
532
665
  }
533
666
  if (process.platform === "win32") {
534
- spawn("taskkill", ["/pid", String(child.pid), "/T", "/F"], { stdio: "ignore", windowsHide: true });
667
+ spawn2("taskkill", ["/pid", String(child.pid), "/T", "/F"], { stdio: "ignore", windowsHide: true });
535
668
  } else {
536
669
  try {
537
670
  process.kill(-child.pid, "SIGTERM");
@@ -542,7 +675,7 @@ function killTree(child) {
542
675
  }
543
676
  function execute(ctx, command, timeout) {
544
677
  return new Promise((resolvePromise) => {
545
- const child = spawn(command, {
678
+ const child = spawn2(command, {
546
679
  cwd: ctx.root,
547
680
  shell: true,
548
681
  windowsHide: true,
@@ -1237,7 +1370,7 @@ var init_session = __esm({
1237
1370
  });
1238
1371
 
1239
1372
  // src/kimi.ts
1240
- import { spawn as spawn2, spawnSync as spawnSync2 } from "node:child_process";
1373
+ import { spawn as spawn3, spawnSync as spawnSync2 } from "node:child_process";
1241
1374
  import { existsSync as existsSync4, mkdirSync as mkdirSync4, writeFileSync as writeFileSync4 } from "node:fs";
1242
1375
  import { homedir as homedir4 } from "node:os";
1243
1376
  import { dirname as dirname3, join as join5 } from "node:path";
@@ -1294,7 +1427,7 @@ function runKimiCode(config, task, opts = {}) {
1294
1427
  OPENAI_API_KEY: config.token
1295
1428
  };
1296
1429
  return new Promise((resolve2) => {
1297
- const child = process.platform === "win32" ? spawn2([bin, ...args].map(quoteWin).join(" "), { stdio: "inherit", env, shell: true }) : spawn2(bin, args, { stdio: "inherit", env });
1430
+ const child = process.platform === "win32" ? spawn3([bin, ...args].map(quoteWin).join(" "), { stdio: "inherit", env, shell: true }) : spawn3(bin, args, { stdio: "inherit", env });
1298
1431
  child.on("error", (err) => {
1299
1432
  if (err.code === "ENOENT") console.log("\n" + INSTALL_HELP);
1300
1433
  else console.error(`failed to launch Kimi CLI: ${err.message}`);
@@ -1482,21 +1615,6 @@ var init_banner = __esm({
1482
1615
  }
1483
1616
  });
1484
1617
 
1485
- // src/browser.ts
1486
- import { spawn as spawn3 } from "node:child_process";
1487
- function openBrowser(url) {
1488
- const cmd = process.platform === "win32" ? "cmd" : process.platform === "darwin" ? "open" : "xdg-open";
1489
- const args = process.platform === "win32" ? ["/c", "start", "", url] : [url];
1490
- const child = spawn3(cmd, args, { detached: true, stdio: "ignore", windowsHide: true });
1491
- child.on("error", () => void 0);
1492
- child.unref();
1493
- }
1494
- var init_browser = __esm({
1495
- "src/browser.ts"() {
1496
- "use strict";
1497
- }
1498
- });
1499
-
1500
1618
  // src/version.ts
1501
1619
  import { readFileSync as readFileSync5 } from "node:fs";
1502
1620
  function readVersion() {
@@ -1713,6 +1831,17 @@ var init_commands = __esm({
1713
1831
  // forms, surveys and app trials, and describing the top-up as a video costs
1714
1832
  // the user the one expectation that makes a screenout make sense.
1715
1833
  { name: "earn", desc: "open the offer wall to earn credits" },
1834
+ // Reachable from inside the REPL because that is where the 401 is printed.
1835
+ // "Run `clixad login` first" is not a runnable instruction at this prompt —
1836
+ // anything without a leading slash is a prompt, so it went to the model and
1837
+ // came back as the same 401.
1838
+ //
1839
+ // Deliberately *no* `args`, though `/login <email>` works: an arg hint makes
1840
+ // enter complete the command instead of running it, and this is the one
1841
+ // command whose whole job is to be the exit from a stuck REPL. The email form
1842
+ // is the dev shortcut, documented on `clixad login [email]`, not the path
1843
+ // somebody in a 401 loop needs.
1844
+ { name: "login", desc: "sign in, or switch account" },
1716
1845
  { name: "compact", desc: "summarise the conversation to free context" },
1717
1846
  { name: "clear", desc: "clear the conversation context" },
1718
1847
  { name: "init", desc: "write a CLIXAD.md for this project" },
@@ -2339,7 +2468,7 @@ function App({ client, config, wallet, session, initialTask }) {
2339
2468
  that is normal, just start another.`
2340
2469
  });
2341
2470
  } else if (err instanceof AuthError) {
2342
- push({ kind: "notice", tone: "error", text: ` ${err.message}` });
2471
+ push({ kind: "notice", tone: "error", text: NOT_SIGNED_IN });
2343
2472
  } else {
2344
2473
  push({ kind: "notice", tone: "error", text: ` error: ${err.message}` });
2345
2474
  }
@@ -2367,6 +2496,7 @@ function App({ client, config, wallet, session, initialTask }) {
2367
2496
  await fn(ac.signal);
2368
2497
  } catch (err) {
2369
2498
  if (ac.signal.aborted) push({ kind: "notice", tone: "warn", text: " (stopped)" });
2499
+ else if (err instanceof AuthError) push({ kind: "notice", tone: "error", text: NOT_SIGNED_IN });
2370
2500
  else push({ kind: "notice", tone: "error", text: ` ${err.message}` });
2371
2501
  } finally {
2372
2502
  busyAbortRef.current = null;
@@ -2376,6 +2506,56 @@ function App({ client, config, wallet, session, initialTask }) {
2376
2506
  },
2377
2507
  [push]
2378
2508
  );
2509
+ const runLogin = useCallback(
2510
+ async (arg) => {
2511
+ const args = parseLoginArgs(arg.split(/\s+/).filter(Boolean));
2512
+ await runBusy(SIGNING_IN, async (signal) => {
2513
+ const outcome = await performLogin(
2514
+ client,
2515
+ config,
2516
+ args,
2517
+ {
2518
+ verify: (start) => push({
2519
+ kind: "notice",
2520
+ text: ` Open ${start.verification_uri}
2521
+ and enter the code: ${start.user_code}
2522
+ Waiting for GitHub \u2014 esc to cancel.`
2523
+ })
2524
+ },
2525
+ signal
2526
+ );
2527
+ switch (outcome.status) {
2528
+ case "ok": {
2529
+ const { account } = outcome;
2530
+ setBalance(account.balance);
2531
+ push({
2532
+ kind: "notice",
2533
+ tone: "good",
2534
+ text: ` signed in as ${account.login ?? account.email ?? "you"} \xB7 balance ${account.balance.toLocaleString("en-US")} credits` + (account.created ? " (signup bonus)" : "")
2535
+ });
2536
+ return;
2537
+ }
2538
+ case "closed":
2539
+ push({
2540
+ kind: "notice",
2541
+ tone: "warn",
2542
+ text: ` Clixad isn't open yet.
2543
+ ${outcome.message}` + (outcome.hadInvite ? "\n That invite code wasn't accepted \u2014 check it for typos." : "")
2544
+ });
2545
+ return;
2546
+ case "timeout":
2547
+ push({ kind: "notice", tone: "warn", text: " login timed out \u2014 /login to try again." });
2548
+ return;
2549
+ case "aborted":
2550
+ push({ kind: "notice", tone: "warn", text: " (stopped) \u2014 /login to try again." });
2551
+ return;
2552
+ default:
2553
+ push({ kind: "notice", tone: "error", text: ` login failed: ${outcome.error}` });
2554
+ }
2555
+ });
2556
+ },
2557
+ [client, config, push, runBusy]
2558
+ );
2379
2559
  const runAdWall = useCallback(async () => {
2380
2560
  const task = pendingTaskRef.current;
2381
2561
  pendingTaskRef.current = null;
@@ -2394,7 +2574,7 @@ function App({ client, config, wallet, session, initialTask }) {
2394
2574
  await runBusy(WAITING_FOR_REWARD, async (signal) => {
2395
2575
  const deadline = Date.now() + 5 * 6e4;
2396
2576
  while (Date.now() < deadline && !credited && !signal.aborted) {
2397
- await sleep(3e3, signal);
2577
+ await sleep2(3e3, signal);
2398
2578
  if (signal.aborted) break;
2399
2579
  const w = await client.wallet(signal).catch(() => void 0);
2400
2580
  if (w && w.balance > before) {
@@ -2523,9 +2703,12 @@ function App({ client, config, wallet, session, initialTask }) {
2523
2703
  setBalance(w.balance);
2524
2704
  const reversals = w.ledger.filter((e) => e.reason === "ad_reversal");
2525
2705
  const reversed = reversals.reduce((sum, e) => sum + Math.abs(e.delta), 0);
2706
+ const screenouts = w.screenouts_today ?? 0;
2707
+ const screenoutCredits = w.screenout_credits_today ?? 0;
2526
2708
  push({
2527
2709
  kind: "notice",
2528
- text: ` balance ${w.balance.toLocaleString("en-US")} credits \xB7 ${w.ads_today} offers today \xB7 $${w.earned_usd_today.toFixed(2)}/$${w.max_reward_usd_per_day.toFixed(2)} earned` + (reversals.length ? `
2710
+ text: ` balance ${w.balance.toLocaleString("en-US")} credits \xB7 ${w.ads_today} offers today \xB7 $${w.earned_usd_today.toFixed(2)}/$${w.max_reward_usd_per_day.toFixed(2)} earned` + (screenouts ? `
2711
+ plus ${screenoutCredits.toLocaleString("en-US")} credits from ${screenouts} screenout bonus${screenouts === 1 ? "" : "es"} \u2014 attempts that didn't qualify, which CPX pays a little for anyway.` : "") + (reversals.length ? `
2529
2712
  recently: ${reversed.toLocaleString("en-US")} credits from ${reversals.length} offer${reversals.length === 1 ? "" : "s"} were reversed by the provider. Run \`clixad wallet\` for the full ledger.` : "")
2530
2713
  });
2531
2714
  });
@@ -2536,11 +2719,14 @@ function App({ client, config, wallet, session, initialTask }) {
2536
2719
  case "earn":
2537
2720
  await runAdWall();
2538
2721
  return;
2722
+ case "login":
2723
+ await runLogin(arg);
2724
+ return;
2539
2725
  default:
2540
2726
  push({ kind: "notice", tone: "warn", text: ` unknown command: /${cmd} \u2014 try /help` });
2541
2727
  }
2542
2728
  },
2543
- [client, config, cycleMode, exit, model, push, runAdWall, runBusy, runTurn2]
2729
+ [client, config, cycleMode, exit, model, push, runAdWall, runBusy, runLogin, runTurn2]
2544
2730
  );
2545
2731
  const submit = useCallback(
2546
2732
  async (raw) => {
@@ -2753,7 +2939,7 @@ function App({ client, config, wallet, session, initialTask }) {
2753
2939
  ] })
2754
2940
  ] });
2755
2941
  }
2756
- function sleep(ms, signal) {
2942
+ function sleep2(ms, signal) {
2757
2943
  return new Promise((resolve2) => {
2758
2944
  if (signal.aborted) return resolve2();
2759
2945
  const done = () => {
@@ -2825,12 +3011,13 @@ function tailLines(text, max) {
2825
3011
  const lines = text.split("\n");
2826
3012
  return lines.length <= max ? text : lines.slice(-max).join("\n");
2827
3013
  }
2828
- var SPINNER, WORKING, WAITING_FOR_REWARD, LOADING, COMPACTING, LIVE_OUTPUT_LINES, COMMITTED_OUTPUT_LINES;
3014
+ var SPINNER, WORKING, WAITING_FOR_REWARD, LOADING, COMPACTING, SIGNING_IN, NOT_SIGNED_IN, LIVE_OUTPUT_LINES, COMMITTED_OUTPUT_LINES;
2829
3015
  var init_app = __esm({
2830
3016
  "src/tui/app.tsx"() {
2831
3017
  "use strict";
2832
3018
  init_client();
2833
3019
  init_config();
3020
+ init_login();
2834
3021
  init_banner();
2835
3022
  init_agent();
2836
3023
  init_context();
@@ -2853,6 +3040,8 @@ var init_app = __esm({
2853
3040
  WAITING_FOR_REWARD = "waiting for the offer\u2026";
2854
3041
  LOADING = "loading\u2026";
2855
3042
  COMPACTING = "compacting\u2026";
3043
+ SIGNING_IN = "signing in\u2026";
3044
+ NOT_SIGNED_IN = " Not signed in, or the stored token isn't valid for this gateway.\n Run /login to sign in.";
2856
3045
  LIVE_OUTPUT_LINES = 5;
2857
3046
  COMMITTED_OUTPUT_LINES = 4;
2858
3047
  }
@@ -2880,6 +3069,7 @@ var init_tui = __esm({
2880
3069
  // src/main.ts
2881
3070
  init_client();
2882
3071
  init_config();
3072
+ init_login();
2883
3073
  init_agent();
2884
3074
  init_context();
2885
3075
  init_permissions();
@@ -2980,25 +3170,44 @@ async function main() {
2980
3170
  process.exitCode = 1;
2981
3171
  }
2982
3172
  }
2983
- function takeInvite(args) {
2984
- const rest = [];
2985
- let invite;
2986
- for (let i = 0; i < args.length; i++) {
2987
- const a = args[i];
2988
- if (a === "--invite") invite = args[++i];
2989
- else if (a.startsWith("--invite=")) invite = a.slice("--invite=".length);
2990
- else rest.push(a);
2991
- }
2992
- return { invite: invite?.trim() || void 0, rest };
2993
- }
2994
3173
  async function login(client, config, args) {
2995
- const { invite, rest } = takeInvite(args);
2996
- const email = rest[0];
2997
- if (!email) {
2998
- const start = await client.deviceStart().catch(() => null);
2999
- if (start) return githubLogin(client, config, start, invite);
3174
+ const parsed = parseLoginArgs(args);
3175
+ const outcome = await performLogin(client, config, parsed, {
3176
+ verify(start) {
3177
+ console.log(`
3178
+ Open ${c.cyan(start.verification_uri)} and enter code: ${c.bold(start.user_code)}
3179
+ `);
3180
+ process.stdout.write(c.dim(" waiting for GitHub authorization\u2026 (Ctrl+C to cancel)"));
3181
+ },
3182
+ tick() {
3183
+ process.stdout.write(c.dim("."));
3184
+ }
3185
+ });
3186
+ switch (outcome.status) {
3187
+ case "ok": {
3188
+ const { account } = outcome;
3189
+ const who = account.login ?? account.email ?? "you";
3190
+ const suffix = account.login && account.email ? c.dim(` (${account.email})`) : "";
3191
+ console.log(c.green(`
3192
+ \u2713 logged in as ${who}`) + suffix);
3193
+ const bonus = account.created ? c.dim(account.signupBonus === void 0 ? " (signup bonus)" : ` (signup bonus ${credits(account.signupBonus)})`) : "";
3194
+ console.log(` balance: ${c.bold(credits(account.balance))} credits${bonus}`);
3195
+ console.log(c.dim(` token stored in ${configPath()}`));
3196
+ return;
3197
+ }
3198
+ case "closed":
3199
+ reportSignupClosed(outcome.message, outcome.hadInvite);
3200
+ process.exitCode = 1;
3201
+ return;
3202
+ case "timeout":
3203
+ console.log(c.red("\n login timed out \u2014 run `clixad login` again."));
3204
+ return;
3205
+ case "aborted":
3206
+ return;
3207
+ default:
3208
+ console.log(c.red(`
3209
+ login failed: ${outcome.error}`));
3000
3210
  }
3001
- return devLogin(client, config, email, invite);
3002
3211
  }
3003
3212
  function reportSignupClosed(message, hadInvite) {
3004
3213
  console.log(c.yellow("\n Clixad isn't open yet.\n"));
@@ -3022,67 +3231,8 @@ function wrapPlain(text, width) {
3022
3231
  if (line2) lines.push(line2);
3023
3232
  return lines;
3024
3233
  }
3025
- async function githubLogin(client, config, start, invite) {
3026
- console.log(`
3027
- Open ${c.cyan(start.verification_uri)} and enter code: ${c.bold(start.user_code)}
3028
- `);
3029
- openBrowser(start.verification_uri);
3030
- process.stdout.write(c.dim(" waiting for GitHub authorization\u2026 (Ctrl+C to cancel)"));
3031
- const deadline = Date.now() + start.expires_in * 1e3;
3032
- let interval = Math.max(start.interval, 1);
3033
- while (Date.now() < deadline) {
3034
- await sleep2(interval * 1e3);
3035
- process.stdout.write(c.dim("."));
3036
- const poll = await client.devicePoll(start.session, invite);
3037
- if (poll.status === "pending") {
3038
- if (poll.interval) interval = poll.interval;
3039
- continue;
3040
- }
3041
- if (poll.status === "closed") {
3042
- reportSignupClosed(poll.message, Boolean(invite));
3043
- process.exitCode = 1;
3044
- return;
3045
- }
3046
- if (poll.status === "complete") {
3047
- config.token = poll.token;
3048
- config.userId = poll.userId;
3049
- config.email = poll.email;
3050
- config.login = poll.login;
3051
- saveConfig(config);
3052
- console.log(c.green(`
3053
- \u2713 logged in as ${poll.login}`) + c.dim(poll.email ? ` (${poll.email})` : ""));
3054
- const bonus = poll.created ? c.dim(` (signup bonus ${poll.signupBonus})`) : "";
3055
- console.log(` balance: ${c.bold(String(poll.balance))} credits${bonus}`);
3056
- console.log(c.dim(` token stored in ${configPath()}`));
3057
- return;
3058
- }
3059
- console.log(c.red(`
3060
- login failed: ${poll.error ?? poll.status}`));
3061
- return;
3062
- }
3063
- console.log(c.red("\n login timed out \u2014 run `clixad login` again."));
3064
- }
3065
- async function devLogin(client, config, email, invite) {
3066
- let res;
3067
- try {
3068
- res = await client.signupDev(email, invite);
3069
- } catch (err) {
3070
- if (err instanceof SignupClosedError) {
3071
- reportSignupClosed(err.message, Boolean(invite));
3072
- process.exitCode = 1;
3073
- return;
3074
- }
3075
- throw err;
3076
- }
3077
- config.token = res.token;
3078
- config.userId = res.userId;
3079
- config.email = res.email;
3080
- saveConfig(config);
3081
- console.log(c.green(`\u2713 logged in as ${res.email}`));
3082
- console.log(` balance: ${c.bold(String(res.balance))} credits (signup bonus)`);
3083
- console.log(c.dim(` token stored in ${configPath()}`));
3084
- }
3085
- var sleep2 = (ms) => new Promise((r) => setTimeout(r, ms));
3234
+ var sleep3 = (ms) => new Promise((r) => setTimeout(r, ms));
3235
+ var credits = (n) => n.toLocaleString("en-US");
3086
3236
  function logout(config) {
3087
3237
  delete config.token;
3088
3238
  delete config.userId;
@@ -3094,7 +3244,7 @@ async function whoami(client, config) {
3094
3244
  if (!config.token) return console.log(c.yellow("not logged in \u2014 run `clixad login`"));
3095
3245
  const w = await client.wallet();
3096
3246
  console.log(`${c.bold(config.email ?? "unknown")} \xB7 model ${c.cyan(config.model)}`);
3097
- console.log(`balance: ${c.bold(String(w.balance))} credits \xB7 ${earnedToday(w)}`);
3247
+ console.log(`balance: ${c.bold(credits(w.balance))} credits \xB7 ${earnedToday(w)}`);
3098
3248
  }
3099
3249
  function earnedToday(w) {
3100
3250
  const offers = `${w.ads_today} offer${w.ads_today === 1 ? "" : "s"}`;
@@ -3158,7 +3308,7 @@ async function earn(client, config) {
3158
3308
  process.stdout.write(c.dim(" waiting for an offer to clear"));
3159
3309
  const deadline = Date.now() + 5 * 60 * 1e3;
3160
3310
  while (Date.now() < deadline) {
3161
- await sleep2(3e3);
3311
+ await sleep3(3e3);
3162
3312
  process.stdout.write(c.dim("."));
3163
3313
  const balance = (await safeWallet(client))?.balance ?? before;
3164
3314
  if (balance > before) {
@@ -3202,12 +3352,12 @@ async function buyCmd(client, config, pack) {
3202
3352
  process.stdout.write(c.dim(" waiting for payment to clear"));
3203
3353
  const deadline = Date.now() + 5 * 60 * 1e3;
3204
3354
  while (Date.now() < deadline) {
3205
- await sleep2(3e3);
3355
+ await sleep3(3e3);
3206
3356
  process.stdout.write(c.dim("."));
3207
3357
  const balance = (await safeWallet(client))?.balance ?? before;
3208
3358
  if (balance > before) {
3209
3359
  console.log(c.green(`
3210
- \u2713 +${(balance - before).toLocaleString("en-US")} credits`) + ` \xB7 balance ${c.bold(String(balance))}`);
3360
+ \u2713 +${credits(balance - before)} credits`) + ` \xB7 balance ${c.bold(credits(balance))}`);
3211
3361
  return;
3212
3362
  }
3213
3363
  }
@@ -3291,10 +3441,6 @@ async function codeCmd(client, config, rest) {
3291
3441
  if (code !== 0) process.exitCode = code;
3292
3442
  }
3293
3443
  async function repl(client, config, opts = {}) {
3294
- if (!config.token) {
3295
- console.log(c.yellow("Not logged in. Run `clixad login` first.\n"));
3296
- return;
3297
- }
3298
3444
  if (opts.resume === "pick") {
3299
3445
  const sessions = listSessions(void 0, 10);
3300
3446
  if (!sessions.length) return console.log(c.yellow("no saved sessions yet"));
@@ -3306,9 +3452,18 @@ async function repl(client, config, opts = {}) {
3306
3452
  return;
3307
3453
  }
3308
3454
  if (!process.stdin.isTTY) {
3455
+ if (!config.token) {
3456
+ console.log(c.yellow("Not logged in, and interactive mode needs a terminal."));
3457
+ console.log(c.dim(' Run `clixad login` in a terminal, then `clixad -p "..."` for scripted runs.'));
3458
+ return;
3459
+ }
3309
3460
  console.log(c.yellow('Interactive mode needs a terminal. Use `clixad -p "..."` for scripted runs.'));
3310
3461
  return;
3311
3462
  }
3463
+ if (!config.token) {
3464
+ await login(client, config, []);
3465
+ if (!config.token) return;
3466
+ }
3312
3467
  let session;
3313
3468
  if (opts.resume === "latest") {
3314
3469
  session = latestSession(process.cwd());
@@ -3372,7 +3527,7 @@ function printHelp() {
3372
3527
  ${c.cyan("--continue")} resume the last session in this directory
3373
3528
  ${c.cyan("--resume")} [id] list saved sessions, or resume one
3374
3529
  ${c.cyan("--version")} print the version and exit
3375
- (no command) interactive coding REPL`);
3530
+ (no command) interactive coding REPL ${c.dim("(signs you in if needed)")}`);
3376
3531
  }
3377
3532
  main().catch((err) => {
3378
3533
  console.error(c.red(err.message));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "clixad",
3
- "version": "0.0.1-beta.4",
3
+ "version": "0.0.1-beta.6",
4
4
  "description": "Free AI coding agent in your terminal, funded by rewarded ads.",
5
5
  "license": "UNLICENSED",
6
6
  "type": "module",