free-pi-cli 0.2.19 → 0.3.0

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
@@ -27,4 +27,4 @@ if (err) {
27
27
  console.error(err);
28
28
  process.exit(1);
29
29
  }
30
- await import("./main-tc7xq892.js");
30
+ await import("./main-376chsck.js");
@@ -44,7 +44,7 @@ function openBrowser(url, platform = process.platform, spawnImpl = spawn) {
44
44
  // package.json
45
45
  var package_default = {
46
46
  name: "free-pi-cli",
47
- version: "0.2.19",
47
+ version: "0.3.0",
48
48
  description: "Free, ad-supported distribution of the pi coding agent. Inference paid for by ads.",
49
49
  license: "MIT",
50
50
  type: "module",
@@ -63,7 +63,7 @@ var package_default = {
63
63
  prepublishOnly: "bun run build"
64
64
  },
65
65
  dependencies: {
66
- "@earendil-works/pi-coding-agent": "0.84.4"
66
+ "@earendil-works/pi-coding-agent": "1.2.0"
67
67
  },
68
68
  devDependencies: {
69
69
  "@freepi/pi-ads": "workspace:*",
@@ -80,10 +80,11 @@ var HELP_TEXT = `
80
80
  free-pi — free AI coding in your terminal; ads pay for the inference.
81
81
 
82
82
  Usage:
83
- free-pi-cli start the coding agent (first run: consent + GitHub sign-in)
84
- free-pi-cli logout remove the stored sign-in token; the next run signs in again
85
- free-pi-cli --version print the CLI version
86
- free-pi-cli --help show this help
83
+ free-pi-cli start the coding agent (first run: consent + GitHub sign-in)
84
+ free-pi-cli --session <id> resume a previous session
85
+ free-pi-cli logout remove the stored sign-in token; the next run signs in again
86
+ free-pi-cli --version print the CLI version
87
+ free-pi-cli --help show this help
87
88
 
88
89
  Environment:
89
90
  FREEPI_BASE_URL override the backend base URL (default https://api.freepi.ai)
@@ -96,6 +97,9 @@ function parseCliArgs(argv) {
96
97
  const [first, ...rest] = argv;
97
98
  if (first === undefined)
98
99
  return { kind: "run" };
100
+ if (first === "--session") {
101
+ return rest.length > 1 ? { kind: "unknown", arg: rest[1] } : { kind: "run" };
102
+ }
99
103
  let kind;
100
104
  if (first === "--help" || first === "-h" || first === "help")
101
105
  kind = "help";
@@ -4851,6 +4855,13 @@ var ClientVersionResponseSchema = object({
4851
4855
  var SessionResetResponseSchema = object({
4852
4856
  released: boolean2()
4853
4857
  });
4858
+ var BugReportRequestSchema = object({
4859
+ description: string2().trim().min(1).max(4000),
4860
+ session_id: string2().max(128).optional(),
4861
+ cli_version: string2().max(32).optional(),
4862
+ model: string2().max(128).optional()
4863
+ });
4864
+ var BugReportResponseSchema = object({ id: string2() });
4854
4865
  // ../shared/src/errors.ts
4855
4866
  function assertExhaustiveErrorCode(code) {
4856
4867
  throw new Error(`unhandled error code: ${String(code)}`);
@@ -4865,6 +4876,8 @@ var ConfigSchema = object({
4865
4876
  accountsPerDayCeiling: number2().int().default(500),
4866
4877
  authRatePerIpPerHour: number2().int().default(30),
4867
4878
  webBuyUrlRatePerProviderPerHour: number2().int().default(20),
4879
+ handoffTokenRatePerProviderPerHour: number2().int().default(60),
4880
+ handoffHourlyCompletionCap: number2().int().default(120),
4868
4881
  tokenRatePerIpPerHour: number2().int().default(3600),
4869
4882
  consentRatePerIpPerHour: number2().int().default(60),
4870
4883
  adInlineTurnFrequency: number2().int().default(5),
@@ -4931,6 +4944,8 @@ function loadConfig(env = process.env) {
4931
4944
  accountsPerDayCeiling: envNumber(env.ACCOUNTS_PER_DAY_CEILING),
4932
4945
  authRatePerIpPerHour: envNumber(env.AUTH_RATE_PER_IP_PER_HOUR),
4933
4946
  webBuyUrlRatePerProviderPerHour: envNumber(env.WEB_BUY_URL_RATE_PER_PROVIDER_PER_HOUR),
4947
+ handoffTokenRatePerProviderPerHour: envNumber(env.HANDOFF_TOKEN_RATE_PER_PROVIDER_PER_HOUR),
4948
+ handoffHourlyCompletionCap: envNumber(env.HANDOFF_HOURLY_COMPLETION_CAP),
4934
4949
  tokenRatePerIpPerHour: envNumber(env.TOKEN_RATE_PER_IP_PER_HOUR),
4935
4950
  consentRatePerIpPerHour: envNumber(env.CONSENT_RATE_PER_IP_PER_HOUR),
4936
4951
  adInlineTurnFrequency: envNumber(env.AD_INLINE_TURN_FREQUENCY),
@@ -5186,6 +5201,8 @@ function mapErrorCode(code) {
5186
5201
  return "too many requests this hour on the free tier — wait for the Retry-After window";
5187
5202
  case "account_review":
5188
5203
  return "this account is under review — contact support";
5204
+ case "ads_required":
5205
+ return "ads aren't displaying for this account, so free completions are paused — run free-pi through `npx free-pi-cli`, then retry";
5189
5206
  default:
5190
5207
  return assertExhaustiveErrorCode(code);
5191
5208
  }
@@ -5573,9 +5590,10 @@ exciting models. Ads shown in the terminal pay for it. You never handle a
5573
5590
  model API key. Config and the sign-in token live under \`~/.free-pi/agent\`,
5574
5591
  separate from any \`~/.pi/agent\` install.
5575
5592
 
5576
- ## The promise
5577
- Ad text never enters the model's context. Ads render only in the terminal,
5578
- in a framed card, and the model never sees them.
5593
+ ## Ads and your data
5594
+ Ads render in the terminal, in a framed card. Some ads come from Gravity, a
5595
+ third-party ad provider that receives excerpts of your recent conversation
5596
+ to pick a relevant ad.
5579
5597
  Coding sessions may be used to train models. This is on the consent screen
5580
5598
  at first run, before the GitHub sign-in.
5581
5599
 
@@ -5699,8 +5717,83 @@ function createToolGuardExtension(opts) {
5699
5717
  };
5700
5718
  }
5701
5719
 
5720
+ // src/bug-report.ts
5721
+ var BUG_USAGE = "Usage: /bug <what went wrong>";
5722
+ var BUG_DESCRIPTION = "Report a bug to free-pi";
5723
+ function buildBugRequest(description, ctx) {
5724
+ return { description, session_id: ctx.sessionId, cli_version: CLI_VERSION, model: ctx.model };
5725
+ }
5726
+ function mapBugFailure(status) {
5727
+ if (status === 401)
5728
+ return "Couldn't send the bug report: you're signed out. Restart free-pi-cli to sign in again.";
5729
+ if (status === 429)
5730
+ return "Couldn't send the bug report: you've reached today's limit of 20 reports. Try again tomorrow.";
5731
+ if (status === 413)
5732
+ return "Couldn't send the bug report: it is too large.";
5733
+ if (status === 400)
5734
+ return "Couldn't send the bug report: the description is too long (4000 characters max).";
5735
+ return `Couldn't send the bug report (the server returned ${status}). Try again in a moment.`;
5736
+ }
5737
+ async function sendBugReport(opts, body) {
5738
+ try {
5739
+ const res = await (opts.fetchImpl ?? fetch)(`${opts.baseUrl}/bug-reports`, {
5740
+ method: "POST",
5741
+ headers: { Authorization: `Bearer ${await opts.getToken()}`, "Content-Type": "application/json" },
5742
+ body: JSON.stringify(body),
5743
+ signal: AbortSignal.timeout(1e4)
5744
+ });
5745
+ if (!res.ok)
5746
+ return { ok: false, message: mapBugFailure(res.status) };
5747
+ const parsed = BugReportResponseSchema.safeParse(await res.json());
5748
+ if (!parsed.success)
5749
+ return { ok: false, message: mapBugFailure(res.status) };
5750
+ return { ok: true, id: parsed.data.id };
5751
+ } catch {
5752
+ return { ok: false, message: "Couldn't reach free-pi to send the bug report — check your connection." };
5753
+ }
5754
+ }
5755
+ function makeBugHandler(mode, opts) {
5756
+ return async (hint) => {
5757
+ if (!hint)
5758
+ return mode.showStatus(BUG_USAGE);
5759
+ const r = await sendBugReport(opts, buildBugRequest(hint, { sessionId: mode.session.sessionId, model: mode.session.model?.id }));
5760
+ if (r.ok)
5761
+ return mode.showStatus(`Thanks — bug report received (#${r.id}).`);
5762
+ if (!mode.editor.getText())
5763
+ mode.editor.setText(`/bug ${hint}`);
5764
+ mode.showError(r.message);
5765
+ };
5766
+ }
5767
+ function installBugHandler(mode, opts) {
5768
+ mode.handleBugCommand = makeBugHandler(mode, opts);
5769
+ }
5770
+ function registerBugAutocomplete(pi) {
5771
+ pi.on("session_start", (_event, ctx) => {
5772
+ ctx.ui.addAutocompleteProvider((cur) => ({
5773
+ triggerCharacters: cur.triggerCharacters,
5774
+ applyCompletion: (...a) => cur.applyCompletion(...a),
5775
+ shouldTriggerFileCompletion: cur.shouldTriggerFileCompletion?.bind(cur),
5776
+ getSuggestions: async (...a) => {
5777
+ const r = await cur.getSuggestions(...a);
5778
+ return r && {
5779
+ ...r,
5780
+ items: r.items.map((i) => i.value === "bug" && i.label === "bug" ? { ...i, description: BUG_DESCRIPTION } : i)
5781
+ };
5782
+ }
5783
+ }));
5784
+ });
5785
+ }
5786
+
5702
5787
  // src/changelog.ts
5703
5788
  var CHANGELOG_HIGHLIGHTS = [
5789
+ {
5790
+ version: "0.3.0",
5791
+ highlights: [
5792
+ "Upgraded the underlying pi agent engine to 1.2.0.",
5793
+ "/bug now sends reports to the free-pi team.",
5794
+ "`npx free-pi-cli --session <id>` resumes a session again."
5795
+ ]
5796
+ },
5704
5797
  {
5705
5798
  version: "0.2.19",
5706
5799
  highlights: [
@@ -5911,6 +6004,7 @@ function createFreePiCommandsExtension(opts) {
5911
6004
  return {
5912
6005
  name: "free-pi-commands",
5913
6006
  factory(pi) {
6007
+ registerBugAutocomplete(pi);
5914
6008
  pi.registerCommand("close-other-session", {
5915
6009
  description: "Clear your other running free-pi session (use if a previous one is stuck).",
5916
6010
  handler: (_args, ctx) => closeOtherSession(opts, ctx)
@@ -6054,7 +6148,7 @@ function resolveFreePiScope(modelRuntime, providerName, modelIds, env = process.
6054
6148
 
6055
6149
  // src/resume-line.ts
6056
6150
  var RESUME_LABEL = "To resume this session:";
6057
- var RESUME_COMMAND_RE = /(To resume this session:(?:\u001b\[[0-9;]*m|\s)*)pi(?=\s)/g;
6151
+ var RESUME_COMMAND_RE = /(To resume this session:(?:\u001b\[[0-9;]*m|\s)*)pi(?=\s)( --session-dir (?:'(?:[^']|'\\'')*'|\S+))?/g;
6058
6152
  function rewriteResumeLine(chunk) {
6059
6153
  if (!chunk.includes(RESUME_LABEL))
6060
6154
  return chunk;
@@ -6140,7 +6234,10 @@ function buildRuntimeOptions(opts, sessionId) {
6140
6234
  defaultModel: models[0].id,
6141
6235
  defaultTools: [...SAFE_TOOLS],
6142
6236
  packages: [],
6143
- quietStartup: true
6237
+ quietStartup: true,
6238
+ tuiMode: "regular",
6239
+ theme: "light/dark",
6240
+ enableInstallTelemetry: false
6144
6241
  });
6145
6242
  return {
6146
6243
  settingsManager,
@@ -6166,6 +6263,7 @@ async function createSessionManager(cwd, sessionDir, sessionId, api = SessionMan
6166
6263
  return api.create(cwd, sessionDir);
6167
6264
  }
6168
6265
  process.env.PI_SKIP_VERSION_CHECK = "1";
6266
+ process.env.PI_TELEMETRY = "0";
6169
6267
  patchResumeLine(process.stdout);
6170
6268
  async function launchPi(opts) {
6171
6269
  const cwd = process.cwd();
@@ -6207,6 +6305,7 @@ async function launchPi(opts) {
6207
6305
  initialImages: [],
6208
6306
  initialMessages: []
6209
6307
  });
6308
+ installBugHandler(mode, { baseUrl: opts.baseUrl, getToken: () => opts.jwt });
6210
6309
  await mode.run();
6211
6310
  return sm.getSessionId();
6212
6311
  }
@@ -6219,8 +6318,9 @@ var CONSENT_TEXT = `
6219
6318
  free-pi — before you start
6220
6319
 
6221
6320
  * Inference is free. Ads pay for it, and so does training.
6222
- * Ad cards render in the terminal UI. Ad content never enters the model's
6223
- context.
6321
+ * Ad cards render in the terminal UI. Some ads come from a third-party ad
6322
+ provider (Gravity), which receives excerpts of your recent conversation
6323
+ to pick a relevant ad.
6224
6324
  * Your coding sessions (prompts, code, files, and model responses) are saved
6225
6325
  as traces and used to train models. We may share or sell that data to
6226
6326
  third parties. Once you accept, there is no opt-out, except where the law
@@ -6239,7 +6339,7 @@ and the Privacy Policy (https://freepi.ai/privacy).
6239
6339
 
6240
6340
  Decline and nothing is sent to the free-pi server.
6241
6341
  `.trim();
6242
- var CONSENT_VERSION = "2";
6342
+ var CONSENT_VERSION = "3";
6243
6343
  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.";
6244
6344
  var CONSENT_PROMPT = "Accept? [y/N] ";
6245
6345
  function parseConsentAnswer(answer) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "free-pi-cli",
3
- "version": "0.2.19",
3
+ "version": "0.3.0",
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",
@@ -19,7 +19,7 @@
19
19
  "prepublishOnly": "bun run build"
20
20
  },
21
21
  "dependencies": {
22
- "@earendil-works/pi-coding-agent": "0.84.4"
22
+ "@earendil-works/pi-coding-agent": "1.2.0"
23
23
  },
24
24
  "devDependencies": {
25
25
  "@freepi/pi-ads": "workspace:*",