free-pi-cli 0.6.6 → 0.6.8

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/dist/index.js CHANGED
@@ -30,4 +30,4 @@ if (err) {
30
30
  console.error(err);
31
31
  process.exit(1);
32
32
  }
33
- await import("./main-m7ct3546.js");
33
+ await import("./main-tyjwqhr5.js");
@@ -14391,7 +14391,10 @@ var AuthTokenPendingSchema = exports_external.object({
14391
14391
  status: exports_external.literal("pending"),
14392
14392
  retry_after: exports_external.number().optional()
14393
14393
  });
14394
- var AuthTokenIssuedSchema = exports_external.object({ token: exports_external.string() });
14394
+ var AuthTokenIssuedSchema = exports_external.object({
14395
+ token: exports_external.string(),
14396
+ guest_bonus_credits_granted: exports_external.number().int().optional()
14397
+ });
14395
14398
  var AuthTokenConsentRequiredSchema = exports_external.object({
14396
14399
  code: exports_external.literal("consent_required"),
14397
14400
  message: exports_external.string()
@@ -14437,7 +14440,9 @@ var GuestStartResponseSchema = exports_external.object({
14437
14440
  guest: exports_external.literal(true),
14438
14441
  allowance_usd: exports_external.string(),
14439
14442
  remaining_usd: exports_external.string(),
14440
- experiments: ExperimentAssignmentSchema.optional()
14443
+ experiments: ExperimentAssignmentSchema.optional(),
14444
+ claim_code: exports_external.string().optional(),
14445
+ github_bonus_credits: exports_external.number().int().optional()
14441
14446
  });
14442
14447
  var GuestUnavailableResponseSchema = exports_external.object({
14443
14448
  code: exports_external.literal("guest_unavailable"),
@@ -14701,6 +14706,7 @@ var ConfigSchema = exports_external.object({
14701
14706
  guestIpHashRetentionDays: exports_external.number().int().default(7),
14702
14707
  returnBonusDay2Credits: exports_external.number().int().default(2000),
14703
14708
  returnBonusDay7Credits: exports_external.number().int().default(1000),
14709
+ guestGithubBonusCredits: exports_external.number().int().default(1e4),
14704
14710
  returnNudgeEmailEnabled: exports_external.boolean().default(false),
14705
14711
  referralBonusCreditsPerDay: exports_external.number().int().default(5000),
14706
14712
  referralBonusDays: exports_external.number().int().default(30),
@@ -14845,6 +14851,7 @@ function loadConfig(env = process.env) {
14845
14851
  guestIpHashRetentionDays: envStrictInt("GUEST_IP_HASH_RETENTION_DAYS", env.GUEST_IP_HASH_RETENTION_DAYS),
14846
14852
  returnBonusDay2Credits: envStrictInt("RETURN_BONUS_DAY2_CREDITS", env.RETURN_BONUS_DAY2_CREDITS),
14847
14853
  returnBonusDay7Credits: envStrictInt("RETURN_BONUS_DAY7_CREDITS", env.RETURN_BONUS_DAY7_CREDITS),
14854
+ guestGithubBonusCredits: envStrictInt("GUEST_GITHUB_BONUS_CREDITS", env.GUEST_GITHUB_BONUS_CREDITS),
14848
14855
  returnNudgeEmailEnabled: envBoolean(env.RETURN_NUDGE_EMAIL_ENABLED),
14849
14856
  referralBonusCreditsPerDay: envStrictInt("REFERRAL_BONUS_CREDITS_PER_DAY", env.REFERRAL_BONUS_CREDITS_PER_DAY),
14850
14857
  referralBonusDays: envStrictInt("REFERRAL_BONUS_DAYS", env.REFERRAL_BONUS_DAYS),
@@ -15095,7 +15102,7 @@ var HiveClassifyResponseSchema = exports_external.object({
15095
15102
  // package.json
15096
15103
  var package_default = {
15097
15104
  name: "free-pi-cli",
15098
- version: "0.6.6",
15105
+ version: "0.6.8",
15099
15106
  description: "Free, ad-supported distribution of the pi coding agent. Inference paid for by ads.",
15100
15107
  license: "MIT",
15101
15108
  type: "module",
@@ -15706,6 +15713,7 @@ function buyMore(buyUrl) {
15706
15713
  }
15707
15714
  var CONFIRM_TURNS = 3;
15708
15715
  var lastCreditUsd;
15716
+ var lastToken;
15709
15717
  var confirm;
15710
15718
  function creditAddedMessage(addedUsd) {
15711
15719
  return `Payment received — ${usdToCredits(addedUsd).toLocaleString("en-US")} credits added to your balance.`;
@@ -15719,6 +15727,12 @@ async function renderMeter(deps, ctx) {
15719
15727
  }
15720
15728
  const theme = ctx.ui.theme;
15721
15729
  const credit = typeof me.credit_usd === "number" ? me.credit_usd : undefined;
15730
+ const token = await deps.getToken();
15731
+ if (token !== lastToken) {
15732
+ lastToken = token;
15733
+ lastCreditUsd = undefined;
15734
+ confirm = undefined;
15735
+ }
15722
15736
  const added = credit !== undefined && lastCreditUsd !== undefined ? credit - lastCreditUsd : 0;
15723
15737
  lastCreditUsd = credit;
15724
15738
  if (added > 0)
@@ -20171,12 +20185,14 @@ function formatStats(stats) {
20171
20185
  const allowance = stats.allowance_credits;
20172
20186
  const refillAt = stats.refill_at;
20173
20187
  const lane2 = stats.lane;
20188
+ const bonus = stats.bonus_wallet_credits;
20174
20189
  const todayLine = typeof remaining === "number" && typeof allowance === "number" && typeof refillAt === "string" ? formatCreditsLeftLine(remaining, allowance, new Date(refillAt)) : `today: ${percentUsed(stats.spent_usd_today, stats.cap_usd_today)}% of your free daily allowance used`;
20175
20190
  return [
20176
20191
  `tier: ${stats.tier}`,
20177
20192
  todayLine,
20178
20193
  ...lane2 === "fallback" ? [SLOW_LANE_METER_LINE] : [],
20179
20194
  ...typeof credits2 === "number" ? [`credit: ${credits2.toLocaleString("en-US")} credits remaining`] : [],
20195
+ ...typeof bonus === "number" && bonus > 0 ? [`bonus: ${bonus.toLocaleString("en-US")} credits remaining`] : [],
20180
20196
  `today tokens: ${stats.prompt_tokens_today} prompt / ${stats.completion_tokens_today} completion`,
20181
20197
  `lifetime: ${stats.lifetime.prompt_tokens} prompt / ${stats.lifetime.completion_tokens} completion tokens`,
20182
20198
  "buy more credits: /buy-credits",
@@ -21206,6 +21222,88 @@ function createErrorNoticeExtension(opts = {}) {
21206
21222
  };
21207
21223
  }
21208
21224
 
21225
+ // src/upgrade-notice.ts
21226
+ var UPGRADE_NOTICE_MARKER = ": freepi_notice";
21227
+ var UPGRADE_NOTICE_TEXT = "free-pi is upgrading — short delay. Your request will continue automatically.";
21228
+ var SLOW_SERVER_HINT = "Waiting for the server — free-pi may be upgrading; your request will continue automatically.";
21229
+ var SLOW_HEADERS_MS = 8000;
21230
+ var UPGRADE_NOTICE_DEDUPE_MS = 60000;
21231
+ function isCompletionsCall(input, completionsUrl) {
21232
+ try {
21233
+ const raw = input instanceof Request ? input.url : String(input);
21234
+ const got = new URL(raw);
21235
+ const want = new URL(completionsUrl);
21236
+ return got.origin === want.origin && got.pathname === want.pathname;
21237
+ } catch {
21238
+ return false;
21239
+ }
21240
+ }
21241
+ function wrapFetchForUpgrade(base, opts) {
21242
+ return async (input, init) => {
21243
+ if (!isCompletionsCall(input, opts.completionsUrl))
21244
+ return base(input, init);
21245
+ const timer = setTimeout(opts.onSlow, opts.slowMs ?? SLOW_HEADERS_MS);
21246
+ let res;
21247
+ try {
21248
+ res = await base(input, init);
21249
+ } finally {
21250
+ clearTimeout(timer);
21251
+ }
21252
+ if (!res.body || !(res.headers.get("content-type") ?? "").includes("text/event-stream"))
21253
+ return res;
21254
+ const decoder = new TextDecoder;
21255
+ let tail = `
21256
+ `;
21257
+ let seen = false;
21258
+ const body = res.body.pipeThrough(new TransformStream({
21259
+ transform(chunk, ctl) {
21260
+ ctl.enqueue(chunk);
21261
+ if (seen)
21262
+ return;
21263
+ const text = tail + decoder.decode(chunk, { stream: true });
21264
+ if (text.includes(`
21265
+ ${UPGRADE_NOTICE_MARKER}`)) {
21266
+ seen = true;
21267
+ opts.onNotice();
21268
+ } else
21269
+ tail = text.slice(-UPGRADE_NOTICE_MARKER.length);
21270
+ }
21271
+ }));
21272
+ return new Response(body, { status: res.status, statusText: res.statusText, headers: res.headers });
21273
+ };
21274
+ }
21275
+ var notify;
21276
+ var lastAt = new Map;
21277
+ var WRAPPED = Symbol.for("free-pi.upgrade-notice");
21278
+ function createUpgradeNoticeExtension(opts) {
21279
+ const now = opts.now ?? Date.now;
21280
+ const show = (message) => {
21281
+ const t = now();
21282
+ if (t - (lastAt.get(message) ?? -Infinity) < UPGRADE_NOTICE_DEDUPE_MS)
21283
+ return;
21284
+ lastAt.set(message, t);
21285
+ notify?.(message);
21286
+ };
21287
+ return {
21288
+ name: "free-pi-upgrade-notice",
21289
+ factory(pi) {
21290
+ pi.on("session_start", (_event, ctx) => {
21291
+ notify = (m) => ctx.ui.notify(m, "info");
21292
+ });
21293
+ if (globalThis.fetch[WRAPPED])
21294
+ return;
21295
+ const wrapped = wrapFetchForUpgrade(globalThis.fetch, {
21296
+ completionsUrl: `${completionsBaseUrl(opts.baseUrl)}/chat/completions`,
21297
+ slowMs: opts.slowMs,
21298
+ onNotice: () => show(UPGRADE_NOTICE_TEXT),
21299
+ onSlow: () => show(SLOW_SERVER_HINT)
21300
+ });
21301
+ wrapped[WRAPPED] = true;
21302
+ globalThis.fetch = wrapped;
21303
+ }
21304
+ };
21305
+ }
21306
+
21209
21307
  // src/agent-rules.ts
21210
21308
  var AGENT_RULES = `## Working rules
21211
21309
  - When you say you will run, check, or change something, make that tool call in the same
@@ -21373,6 +21471,17 @@ var LOGIN_VALUE_LINE = "sign in free with GitHub: full daily allowance, FREE abl
21373
21471
  var GUEST_NEAR_WALL_LINE = `A few guest requests left. ${LOGIN_VALUE_LINE}`;
21374
21472
  var SIGNED_IN_CONTINUE_LINE = "Signed in. Your conversation continues — full daily allowance is unlocked.";
21375
21473
  var HEADLESS_FALLBACK_LINE = "No browser or clipboard here — open the URL and enter the code above.";
21474
+ function formatGuestBonusBox(claimCode, credits2) {
21475
+ const amount = credits2.toLocaleString("en-US");
21476
+ const rows = [
21477
+ `GUEST BONUS * code ${claimCode}`,
21478
+ `Connect GitHub with /login -> +${amount} credits`,
21479
+ "One time, on your first GitHub sign-in."
21480
+ ];
21481
+ const inner = Math.max(...rows.map((r) => r.length)) + 4;
21482
+ const edge = `+${"-".repeat(inner)}+`;
21483
+ return [edge, ...rows.map((r) => `| ${r.padEnd(inner - 2)}|`), edge];
21484
+ }
21376
21485
  function isHandoffTreatment(variant) {
21377
21486
  return variant === "treatment";
21378
21487
  }
@@ -21445,6 +21554,22 @@ function mergeAttribution(a, b) {
21445
21554
 
21446
21555
  // src/changelog.ts
21447
21556
  var CHANGELOG_HIGHLIGHTS = [
21557
+ {
21558
+ version: "0.6.8",
21559
+ highlights: [
21560
+ "During a server upgrade free-pi now says it is upgrading and that your request will continue automatically.",
21561
+ "Guest trials show a bonus code: sign in with GitHub (/login) for a one-time +10,000 credits.",
21562
+ "/usage now shows your bonus credits on their own line, matching the footer."
21563
+ ]
21564
+ },
21565
+ {
21566
+ version: "0.6.7",
21567
+ highlights: [
21568
+ "/logout now signs you out of free-pi: it removes your saved sign-in and exits. Restart free-pi to sign in again. Guests are told they are not signed in.",
21569
+ "The confirmation code shown during browser sign-in is now bold and bright, so it is easy to compare with the one on freepi.ai.",
21570
+ 'Fixed a wrong "Payment received" line that could appear right after signing in.'
21571
+ ]
21572
+ },
21448
21573
  {
21449
21574
  version: "0.6.6",
21450
21575
  highlights: [
@@ -21721,6 +21846,9 @@ function highlightsFor(version3) {
21721
21846
  return CHANGELOG_HIGHLIGHTS.find((e) => e.version === version3)?.highlights;
21722
21847
  }
21723
21848
 
21849
+ // src/commands.ts
21850
+ import { access } from "node:fs/promises";
21851
+
21724
21852
  // src/auth-flow.ts
21725
21853
  class UnauthorizedError extends Error {
21726
21854
  constructor() {
@@ -21818,7 +21946,9 @@ async function startGuestSession(baseUrl, consentVersion, attribution = {}) {
21818
21946
  allowance_usd: parsed.allowance_usd,
21819
21947
  remaining_usd: parsed.remaining_usd,
21820
21948
  guestHandoffVariant: parsed.experiments?.guest_handoff,
21821
- firstTaskVariant: parsed.experiments?.first_task
21949
+ firstTaskVariant: parsed.experiments?.first_task,
21950
+ claimCode: parsed.claim_code,
21951
+ githubBonusCredits: parsed.github_bonus_credits
21822
21952
  };
21823
21953
  }
21824
21954
  async function reportFunnelEvent(baseUrl, event, opts = {}) {
@@ -22228,7 +22358,10 @@ function browserLikelyAvailable(env = process.env, platform = process.platform)
22228
22358
  return false;
22229
22359
  return true;
22230
22360
  }
22231
- function formatConnectPrompt(connect) {
22361
+ function emphasize(text, color = !process.env.NO_COLOR && process.stdout.isTTY === true) {
22362
+ return color ? `\x1B[1;97m${text}\x1B[0m` : text;
22363
+ }
22364
+ function formatConnectPrompt(connect, color) {
22232
22365
  return [
22233
22366
  "",
22234
22367
  LOGIN_REWARD_LINE,
@@ -22236,7 +22369,7 @@ function formatConnectPrompt(connect) {
22236
22369
  " Opening your browser to connect this terminal. If it doesn't open, go to:",
22237
22370
  ` ${connect.connect_url}`,
22238
22371
  "",
22239
- ` Confirmation code: ${connect.confirm_code}`,
22372
+ ` Confirmation code: ${emphasize(connect.confirm_code, color)}`,
22240
22373
  " (the page must show the same code)",
22241
22374
  "",
22242
22375
  DEVICE_LOGIN_HINT,
@@ -22468,7 +22601,7 @@ function installLoginHandler(mode, opts) {
22468
22601
  const m = mode;
22469
22602
  mode.handleLoginCommand = () => {
22470
22603
  const lines = [];
22471
- const notify = (message, type) => {
22604
+ const notify2 = (message, type) => {
22472
22605
  if (type === "error")
22473
22606
  return m.showError(message);
22474
22607
  if (type === "warning")
@@ -22477,7 +22610,34 @@ function installLoginHandler(mode, opts) {
22477
22610
  m.showStatus(lines.join(`
22478
22611
  `));
22479
22612
  };
22480
- return runLogin(opts, { ui: { notify } });
22613
+ return runLogin(opts, { ui: { notify: notify2 } });
22614
+ };
22615
+ }
22616
+ var LOGOUT_DONE_LINE = "Signed out of free-pi. Restart free-pi to sign in again.";
22617
+ var LOGOUT_NOT_SIGNED_IN_LINE = "You're not signed in to free-pi.";
22618
+ function installLogoutHandler(mode, opts) {
22619
+ const printAtExit = opts.printAtExit ?? ((line) => void process.once("exit", () => process.stdout.write(`${line}
22620
+ `)));
22621
+ const m = mode;
22622
+ mode.showOAuthSelector = async () => {
22623
+ if (!opts.credentialsPath || opts.isGuest?.()) {
22624
+ m.showStatus(LOGOUT_NOT_SIGNED_IN_LINE);
22625
+ return;
22626
+ }
22627
+ try {
22628
+ try {
22629
+ await runLogout({ credentialsPath: opts.credentialsPath, loadJwt, clearJwt, log: () => {} });
22630
+ } catch (err) {
22631
+ if (await access(opts.credentialsPath).then(() => true, () => false)) {
22632
+ const reason = err instanceof Error && err.message ? err.message : "unknown error";
22633
+ m.showError(`Couldn't sign out: ${reason}. Try: npx free-pi-cli@latest logout`);
22634
+ return;
22635
+ }
22636
+ }
22637
+ m.showStatus(LOGOUT_DONE_LINE);
22638
+ printAtExit(LOGOUT_DONE_LINE);
22639
+ await m.shutdown();
22640
+ } catch {}
22481
22641
  };
22482
22642
  }
22483
22643
  async function showUsage(opts, ctx) {
@@ -23404,7 +23564,7 @@ function createHiveExtension(opts) {
23404
23564
  let connectTimer;
23405
23565
  let roomWaiter;
23406
23566
  const dialogs = new Set;
23407
- const notify = (message, type = "info") => ctxRef?.ui.notify(message, type);
23567
+ const notify2 = (message, type = "info") => ctxRef?.ui.notify(message, type);
23408
23568
  function send(frame) {
23409
23569
  if (ws && ws.readyState === OPEN)
23410
23570
  ws.send(JSON.stringify(frame));
@@ -23448,13 +23608,13 @@ function createHiveExtension(opts) {
23448
23608
  const message = [notice, changes].filter(Boolean).join(`
23449
23609
  `);
23450
23610
  if (message)
23451
- notify(message, "info");
23611
+ notify2(message, "info");
23452
23612
  roomWaiter?.();
23453
23613
  roomWaiter = undefined;
23454
23614
  }
23455
23615
  async function start(ctx) {
23456
23616
  if (ws || starting) {
23457
- notify(roomUrl ? `Hive room: ${roomUrl}` : "Hive: still connecting...");
23617
+ notify2(roomUrl ? `Hive room: ${roomUrl}` : "Hive: still connecting...");
23458
23618
  return;
23459
23619
  }
23460
23620
  starting = true;
@@ -23463,7 +23623,7 @@ function createHiveExtension(opts) {
23463
23623
  token = await opts.getToken();
23464
23624
  } catch {
23465
23625
  starting = false;
23466
- notify("Hive: couldn't read your free-pi login. Try again after signing in.", "error");
23626
+ notify2("Hive: couldn't read your free-pi login. Try again after signing in.", "error");
23467
23627
  return;
23468
23628
  }
23469
23629
  starting = false;
@@ -23530,7 +23690,7 @@ Sandboxing is available: when you approve a guest's shell command you can run it
23530
23690
  clearTimeout(connectTimer);
23531
23691
  if (!settled)
23532
23692
  send({ type: "busy", settled: false });
23533
- notify(`Hive room open: ${frame.url}
23693
+ notify2(`Hive room open: ${frame.url}
23534
23694
  Guests sign in with GitHub to watch and chat. They can ask to drive; you approve each request, and risky actions still ask you.${sandboxLine()}`);
23535
23695
  updateStatus();
23536
23696
  roomWaiter?.();
@@ -23562,7 +23722,7 @@ Guests sign in with GitHub to watch and chat. They can ask to drive; you approve
23562
23722
  break;
23563
23723
  case "error":
23564
23724
  if (frame.code !== "stale")
23565
- notify(`Hive: ${frame.code}`, "warning");
23725
+ notify2(`Hive: ${frame.code}`, "warning");
23566
23726
  break;
23567
23727
  case "closed":
23568
23728
  endRoom(`Hive room closed (${frame.reason}).`);
@@ -23735,7 +23895,7 @@ ${decision.reason}`, ACTION_DIALOG_MS);
23735
23895
  handler: async (args, ctx) => {
23736
23896
  ctxRef = ctx;
23737
23897
  const [sub = "", ...rest3] = args.trim().split(/\s+/);
23738
- const noRoom = () => notify("No hive room is open. Run /hive to start one.");
23898
+ const noRoom = () => notify2("No hive room is open. Run /hive to start one.");
23739
23899
  switch (sub.toLowerCase()) {
23740
23900
  case "":
23741
23901
  return start(ctx);
@@ -23754,20 +23914,20 @@ ${decision.reason}`, ACTION_DIALOG_MS);
23754
23914
  const handle2 = (rest3[0] ?? "").replace(/^@/, "").toLowerCase();
23755
23915
  const ids = new Set(members.filter((m) => handle2 && m.handle.toLowerCase() === handle2).map((m) => m.provider_id));
23756
23916
  if (ids.size === 0)
23757
- return notify(`No guest named "${handle2}" in the room. Usage: /hive kick <handle>`);
23917
+ return notify2(`No guest named "${handle2}" in the room. Usage: /hive kick <handle>`);
23758
23918
  dropRecorded();
23759
23919
  for (const provider_id of ids) {
23760
23920
  kicked.add(provider_id);
23761
23921
  send({ type: "kick", provider_id });
23762
23922
  }
23763
- return notify(`Kicked @${handle2}. They cannot rejoin this room.`);
23923
+ return notify2(`Kicked @${handle2}. They cannot rejoin this room.`);
23764
23924
  }
23765
23925
  case "end":
23766
23926
  if (!ws)
23767
23927
  return noRoom();
23768
23928
  return endRoom("Hive room ended.", true);
23769
23929
  default:
23770
- return notify("Usage: /hive, /hive take, /hive kick <handle>, /hive end");
23930
+ return notify2("Usage: /hive, /hive take, /hive kick <handle>, /hive end");
23771
23931
  }
23772
23932
  }
23773
23933
  });
@@ -24695,6 +24855,7 @@ function buildRuntimeOptions(opts, sessionId) {
24695
24855
  imageToolExtension,
24696
24856
  visionToolExtension,
24697
24857
  errorNoticeExtension,
24858
+ createUpgradeNoticeExtension({ baseUrl: opts.baseUrl }),
24698
24859
  createOutputLimitExtension(),
24699
24860
  toolGuardExtension,
24700
24861
  commandsExtension,
@@ -24811,6 +24972,7 @@ async function launchPi(opts) {
24811
24972
  onLogin: (token) => auth.becomeSignedIn(token),
24812
24973
  variant: () => opts.guestHandoffVariant
24813
24974
  });
24975
+ installLogoutHandler(mode, { credentialsPath: opts.credentialsPath, isGuest: () => auth.isGuest() });
24814
24976
  installModelAllowanceHandler(mode);
24815
24977
  installFooterRightAlign(mode);
24816
24978
  await mode.run();
@@ -25385,6 +25547,11 @@ You're on a short guest trial — ads pay for it. ${LOGIN_VALUE_LINE}
25385
25547
  ` : `
25386
25548
  You're on a short guest trial — ads pay for it. Sign in with GitHub anytime (/login) to get 15,000 free credits a day and unlock a one-time comeback bonus.
25387
25549
  `);
25550
+ if (guest.claimCode && guest.githubBonusCredits) {
25551
+ deps.log(emphasize(formatGuestBonusBox(guest.claimCode, guest.githubBonusCredits).join(`
25552
+ `)) + `
25553
+ `);
25554
+ }
25388
25555
  guestHandoffVariant = guest.guestHandoffVariant;
25389
25556
  showFirstTask = guest.firstTaskVariant === "treatment" && !resumeFlags.hasPrompt;
25390
25557
  } catch (err) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "free-pi-cli",
3
- "version": "0.6.6",
3
+ "version": "0.6.8",
4
4
  "description": "Free, ad-supported distribution of the pi coding agent. Inference paid for by ads.",
5
5
  "license": "MIT",
6
6
  "type": "module",