free-pi-cli 0.2.14 → 0.2.17

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-7619wn7y.js");
33
+ await import("./main-48xqf2dj.js");
@@ -48,7 +48,7 @@ function openBrowser(url, platform = process.platform, spawnImpl = spawn) {
48
48
  // package.json
49
49
  var package_default = {
50
50
  name: "free-pi-cli",
51
- version: "0.2.14",
51
+ version: "0.2.17",
52
52
  description: "Free, ad-supported distribution of the pi coding agent. Inference paid for by ads.",
53
53
  license: "MIT",
54
54
  type: "module",
@@ -14599,6 +14599,7 @@ var ConfigSchema = exports_external.object({
14599
14599
  contextCharBudget: exports_external.number().int().default(800000),
14600
14600
  accountsPerDayCeiling: exports_external.number().int().default(500),
14601
14601
  authRatePerIpPerHour: exports_external.number().int().default(30),
14602
+ webBuyUrlRatePerProviderPerHour: exports_external.number().int().default(20),
14602
14603
  tokenRatePerIpPerHour: exports_external.number().int().default(3600),
14603
14604
  consentRatePerIpPerHour: exports_external.number().int().default(60),
14604
14605
  adInlineTurnFrequency: exports_external.number().int().default(5),
@@ -14664,6 +14665,7 @@ function loadConfig(env = process.env) {
14664
14665
  contextCharBudget: envNumber(env.CONTEXT_CHAR_BUDGET),
14665
14666
  accountsPerDayCeiling: envNumber(env.ACCOUNTS_PER_DAY_CEILING),
14666
14667
  authRatePerIpPerHour: envNumber(env.AUTH_RATE_PER_IP_PER_HOUR),
14668
+ webBuyUrlRatePerProviderPerHour: envNumber(env.WEB_BUY_URL_RATE_PER_PROVIDER_PER_HOUR),
14667
14669
  tokenRatePerIpPerHour: envNumber(env.TOKEN_RATE_PER_IP_PER_HOUR),
14668
14670
  consentRatePerIpPerHour: envNumber(env.CONSENT_RATE_PER_IP_PER_HOUR),
14669
14671
  adInlineTurnFrequency: envNumber(env.AD_INLINE_TURN_FREQUENCY),
@@ -14772,6 +14774,7 @@ async function fetchMe(deps) {
14772
14774
  }
14773
14775
 
14774
14776
  // ../pi-ads/src/style.ts
14777
+ import { createHash } from "node:crypto";
14775
14778
  var OSC_RE = /\x1b\][\s\S]*?(?:\x07|\x1b\\)/g;
14776
14779
  var CSI_RE = /\x1b\[[0-9;?]*[ -/]*[@-~]/g;
14777
14780
  var C1_RE = /[\x80-\x9f]/g;
@@ -14791,8 +14794,9 @@ function truncate(text, maxWidth) {
14791
14794
  function pad(text, width) {
14792
14795
  return text.length >= width ? text.slice(0, width) : text + " ".repeat(width - text.length);
14793
14796
  }
14794
- function osc8(text, url2) {
14795
- return `\x1B]8;;${url2}\x07${text}\x1B]8;;\x07`;
14797
+ function osc8(text, url2, id) {
14798
+ const params = id ? `id=${id}` : "";
14799
+ return `\x1B]8;${params};${url2}\x07${text}\x1B]8;;\x07`;
14796
14800
  }
14797
14801
  function renderAdCard(creative, clickUrl, theme, columns) {
14798
14802
  const headline = sanitizeText(creative.headline);
@@ -14803,20 +14807,20 @@ function renderAdCard(creative, clickUrl, theme, columns) {
14803
14807
  const label = " AD ░▒▓ ";
14804
14808
  const top = `┌${label}${"─".repeat(Math.max(0, innerWidth + 2 - label.length))}┐`;
14805
14809
  const bottom = `└${"─".repeat(innerWidth + 2)}┘`;
14810
+ const linkId = createHash("sha256").update(url2).digest("hex").slice(0, 12);
14806
14811
  const row = (text, opts) => {
14807
14812
  const plain = pad(truncate(text, innerWidth), innerWidth);
14808
14813
  let styled = theme.fg("accent", plain);
14809
14814
  if (opts?.bold)
14810
14815
  styled = theme.bold(styled);
14811
- const content = opts?.linkUrl ? osc8(styled, opts.linkUrl) : styled;
14812
- return `│ ${content} │`;
14816
+ return `│ ${styled} │`;
14813
14817
  };
14814
14818
  return [
14815
- theme.fg("borderAccent", top),
14816
- row(headline, { bold: true }),
14817
- row(body),
14818
- row(`▸ ${cta}`, { linkUrl: url2 }),
14819
- theme.fg("borderAccent", bottom)
14819
+ theme.fg("borderAccent", osc8(top, url2, linkId)),
14820
+ osc8(row(headline, { bold: true }), url2, linkId),
14821
+ osc8(row(body), url2, linkId),
14822
+ osc8(row(`▸ ${cta}`), url2, linkId),
14823
+ theme.fg("borderAccent", osc8(bottom, url2, linkId))
14820
14824
  ];
14821
14825
  }
14822
14826
  function renderPlainAdLine(creative, clickUrl, theme, columns) {
@@ -14832,19 +14836,21 @@ function renderErrorLine(message, theme) {
14832
14836
 
14833
14837
  // ../pi-ads/src/banner.ts
14834
14838
  var BANNER_WIDGET_ID = "freepi-ad";
14835
- function createBannerRenderer() {
14839
+ function createBannerRenderer(opts = {}) {
14836
14840
  let lastPostedToken;
14837
14841
  return {
14838
14842
  async render(deps, ctx, config3, columnsOverride) {
14839
14843
  const ad = await fetchAdNext(deps, "banner");
14840
14844
  if (!ad) {
14841
14845
  ctx.ui.setWidget(BANNER_WIDGET_ID, undefined);
14846
+ opts.onBannerAd?.(undefined);
14842
14847
  return;
14843
14848
  }
14844
14849
  const theme = ctx.ui.theme;
14845
14850
  const columns = columnsOverride ?? process.stdout.columns ?? 80;
14846
14851
  const lines = columns >= config3.adMinColumns ? renderAdCard(ad.creative, ad.click_url, theme, columns) : [renderPlainAdLine(ad.creative, ad.click_url, theme, columns)];
14847
14852
  ctx.ui.setWidget(BANNER_WIDGET_ID, lines);
14853
+ opts.onBannerAd?.(ad.click_url);
14848
14854
  if (lastPostedToken !== ad.click_token) {
14849
14855
  lastPostedToken = ad.click_token;
14850
14856
  await postImpression(deps, ad.ad_id, ad.click_token);
@@ -14975,7 +14981,7 @@ function createAdsExtension(opts) {
14975
14981
  return {
14976
14982
  name: "free-pi-ads",
14977
14983
  factory(pi) {
14978
- const banner = createBannerRenderer();
14984
+ const banner = createBannerRenderer({ onBannerAd: opts.onBannerAd });
14979
14985
  const inline = createInlineRenderer();
14980
14986
  let bannerInterval;
14981
14987
  pi.on("session_start", async (_event, ctx) => {
@@ -19405,6 +19411,73 @@ function createBuyToolExtension(opts) {
19405
19411
  };
19406
19412
  }
19407
19413
 
19414
+ // src/docs.ts
19415
+ var FREE_PI_DOCS = `# free-pi
19416
+
19417
+ ## What free-pi is
19418
+ free-pi is a free, ad-supported distribution of the pi coding agent.
19419
+ You run \`npx free-pi-cli\`, sign in with GitHub, and get free inference with
19420
+ exciting models. Ads shown in the terminal pay for it. You never handle a
19421
+ model API key. Config and the sign-in token live under \`~/.free-pi/agent\`,
19422
+ separate from any \`~/.pi/agent\` install.
19423
+
19424
+ ## The promise
19425
+ Ad text never enters the model's context. Ads render only in the terminal,
19426
+ in a framed card, and the model never sees them.
19427
+ Coding sessions may be used to train models. This is on the consent screen
19428
+ at first run, before the GitHub sign-in.
19429
+
19430
+ ## The free allowance
19431
+ Each user gets a free allowance every day. The allowance can change with
19432
+ supply and demand. \`/usage\` shows how much of today's allowance is spent, as
19433
+ a percentage, with token counts.
19434
+ One session per account runs at a time. If a previous session is stuck,
19435
+ \`/close-other-session\` frees it.
19436
+
19437
+ ## Credits
19438
+ \`/buy-credits\` opens the buy page in your browser. The free allowance spends
19439
+ first. Purchased credits cover use beyond it and do not expire.
19440
+
19441
+ ## Commands
19442
+ - \`/usage\` — spend and remaining budget today
19443
+ - \`/support\` — visit today's advertiser
19444
+ - \`/tos\` — open the Terms of Service
19445
+ - \`/privacy-policy\` — open the Privacy Policy
19446
+ - \`/buy-credits\` — get more usage
19447
+ - \`/close-other-session\` — free a stuck session on another machine
19448
+ - \`/update\` — get the latest free-pi
19449
+ - \`/whats-new\` — what changed in recent releases
19450
+
19451
+ ## Support
19452
+ Today's sponsor keeps free-pi free. When the user asks how to help the
19453
+ project, or seems to like free-pi, suggest \`/support\` to visit the sponsor.
19454
+ More at https://freepi.ai
19455
+ `;
19456
+
19457
+ // src/docs-tool.ts
19458
+ var DOCS_TOOL_NAME = "free_pi_docs";
19459
+ var PARAMS3 = exports_typebox.Object({});
19460
+ function createDocsToolExtension() {
19461
+ return {
19462
+ name: "free-pi-docs-tool",
19463
+ factory(pi) {
19464
+ pi.registerTool({
19465
+ name: DOCS_TOOL_NAME,
19466
+ label: "About free-pi",
19467
+ description: "Returns the built-in free-pi guide. Read-only, takes no arguments, makes no network calls.",
19468
+ promptSnippet: "Explain free-pi itself: what it is, how the free allowance and ads work, credits, and the free-pi slash commands",
19469
+ parameters: PARAMS3,
19470
+ async execute() {
19471
+ return {
19472
+ content: [{ type: "text", text: FREE_PI_DOCS }],
19473
+ details: { ok: true }
19474
+ };
19475
+ }
19476
+ });
19477
+ }
19478
+ };
19479
+ }
19480
+
19408
19481
  // src/error-notice.ts
19409
19482
  var NOTICE_DEDUPE_MS = 15000;
19410
19483
  function parseErrorEnvelope(errorMessage) {
@@ -19476,6 +19549,27 @@ function createToolGuardExtension(opts) {
19476
19549
 
19477
19550
  // src/changelog.ts
19478
19551
  var CHANGELOG_HIGHLIGHTS = [
19552
+ {
19553
+ version: "0.2.17",
19554
+ highlights: [
19555
+ "/tos and /privacy-policy open the Terms and Privacy Policy in your browser.",
19556
+ "The consent screen and startup header now state that usage is funded by ads and training."
19557
+ ]
19558
+ },
19559
+ {
19560
+ version: "0.2.16",
19561
+ highlights: [
19562
+ "The whole ad card is now one clickable link.",
19563
+ "Ask the agent about free-pi: it can explain the allowance, credits, and commands."
19564
+ ]
19565
+ },
19566
+ {
19567
+ version: "0.2.15",
19568
+ highlights: [
19569
+ "New startup screen: the model, a welcome line, and the five free-pi commands.",
19570
+ "/support opens today's advertiser in your browser. It grants nothing."
19571
+ ]
19572
+ },
19479
19573
  {
19480
19574
  version: "0.2.14",
19481
19575
  highlights: [
@@ -19624,6 +19718,25 @@ async function buyCredits(opts, ctx) {
19624
19718
  (opts.openBrowserImpl ?? openBrowser)(page.url);
19625
19719
  ctx.ui.notify(buyPageText(page.url), "info");
19626
19720
  }
19721
+ async function openAdvertiser(opts, ctx) {
19722
+ const url2 = opts.getAdvertiserUrl?.();
19723
+ if (!url2) {
19724
+ ctx.ui.notify("No advertiser loaded yet.", "info");
19725
+ return;
19726
+ }
19727
+ (opts.openBrowserImpl ?? openBrowser)(url2);
19728
+ ctx.ui.notify("Opening today's advertiser in your browser. Thank you for supporting free-pi.", "info");
19729
+ }
19730
+ var TERMS_URL = "https://freepi.ai/terms";
19731
+ var PRIVACY_URL = "https://freepi.ai/privacy";
19732
+ async function openTos(opts, ctx) {
19733
+ (opts.openBrowserImpl ?? openBrowser)(TERMS_URL);
19734
+ ctx.ui.notify(`Opening the Terms of Service in your browser: ${TERMS_URL}`, "info");
19735
+ }
19736
+ async function openPrivacyPolicy(opts, ctx) {
19737
+ (opts.openBrowserImpl ?? openBrowser)(PRIVACY_URL);
19738
+ ctx.ui.notify(`Opening the Privacy Policy in your browser: ${PRIVACY_URL}`, "info");
19739
+ }
19627
19740
  async function showUsage(opts, ctx) {
19628
19741
  const r = await fetchUsageText(opts);
19629
19742
  ctx.ui.notify(r.text, r.ok ? "info" : "error");
@@ -19652,6 +19765,91 @@ function createFreePiCommandsExtension(opts) {
19652
19765
  description: "Buy more usage ($5 or $10 packs, card or USDC) — opens the buy page.",
19653
19766
  handler: (_args, ctx) => buyCredits(opts, ctx)
19654
19767
  });
19768
+ pi.registerCommand("support", {
19769
+ description: "Open today's advertiser in your browser to support free-pi.",
19770
+ handler: (_args, ctx) => openAdvertiser(opts, ctx)
19771
+ });
19772
+ pi.registerCommand("tos", {
19773
+ description: "Open the free-pi Terms of Service in your browser.",
19774
+ handler: (_args, ctx) => openTos(opts, ctx)
19775
+ });
19776
+ pi.registerCommand("privacy-policy", {
19777
+ description: "Open the free-pi Privacy Policy in your browser.",
19778
+ handler: (_args, ctx) => openPrivacyPolicy(opts, ctx)
19779
+ });
19780
+ }
19781
+ };
19782
+ }
19783
+
19784
+ // src/header.ts
19785
+ var COMMANDS = [
19786
+ { name: "/usage", description: "spend and remaining budget today" },
19787
+ { name: "/support", description: "visit today's advertiser" },
19788
+ { name: "/tos", description: "terms of service" },
19789
+ { name: "/privacy-policy", description: "privacy policy" },
19790
+ { name: "/buy-credits", description: "get more usage" },
19791
+ { name: "/close-other-session", description: "free a stuck session on another machine" },
19792
+ { name: "/update", description: "get the latest free-pi" }
19793
+ ];
19794
+ var NAME_COLUMN = 24;
19795
+ var WELCOME_LINE = "Welcome to Free Pi, ad-supported inference. Please visit our advertisers to support us.";
19796
+ var CONSENT_LINE_1 = "Usage is funded by ads and by training on your sessions. By using free-pi you consent.";
19797
+ var CONSENT_LINE_2 = "See /tos and /privacy-policy.";
19798
+ var EXPANDED_HINT_LINE = "esc interrupt · ctrl+c clear / exit · / commands · ! bash";
19799
+ function truncate2(text, maxWidth) {
19800
+ if (maxWidth <= 0)
19801
+ return "";
19802
+ if (text.length <= maxWidth)
19803
+ return text;
19804
+ if (maxWidth <= 1)
19805
+ return text.slice(0, maxWidth);
19806
+ return `${text.slice(0, maxWidth - 1)}…`;
19807
+ }
19808
+ function headerLines(theme, modelName, expanded, width = Number.POSITIVE_INFINITY) {
19809
+ const inner = width - 1;
19810
+ const fit = (plain, styled) => plain.length <= inner ? styled() : theme.fg("dim", truncate2(plain, inner));
19811
+ const titleRest = ` · ${modelName} · ctrl+o for help`;
19812
+ const title = fit(`free-pi${titleRest}`, () => `${theme.bold(theme.fg("accent", "free-pi"))}${theme.fg("dim", titleRest)}`);
19813
+ const commandLines = COMMANDS.map(({ name, description }) => {
19814
+ const padded = ` ${name}`.padEnd(NAME_COLUMN, " ");
19815
+ return fit(`${padded}${description}`, () => `${padded}${theme.fg("dim", description)}`);
19816
+ });
19817
+ const dim = (plain) => fit(plain, () => theme.fg("dim", plain));
19818
+ const lines = [
19819
+ "",
19820
+ title,
19821
+ "",
19822
+ dim(WELCOME_LINE),
19823
+ "",
19824
+ dim(CONSENT_LINE_1),
19825
+ dim(CONSENT_LINE_2),
19826
+ "",
19827
+ ...commandLines,
19828
+ ""
19829
+ ];
19830
+ if (expanded)
19831
+ lines.push(dim(EXPANDED_HINT_LINE));
19832
+ return lines.map((line) => line === "" ? line : ` ${line}`);
19833
+ }
19834
+ function createHeaderExtension(opts) {
19835
+ return {
19836
+ name: "free-pi-header",
19837
+ factory(pi) {
19838
+ pi.on("session_start", (_event, ctx) => {
19839
+ ctx.ui.setHeader((tui, theme) => {
19840
+ let expanded = false;
19841
+ return {
19842
+ render(width) {
19843
+ return headerLines(theme, opts.modelName, expanded, width);
19844
+ },
19845
+ invalidate() {},
19846
+ setExpanded(next) {
19847
+ expanded = next;
19848
+ tui.requestRender();
19849
+ }
19850
+ };
19851
+ });
19852
+ });
19655
19853
  }
19656
19854
  };
19657
19855
  }
@@ -19676,7 +19874,10 @@ function catalogModelsFor(opts) {
19676
19874
  const id = opts.model || MODEL_ID;
19677
19875
  return [{ id, name: id }];
19678
19876
  }
19679
- var ALLOWED_TOOL_NAMES = [...SAFE_TOOLS, USAGE_TOOL_NAME, BUY_TOOL_NAME];
19877
+ var ALLOWED_TOOL_NAMES = [...SAFE_TOOLS, USAGE_TOOL_NAME, BUY_TOOL_NAME, DOCS_TOOL_NAME];
19878
+ function resolveModelName(opts) {
19879
+ return opts.models?.[0]?.name || opts.model || MODEL_ID;
19880
+ }
19680
19881
  function buildRuntimeOptions(opts, sessionId) {
19681
19882
  const models = catalogModelsFor(opts);
19682
19883
  const providerExtension = {
@@ -19685,10 +19886,14 @@ function buildRuntimeOptions(opts, sessionId) {
19685
19886
  pi.registerProvider(PROVIDER_NAME, buildProviderConfig(opts.baseUrl, opts.jwt, sessionId, models));
19686
19887
  }
19687
19888
  };
19889
+ let advertiserUrl;
19688
19890
  const adsExtension = createAdsExtension({
19689
19891
  baseUrl: opts.baseUrl,
19690
19892
  getToken: () => opts.jwt,
19691
- sessionId
19893
+ sessionId,
19894
+ onBannerAd: (u) => {
19895
+ advertiserUrl = u;
19896
+ }
19692
19897
  });
19693
19898
  const usageToolExtension = createUsageToolExtension({
19694
19899
  baseUrl: opts.baseUrl,
@@ -19698,6 +19903,7 @@ function buildRuntimeOptions(opts, sessionId) {
19698
19903
  baseUrl: opts.baseUrl,
19699
19904
  getToken: () => opts.jwt
19700
19905
  });
19906
+ const docsToolExtension = createDocsToolExtension();
19701
19907
  const errorNoticeExtension = createErrorNoticeExtension();
19702
19908
  const toolGuardExtension = createToolGuardExtension({
19703
19909
  baseUrl: opts.baseUrl,
@@ -19706,22 +19912,29 @@ function buildRuntimeOptions(opts, sessionId) {
19706
19912
  });
19707
19913
  const commandsExtension = createFreePiCommandsExtension({
19708
19914
  baseUrl: opts.baseUrl,
19709
- getToken: () => opts.jwt
19915
+ getToken: () => opts.jwt,
19916
+ getAdvertiserUrl: () => advertiserUrl
19917
+ });
19918
+ const headerExtension = createHeaderExtension({
19919
+ modelName: resolveModelName(opts)
19710
19920
  });
19711
19921
  const extensionFactories = [
19712
19922
  providerExtension,
19713
19923
  adsExtension,
19714
19924
  usageToolExtension,
19715
19925
  buyToolExtension,
19926
+ docsToolExtension,
19716
19927
  errorNoticeExtension,
19717
19928
  toolGuardExtension,
19718
- commandsExtension
19929
+ commandsExtension,
19930
+ headerExtension
19719
19931
  ];
19720
19932
  const settingsManager = SettingsManager.inMemory({
19721
19933
  defaultProvider: PROVIDER_NAME,
19722
19934
  defaultModel: models[0].id,
19723
19935
  defaultTools: [...SAFE_TOOLS],
19724
- packages: []
19936
+ packages: [],
19937
+ quietStartup: true
19725
19938
  });
19726
19939
  return {
19727
19940
  settingsManager,
@@ -19797,18 +20010,28 @@ import { createInterface } from "node:readline/promises";
19797
20010
  var CONSENT_TEXT = `
19798
20011
  free-pi — before you start
19799
20012
 
19800
- * Inference is free. Ads pay for it.
19801
- * A persistent ad banner and occasional inline ad cards render in the
19802
- terminal UI. Ad content never enters the model's context.
19803
- * Your coding sessions may be used to train models.
20013
+ * Inference is free. Ads pay for it, and so does training.
20014
+ * Ad cards render in the terminal UI. Ad content never enters the model's
20015
+ context.
20016
+ * Your coding sessions (prompts, code, files, and model responses) are saved
20017
+ as traces and used to train models. We may share or sell that data to
20018
+ third parties. Once you accept, there is no opt-out, except where the law
20019
+ gives you one.
20020
+ * We do our best to remove personal information from traces before we store
20021
+ them. We cannot promise it is perfectly clean.
20022
+ * free-pi is in alpha and testing. There are no warranties or guarantees,
20023
+ expressed or implied, and no guarantee of uptime.
19804
20024
  * If GitHub email capture is enabled, we collect your GitHub email and may
19805
20025
  use it to contact you about the service or product.
19806
20026
  * free-pi-cli auto-updates itself when a new version is available. Disable
19807
20027
  with FREEPI_NO_AUTO_UPDATE or {"autoUpdate":false} in ~/.free-pi/config.json.
19808
20028
 
20029
+ By accepting you agree to the Terms of Service (https://freepi.ai/terms)
20030
+ and the Privacy Policy (https://freepi.ai/privacy).
20031
+
19809
20032
  Decline and nothing is sent to the free-pi server.
19810
20033
  `.trim();
19811
- var CONSENT_VERSION = "1";
20034
+ var CONSENT_VERSION = "2";
19812
20035
  var NON_INTERACTIVE_CONSENT_TEXT = "free-pi first-run consent needs an interactive terminal (stdin is not a TTY). " + "Run `npx free-pi-cli` in a terminal once to review and accept; " + "after that, non-interactive runs work.";
19813
20036
  var CONSENT_PROMPT = "Accept? [y/N] ";
19814
20037
  function parseConsentAnswer(answer) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "free-pi-cli",
3
- "version": "0.2.14",
3
+ "version": "0.2.17",
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",