free-pi-cli 0.6.10 → 0.6.11

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-gtmbyjcm.js");
33
+ await import("./main-p7k55rz4.js");
@@ -519,7 +519,7 @@ function folderMatches(cwd, sessionCwd) {
519
519
  // package.json
520
520
  var package_default = {
521
521
  name: "free-pi-cli",
522
- version: "0.6.10",
522
+ version: "0.6.11",
523
523
  description: "Free, ad-supported distribution of the pi coding agent. Inference paid for by ads.",
524
524
  license: "MIT",
525
525
  type: "module",
@@ -584,6 +584,7 @@ function createOutputLimitExtension() {
584
584
  // src/provider.ts
585
585
  var PROVIDER_NAME = "free-pi";
586
586
  var DEFENSE_PROVIDER_NAME = "free-pi-defense";
587
+ var CONTEXT_WINDOW = 200000;
587
588
  var MODEL_ID = "deepseek/deepseek-v4-flash";
588
589
  var SAFE_TOOLS = ["read", "bash", "edit", "write", "grep", "find", "ls"];
589
590
  function completionsBaseUrl(baseUrl) {
@@ -610,7 +611,7 @@ function buildProviderConfig(baseUrl, jwt, sessionId, models, name = PROVIDER_NA
610
611
  compat: { supportsDeveloperRole: false },
611
612
  input: ["text"],
612
613
  cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
613
- contextWindow: 1048576,
614
+ contextWindow: CONTEXT_WINDOW,
614
615
  maxTokens: MAX_OUTPUT_TOKENS
615
616
  }))
616
617
  };
@@ -921,12 +922,12 @@ function parseResumeFlags(argv) {
921
922
  i += 1;
922
923
  continue;
923
924
  }
924
- if (arg === "--ref" || arg === "--src") {
925
+ if (arg === "--ref" || arg === "--src" || arg === "--redeem") {
925
926
  if (args[i + 1] && !String(args[i + 1]).startsWith("--"))
926
927
  i += 1;
927
928
  continue;
928
929
  }
929
- if (arg.startsWith("--ref=") || arg.startsWith("--src="))
930
+ if (arg.startsWith("--ref=") || arg.startsWith("--src=") || arg.startsWith("--redeem="))
930
931
  continue;
931
932
  if (!arg.startsWith("-"))
932
933
  hasPrompt = true;
@@ -15469,7 +15470,8 @@ var ConnectStartResponseSchema = exports_external.object({
15469
15470
  var GuestStartRequestSchema = exports_external.object({
15470
15471
  consent_version: exports_external.string().min(1).max(32),
15471
15472
  signup_src: exports_external.string().optional(),
15472
- referral_code: exports_external.string().optional()
15473
+ referral_code: exports_external.string().optional(),
15474
+ client: exports_external.object({ os: exports_external.string().max(64).optional(), cli_version: exports_external.string().max(64).optional() }).optional()
15473
15475
  });
15474
15476
  var ExperimentVariantSchema = exports_external.enum(["control", "treatment"]);
15475
15477
  var ExperimentAssignmentSchema = exports_external.object({
@@ -15683,7 +15685,7 @@ var ConfigSchema = exports_external.object({
15683
15685
  perUserRollingCapUsd: exports_external.number().default(0),
15684
15686
  rollingCapWindowHours: exports_external.number().int().default(5),
15685
15687
  globalDailyCapUsd: exports_external.number().default(50),
15686
- contextCharBudget: exports_external.number().int().default(800000),
15688
+ contextCharBudget: exports_external.number().int().default(1200000),
15687
15689
  maxOutputTokens: exports_external.number().int().min(1).default(32768),
15688
15690
  accountsPerDayCeiling: exports_external.number().int().default(500),
15689
15691
  authRatePerIpPerHour: exports_external.number().int().default(30),
@@ -15739,6 +15741,28 @@ var ConfigSchema = exports_external.object({
15739
15741
  signedInMaxCliSessions: exports_external.number().int().min(1).default(4),
15740
15742
  premiumRatioThreshold: exports_external.number().int().min(1).default(5),
15741
15743
  fallbackExpensiveRatio: exports_external.number().int().min(1).default(5),
15744
+ larprouterTrialModels: exports_external.array(exports_external.string()).default([]),
15745
+ larprouterTrialSharePct: exports_external.number().int().min(0).max(100).default(0),
15746
+ larprouterBaseUrl: exports_external.string().default("https://larprouter.com/v1"),
15747
+ larprouterFallbackModels: exports_external.array(exports_external.string()).default([]),
15748
+ larprouterFallbackTerminal: exports_external.boolean().default(false),
15749
+ larprouterTtftMs: exports_external.number().int().min(1).default(8000),
15750
+ larprouterPrices: exports_external.record(exports_external.string(), exports_external.object({ input: exports_external.string(), cacheRead: exports_external.string(), output: exports_external.string() })).default({ "deepseek-v4.1-flash-1017-non-abliterated": { input: "80000", cacheRead: "10000", output: "350000" } }),
15751
+ larprouterModelMap: exports_external.record(exports_external.string(), exports_external.string()).default({
15752
+ "deepseek-v4-flash": "deepseek-v4.1-flash-1017-non-abliterated",
15753
+ "deepseek-v4.1-flash": "deepseek-v4.1-flash-1017-non-abliterated"
15754
+ }),
15755
+ experientialBaseUrl: exports_external.string().default("https://api.experientiallabs.ai/v1"),
15756
+ experientialModelMap: exports_external.record(exports_external.string(), exports_external.string()).default({
15757
+ "deepseek-v4.1-flash": "deepseek-v4.1-flash-1017",
15758
+ "deepseek-v4-flash": "deepseek-v4.1-flash-1017",
15759
+ "gpt-6-luna": "gpt-6-luna"
15760
+ }),
15761
+ experientialPrimaryModels: exports_external.array(exports_external.string()).default([]),
15762
+ experientialFallbackModels: exports_external.array(exports_external.string()).default([]),
15763
+ experientialTtftMs: exports_external.number().int().min(1).default(1e4),
15764
+ experientialMaxCostUsdPerRequest: exports_external.string().default("0.0005"),
15765
+ experientialPriceCooldownMs: exports_external.number().int().min(1).default(3600000),
15742
15766
  minCliVersion: exports_external.string().default("0.0.0"),
15743
15767
  latestCliVersion: exports_external.string().default("0.2.0"),
15744
15768
  abuseWindowMinutes: exports_external.number().int().default(30),
@@ -15765,6 +15789,7 @@ var ConfigSchema = exports_external.object({
15765
15789
  guestEnabled: exports_external.boolean().default(false),
15766
15790
  guestAllowanceMicros: exports_external.bigint().default(30000n),
15767
15791
  guestGlobalDailyCapMicros: exports_external.bigint().default(3000000n),
15792
+ guestHoldMicros: exports_external.bigint().default(5000n),
15768
15793
  guestIpSessionsPerDay: exports_external.number().int().default(8),
15769
15794
  guestIpAllowanceMicrosPerDay: exports_external.bigint().default(240000n),
15770
15795
  guestMaxConcurrentStreams: exports_external.number().int().default(10),
@@ -15780,11 +15805,16 @@ var ConfigSchema = exports_external.object({
15780
15805
  referralGithubMinAgeDays: exports_external.number().int().default(30),
15781
15806
  referralCreditGrantCredits: exports_external.number().int().default(0),
15782
15807
  webClaimCredits: exports_external.number().int().default(0),
15808
+ bountyCredits: exports_external.number().int().positive().default(5000),
15809
+ bountyMonthlyCapUsd: exports_external.string().regex(/^\d+(\.\d{1,6})?$/).default("50"),
15783
15810
  geoipDbPath: exports_external.string().optional(),
15784
15811
  sanctionedCountries: exports_external.array(exports_external.string()).default(["IR", "CU", "KP", "SY"]),
15812
+ sanctionedTimezones: exports_external.array(exports_external.string()).default(["Asia/Tehran", "Asia/Pyongyang", "America/Havana", "Asia/Damascus", "Europe/Simferopol"]),
15785
15813
  regionLimitsStart: exports_external.date().optional(),
15786
15814
  fullAllowanceCountries: exports_external.array(exports_external.string()).default(["US", "CA", "GB", "AU", "NZ", "IE"]),
15787
15815
  priorityCountries: exports_external.array(exports_external.string()).default(["US", "CA", "AU", "NZ"]),
15816
+ freeSpeed: exports_external.enum(["off", "log", "on"]).default("log"),
15817
+ freeSpeedDelaysS: exports_external.array(exports_external.number().int().min(0).max(120)).length(4).default([0, 10, 5, 20]),
15788
15818
  winbackEmail: exports_external.enum(["off", "dry_run", "live"]).default("off"),
15789
15819
  winbackMinDays: exports_external.number().int().default(3),
15790
15820
  winbackMaxDays: exports_external.number().int().default(14),
@@ -15829,6 +15859,33 @@ function envStrictInt(name, value) {
15829
15859
  throw new Error(`${name} must be a non-negative integer, got "${value}"`);
15830
15860
  return Number(v);
15831
15861
  }
15862
+ function envPairs(value) {
15863
+ const out = {};
15864
+ for (const part of (value ?? "").split(",")) {
15865
+ const bits = part.split("=").map((s) => s.trim());
15866
+ if (bits.length === 2 && bits[0] && bits[1])
15867
+ out[bits[0]] = bits[1];
15868
+ }
15869
+ return Object.keys(out).length > 0 ? out : undefined;
15870
+ }
15871
+ function usdToMicrosString(v) {
15872
+ const m = /^(\d{1,6})(?:\.(\d{1,6}))?$/.exec(v.trim());
15873
+ return m ? (BigInt(m[1]) * 1000000n + BigInt((m[2] ?? "").padEnd(6, "0"))).toString() : null;
15874
+ }
15875
+ function envPrices(value) {
15876
+ const out = {};
15877
+ for (const part of (value ?? "").split(",")) {
15878
+ const bits = part.split("=").map((s) => s.trim());
15879
+ const nums = bits.length === 2 ? bits[1].split("/").map(usdToMicrosString) : [];
15880
+ if (bits[0] && nums.length === 3 && nums.every((n) => n !== null))
15881
+ out[bits[0]] = { input: nums[0], cacheRead: nums[1], output: nums[2] };
15882
+ }
15883
+ return Object.keys(out).length > 0 ? out : undefined;
15884
+ }
15885
+ function envPercent(value) {
15886
+ const n = envNumber(value);
15887
+ return n === undefined ? undefined : Math.min(100, Math.max(0, Math.trunc(n)));
15888
+ }
15832
15889
  function envBoolean(value) {
15833
15890
  if (value === undefined || value === "")
15834
15891
  return;
@@ -15846,6 +15903,11 @@ function envList(value) {
15846
15903
  function envCountries(value) {
15847
15904
  return envList(value)?.map((s) => s.toUpperCase());
15848
15905
  }
15906
+ function envTimezones(value) {
15907
+ if (value === undefined)
15908
+ return;
15909
+ return value.split(",").map((s) => s.trim()).filter((s) => s.length > 0);
15910
+ }
15849
15911
  function envStreakRewards(value) {
15850
15912
  if (!value?.trim())
15851
15913
  return;
@@ -15869,6 +15931,24 @@ function envStreaksMode(value) {
15869
15931
  throw new Error(`STREAKS_ENABLED must be off, log or on, got "${value}"`);
15870
15932
  return v;
15871
15933
  }
15934
+ function envFreeSpeedMode(value) {
15935
+ if (value === undefined || value.trim() === "")
15936
+ return;
15937
+ const v = value.trim().toLowerCase();
15938
+ if (v !== "off" && v !== "log" && v !== "on")
15939
+ throw new Error(`FREE_SPEED must be off, log or on, got "${value}"`);
15940
+ return v;
15941
+ }
15942
+ function envFreeSpeedDelays(value) {
15943
+ if (!value?.trim())
15944
+ return;
15945
+ const parts = value.split(",").map((s) => s.trim());
15946
+ const nums = parts.map((s) => /^\d+$/.test(s) ? Number(s) : NaN);
15947
+ if (nums.length !== 4 || nums.some((n) => !Number.isInteger(n) || n > 120)) {
15948
+ throw new Error(`FREE_SPEED_DELAYS must be four whole seconds 0-120 (priority+clicking, priority+not_clicking, other+clicking, other+not_clicking), got "${value}"`);
15949
+ }
15950
+ return nums;
15951
+ }
15872
15952
  function envDate(value) {
15873
15953
  if (!value?.trim())
15874
15954
  return;
@@ -15881,6 +15961,14 @@ function envDecimalString(value) {
15881
15961
  const t = value.trim();
15882
15962
  return /^\d+(\.\d+)?$/.test(t) ? t : undefined;
15883
15963
  }
15964
+ function envBountyCap(value) {
15965
+ const t = value?.trim();
15966
+ if (!t)
15967
+ return;
15968
+ if (!/^\d+(\.\d{1,6})?$/.test(t))
15969
+ throw new Error(`BOUNTY_MONTHLY_CAP_USD must be a decimal like "50" or "0.50", got "${value}"`);
15970
+ return t;
15971
+ }
15884
15972
  function loadConfig(env = process.env) {
15885
15973
  return ConfigSchema.parse({
15886
15974
  perUserDailyCapUsd: envNumber(env.PER_USER_DAILY_CAP_USD),
@@ -15938,6 +16026,21 @@ function loadConfig(env = process.env) {
15938
16026
  signedInMaxCliSessions: envNumber(env.SIGNED_IN_MAX_CLI_SESSIONS),
15939
16027
  premiumRatioThreshold: envNumber(env.PREMIUM_RATIO_THRESHOLD),
15940
16028
  fallbackExpensiveRatio: envNumber(env.FALLBACK_EXPENSIVE_RATIO),
16029
+ larprouterTrialModels: envList(env.LARPROUTER_TRIAL_MODELS),
16030
+ larprouterTrialSharePct: envPercent(env.LARPROUTER_TRIAL_SHARE_PCT),
16031
+ larprouterFallbackModels: envList(env.LARPROUTER_FALLBACK_MODELS),
16032
+ larprouterTtftMs: envNumber(env.LARPROUTER_TTFT_MS),
16033
+ larprouterFallbackTerminal: envBoolean(env.LARPROUTER_FALLBACK_TERMINAL),
16034
+ larprouterModelMap: envPairs(env.LARPROUTER_MODEL_MAP),
16035
+ larprouterPrices: envPrices(env.LARPROUTER_PRICES),
16036
+ larprouterBaseUrl: env.LARPROUTER_BASE_URL?.trim().replace(/\/$/, "") || undefined,
16037
+ experientialBaseUrl: env.EXPERIENTIAL_BASE_URL?.trim().replace(/\/$/, "") || undefined,
16038
+ experientialModelMap: envPairs(env.EXPERIENTIAL_MODEL_MAP),
16039
+ experientialPrimaryModels: envList(env.EXPERIENTIAL_PRIMARY_MODELS),
16040
+ experientialFallbackModels: envList(env.EXPERIENTIAL_FALLBACK_MODELS),
16041
+ experientialTtftMs: envNumber(env.EXPERIENTIAL_TTFT_MS),
16042
+ experientialMaxCostUsdPerRequest: /^\d{1,6}(\.\d{1,6})?$/.test(env.EXPERIENTIAL_MAX_COST_USD_PER_REQUEST?.trim() ?? "") ? env.EXPERIENTIAL_MAX_COST_USD_PER_REQUEST.trim() : undefined,
16043
+ experientialPriceCooldownMs: envNumber(env.EXPERIENTIAL_PRICE_COOLDOWN_MS),
15941
16044
  minCliVersion: env.MIN_CLI_VERSION || undefined,
15942
16045
  latestCliVersion: env.LATEST_CLI_VERSION || undefined,
15943
16046
  abuseWindowMinutes: envNumber(env.ABUSE_WINDOW_MINUTES),
@@ -15964,6 +16067,7 @@ function loadConfig(env = process.env) {
15964
16067
  guestEnabled: envBoolean(env.GUEST_ENABLED),
15965
16068
  guestAllowanceMicros: envMicros("GUEST_ALLOWANCE_USD", env.GUEST_ALLOWANCE_USD),
15966
16069
  guestGlobalDailyCapMicros: envMicros("GUEST_GLOBAL_DAILY_CAP_USD", env.GUEST_GLOBAL_DAILY_CAP_USD),
16070
+ guestHoldMicros: envMicros("GUEST_HOLD_USD", env.GUEST_HOLD_USD),
15967
16071
  guestIpSessionsPerDay: envStrictInt("GUEST_IP_SESSIONS_PER_DAY", env.GUEST_IP_SESSIONS_PER_DAY),
15968
16072
  guestIpAllowanceMicrosPerDay: envMicros("GUEST_IP_ALLOWANCE_USD_PER_DAY", env.GUEST_IP_ALLOWANCE_USD_PER_DAY),
15969
16073
  guestMaxConcurrentStreams: envStrictInt("GUEST_MAX_CONCURRENT_STREAMS", env.GUEST_MAX_CONCURRENT_STREAMS),
@@ -15979,12 +16083,17 @@ function loadConfig(env = process.env) {
15979
16083
  referralGithubMinAgeDays: envStrictInt("REFERRAL_GITHUB_MIN_AGE_DAYS", env.REFERRAL_GITHUB_MIN_AGE_DAYS),
15980
16084
  referralCreditGrantCredits: envStrictInt("REFERRAL_CREDIT_GRANT_CREDITS", env.REFERRAL_CREDIT_GRANT_CREDITS),
15981
16085
  webClaimCredits: envStrictInt("WEB_CLAIM_CREDITS", env.WEB_CLAIM_CREDITS),
16086
+ bountyCredits: envStrictInt("BOUNTY_CREDITS", env.BOUNTY_CREDITS),
16087
+ bountyMonthlyCapUsd: envBountyCap(env.BOUNTY_MONTHLY_CAP_USD),
15982
16088
  geoipDbPath: env.GEOIP_DB_PATH?.trim() || undefined,
15983
16089
  sanctionedCountries: envCountries(env.SANCTIONED_COUNTRIES),
16090
+ sanctionedTimezones: envTimezones(env.SANCTIONED_TIMEZONES),
15984
16091
  regionLimitsStart: envDate(env.REGION_LIMITS_START),
15985
16092
  fullAllowanceCountries: envCountries(env.FULL_ALLOWANCE_COUNTRIES),
15986
16093
  regionLowCapMicros: envMicros("REGION_LOW_CAP_USD", env.REGION_LOW_CAP_USD),
15987
16094
  priorityCountries: envCountries(env.PRIORITY_COUNTRIES),
16095
+ freeSpeed: envFreeSpeedMode(env.FREE_SPEED),
16096
+ freeSpeedDelaysS: envFreeSpeedDelays(env.FREE_SPEED_DELAYS),
15988
16097
  streaksEnabled: envStreaksMode(env.STREAKS_ENABLED),
15989
16098
  streakMinRequests: envStrictInt("STREAK_MIN_REQUESTS", env.STREAK_MIN_REQUESTS),
15990
16099
  streakRewards: envStreakRewards(env.STREAK_REWARDS),
@@ -16250,6 +16359,8 @@ Usage:
16250
16359
  continue a Claude Code or Codex session from this folder
16251
16360
  (inside free-pi, /import does the same)
16252
16361
  free-pi-cli --device-login sign in with a GitHub device code instead of the browser page
16362
+ free-pi-cli --redeem <code> redeem a credit code from an email, then start
16363
+ (inside free-pi, /redeem does the same)
16253
16364
  free-pi-cli --ref <code> first-run referral code (from freepi.ai/?ref=)
16254
16365
  free-pi-cli --src <channel> first-run campaign attribution (from freepi.ai/?src=)
16255
16366
  free-pi-cli logout remove the stored sign-in token; the next run signs in again
@@ -16300,13 +16411,13 @@ function parseCliArgs(argv) {
16300
16411
  i += 1;
16301
16412
  continue;
16302
16413
  }
16303
- if (arg === "--session" || arg === "--ref" || arg === "--src") {
16414
+ if (arg === "--session" || arg === "--ref" || arg === "--src" || arg === "--redeem") {
16304
16415
  i += 1;
16305
16416
  if (argv[i] && !String(argv[i]).startsWith("--"))
16306
16417
  i += 1;
16307
16418
  continue;
16308
16419
  }
16309
- if (arg.startsWith("--session=") || arg.startsWith("--ref=") || arg.startsWith("--src=")) {
16420
+ if (arg.startsWith("--session=") || arg.startsWith("--ref=") || arg.startsWith("--src=") || arg.startsWith("--redeem=")) {
16310
16421
  i += 1;
16311
16422
  continue;
16312
16423
  }
@@ -16357,6 +16468,18 @@ function parseAttributionFlags(argv, env = process.env) {
16357
16468
  }
16358
16469
  return { referralCode: referralRaw, signupSrc: signupRaw };
16359
16470
  }
16471
+ function parseRedeemFlag(argv) {
16472
+ for (let i = 0;i < argv.length; i++) {
16473
+ const arg = argv[i];
16474
+ if (arg === "--redeem") {
16475
+ const next = argv[i + 1];
16476
+ return { requested: true, code: next && !next.startsWith("--") ? next : undefined };
16477
+ }
16478
+ if (arg.startsWith("--redeem="))
16479
+ return { requested: true, code: arg.slice("--redeem=".length) || undefined };
16480
+ }
16481
+ return { requested: false, code: undefined };
16482
+ }
16360
16483
  function parseImportFlag(argv) {
16361
16484
  for (let i = 0;i < argv.length; i++) {
16362
16485
  const arg = argv[i];
@@ -16810,6 +16933,8 @@ function mapErrorCode(code) {
16810
16933
  return "this model is busy right now — try again in a few minutes, or switch models with /model";
16811
16934
  case "context_too_long_for_fallback":
16812
16935
  return "this conversation is too long for this model right now — run /compact, or switch to a cheaper model with /model";
16936
+ case "context_too_long":
16937
+ return "this conversation is too long — run /compact or start a new session with /new";
16813
16938
  default:
16814
16939
  return assertExhaustiveErrorCode(code);
16815
16940
  }
@@ -22266,9 +22391,10 @@ var LIMIT_CODES = new Set([
22266
22391
  "region_unavailable",
22267
22392
  "premium_allowance",
22268
22393
  "model_at_capacity",
22269
- "context_too_long_for_fallback"
22394
+ "context_too_long_for_fallback",
22395
+ "context_too_long"
22270
22396
  ]);
22271
- var SERVER_MESSAGE_CODES = new Set(["premium_allowance", "model_at_capacity", "context_too_long_for_fallback"]);
22397
+ var SERVER_MESSAGE_CODES = new Set(["premium_allowance", "model_at_capacity", "context_too_long_for_fallback", "context_too_long"]);
22272
22398
  function createErrorNoticeExtension(opts = {}) {
22273
22399
  const lastAt = new Map;
22274
22400
  const now = opts.now ?? Date.now;
@@ -22378,6 +22504,31 @@ function createUpgradeNoticeExtension(opts) {
22378
22504
  };
22379
22505
  }
22380
22506
 
22507
+ // src/tool-outcomes.ts
22508
+ var MAX_IDS = 50;
22509
+ function createToolOutcomesExtension() {
22510
+ const failed = [];
22511
+ let evicted = false;
22512
+ return {
22513
+ name: "free-pi-tool-outcomes",
22514
+ factory(pi) {
22515
+ pi.on("message_end", (event) => {
22516
+ const m = event.message;
22517
+ if (m.role !== "toolResult" || !m.isError || !m.toolCallId || !/^[\w.:-]{1,100}$/.test(m.toolCallId))
22518
+ return;
22519
+ failed.push(m.toolCallId);
22520
+ if (failed.length > MAX_IDS) {
22521
+ failed.shift();
22522
+ evicted = true;
22523
+ }
22524
+ });
22525
+ pi.on("before_provider_headers", (event) => {
22526
+ event.headers["x-freepi-tool-errors"] = `${evicted ? "v1x" : "v1"}:${failed.join(",")}`;
22527
+ });
22528
+ }
22529
+ };
22530
+ }
22531
+
22381
22532
  // src/agent-rules.ts
22382
22533
  var AGENT_RULES = `## Working rules
22383
22534
  - When you say you will run, check, or change something, make that tool call in the same
@@ -22628,6 +22779,13 @@ function mergeAttribution(a, b) {
22628
22779
 
22629
22780
  // src/changelog.ts
22630
22781
  var CHANGELOG_HIGHLIGHTS = [
22782
+ {
22783
+ version: "0.6.11",
22784
+ highlights: [
22785
+ "/redeem <code> claims a credit code, like the one in the outage apology email. It works once, on the account it was issued to.",
22786
+ "Long sessions now compact earlier, so they are less likely to hit the context limit."
22787
+ ]
22788
+ },
22631
22789
  {
22632
22790
  version: "0.6.10",
22633
22791
  highlights: [
@@ -23702,6 +23860,39 @@ async function runLoginAuto(deps, attribution = {}, opts = {}) {
23702
23860
  return runDeviceLogin(deps, attribution);
23703
23861
  }
23704
23862
 
23863
+ // src/redeem-tool.ts
23864
+ var REDEEM_USAGE = "Usage: /redeem FREEPI-XXXX-XXXX (the code from your email)";
23865
+ var REDEEM_GUEST_LINE = "Sign in with GitHub to redeem a code. Run /login.";
23866
+ var REFUSALS = new Set(["not_found", "not_yours", "expired", "already_redeemed", "rate_limited", "guest_login_required", "not_redeemable"]);
23867
+ async function redeemCode(opts, rawCode) {
23868
+ const code = rawCode.trim();
23869
+ if (!code)
23870
+ return { text: REDEEM_USAGE, type: "info" };
23871
+ if (opts.isGuest?.())
23872
+ return { text: REDEEM_GUEST_LINE, type: "warning" };
23873
+ try {
23874
+ const res = await (opts.fetchImpl ?? fetch)(new URL("/me/redeem", opts.baseUrl), {
23875
+ method: "POST",
23876
+ headers: { authorization: `Bearer ${await opts.getToken()}`, "content-type": "application/json" },
23877
+ body: JSON.stringify({ code }),
23878
+ signal: AbortSignal.timeout(15000)
23879
+ });
23880
+ const body = await res.json().catch(() => {
23881
+ return;
23882
+ });
23883
+ if (res.ok && body?.ok === true && typeof body.credits_added === "number" && typeof body.balance === "number") {
23884
+ const n = (v) => v.toLocaleString("en-US");
23885
+ return { text: `Added ${n(body.credits_added)} credits. You now have ${n(body.balance)} credits.`, type: "info" };
23886
+ }
23887
+ if (typeof body?.code === "string" && REFUSALS.has(body.code) && typeof body.message === "string") {
23888
+ return { text: body.message, type: "warning" };
23889
+ }
23890
+ return { text: "Couldn't redeem your code (the server said no). Try again in a moment.", type: "error" };
23891
+ } catch {
23892
+ return { text: "Couldn't reach free-pi to redeem your code. Check your connection.", type: "error" };
23893
+ }
23894
+ }
23895
+
23705
23896
  // src/commands.ts
23706
23897
  import { homedir as homedir3 } from "node:os";
23707
23898
 
@@ -24035,6 +24226,10 @@ async function showReferrals(opts, ctx) {
24035
24226
  ctx.ui.notify(lines.join(`
24036
24227
  `), "info");
24037
24228
  }
24229
+ async function showRedeem(opts, args, ctx) {
24230
+ const r = await redeemCode(opts, args);
24231
+ ctx.ui.notify(r.text, r.type);
24232
+ }
24038
24233
  var REA_ARGS = ["-y", "rea-agents@6.7.0", "mcp"];
24039
24234
  var REA_DESCRIPTION = "Reverse-engineering tools (REA): native binaries, JS/Electron apps, APKs, .NET, websites. Use for lawful, authorized analysis only.";
24040
24235
  var REA_NODE_RANGE = "^22.19.0, ^24.11.0, or >=26.0.0";
@@ -24162,6 +24357,10 @@ function createFreePiCommandsExtension(opts) {
24162
24357
  description: "See how many friends signed up with your code and your active bonuses.",
24163
24358
  handler: (_args, ctx) => showReferrals(opts, ctx)
24164
24359
  });
24360
+ pi.registerCommand("redeem", {
24361
+ description: "Redeem a credit code from an email: /redeem FREEPI-XXXX-XXXX.",
24362
+ handler: (args, ctx) => showRedeem(opts, args, ctx)
24363
+ });
24165
24364
  pi.registerCommand("tos", {
24166
24365
  description: "Open the free-pi Terms of Service in your browser.",
24167
24366
  handler: (_args, ctx) => openTos(opts, ctx)
@@ -24191,6 +24390,7 @@ var ALL_COMMANDS = [
24191
24390
  { name: "/close-other-session", description: "free a stuck session on another machine" },
24192
24391
  { name: "/invite", description: "invite a friend, you both get +5k credits/day" },
24193
24392
  { name: "/referrals", description: "your referral status" },
24393
+ { name: "/redeem", description: "redeem a credit code" },
24194
24394
  { name: "/update", description: "get the latest free-pi" }
24195
24395
  ];
24196
24396
  var commands = () => ALL_COMMANDS.filter((c) => c.name !== "/hive" || process.env.FREEPI_HIVE === "1");
@@ -26207,6 +26407,7 @@ function buildRuntimeOptions(opts, sessionId) {
26207
26407
  errorNoticeExtension,
26208
26408
  createUpgradeNoticeExtension({ baseUrl: opts.baseUrl }),
26209
26409
  createOutputLimitExtension(),
26410
+ createToolOutcomesExtension(),
26210
26411
  toolGuardExtension,
26211
26412
  commandsExtension,
26212
26413
  headerExtension,
@@ -26913,6 +27114,13 @@ Couldn't finish GitHub sign-in. Continuing as a guest.
26913
27114
  ${creditsBack}`);
26914
27115
  reportRefillCardShown({ baseUrl: deps.baseUrl, jwt: jwt2 });
26915
27116
  }
27117
+ const redeemFlag = parseRedeemFlag(argv);
27118
+ if (redeemFlag.requested) {
27119
+ const r = await redeemCode({ baseUrl: deps.baseUrl, getToken: () => jwt2, isGuest: () => Boolean(me?.guest) }, redeemFlag.code ?? "");
27120
+ deps.log(`
27121
+ ${r.text}
27122
+ `);
27123
+ }
26916
27124
  const importFlag = parseImportFlag(argv);
26917
27125
  let session = resumeFlags.session;
26918
27126
  const lookupLatest = deps.findLatestSession ?? defaultFindLatestSession;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "free-pi-cli",
3
- "version": "0.6.10",
3
+ "version": "0.6.11",
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",