free-pi-cli 0.6.1 → 0.6.2

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-zv5s2zn8.js");
33
+ await import("./main-cwwgr8bh.js");
@@ -5,8 +5,8 @@ import {
5
5
  } from "./index-n5qs9pq1.js";
6
6
 
7
7
  // src/main.ts
8
- import { realpathSync as realpathSync3, writeFileSync as writeFileSync3 } from "node:fs";
9
- import { dirname as dirname10, join as join11 } from "node:path";
8
+ import { realpathSync as realpathSync4, writeFileSync as writeFileSync3 } from "node:fs";
9
+ import { dirname as dirname10, join as join12 } from "node:path";
10
10
 
11
11
  // src/browser.ts
12
12
  import { spawn } from "node:child_process";
@@ -29,6 +29,8 @@ function buildOpenCommand(url, platform) {
29
29
  windowsVerbatimArguments: true
30
30
  };
31
31
  }
32
+ if (platform === "android")
33
+ return { command: "termux-open-url", args: [url] };
32
34
  return { command: platform === "darwin" ? "open" : "xdg-open", args: [url] };
33
35
  }
34
36
  function openBrowser(url, platform = process.platform, spawnImpl = spawn, exitWaitMs = 2000) {
@@ -14410,7 +14412,8 @@ var GuestStartRequestSchema = exports_external.object({
14410
14412
  });
14411
14413
  var ExperimentVariantSchema = exports_external.enum(["control", "treatment"]);
14412
14414
  var ExperimentAssignmentSchema = exports_external.object({
14413
- guest_handoff: ExperimentVariantSchema.optional()
14415
+ guest_handoff: ExperimentVariantSchema.optional(),
14416
+ first_task: ExperimentVariantSchema.optional()
14414
14417
  });
14415
14418
  var GuestStartResponseSchema = exports_external.object({
14416
14419
  token: exports_external.string(),
@@ -14432,7 +14435,7 @@ var ReturnBonusTelemetrySchema = exports_external.object({
14432
14435
  type: exports_external.enum(["card_shown", "credits_back"])
14433
14436
  });
14434
14437
  var ExperimentExposureSchema = exports_external.object({
14435
- experiment: exports_external.enum(["resume_on_launch"]),
14438
+ experiment: exports_external.enum(["resume_on_launch", "retain.first_task"]),
14436
14439
  event: exports_external.enum(["offer_shown", "offer_accepted"])
14437
14440
  });
14438
14441
  var ReturnBonusSurfaceSchema = exports_external.object({
@@ -14626,6 +14629,18 @@ var ConfigSchema = exports_external.object({
14626
14629
  emailProviderUrl: exports_external.string().optional(),
14627
14630
  hiveEnabled: exports_external.boolean().default(false),
14628
14631
  providerOrder: exports_external.array(exports_external.string()).optional(),
14632
+ openrouterFlashProviderOrder: exports_external.array(exports_external.string()).default([
14633
+ "streamlake/fp8",
14634
+ "deepinfra/fp8",
14635
+ "gmicloud/fp8",
14636
+ "digitalocean"
14637
+ ]),
14638
+ openrouterFlashProviderIgnore: exports_external.array(exports_external.string()).default(["baidu", "relace"]),
14639
+ openrouterFlashMaxPricePrompt: exports_external.string().default("0.12"),
14640
+ openrouterFlashMaxPriceCompletion: exports_external.string().default("0.25"),
14641
+ openrouterSessionPinTtlSeconds: exports_external.number().int().min(1).default(1800),
14642
+ costAlertMultiplier: exports_external.number().int().min(1).default(2),
14643
+ costAlertMinHours: exports_external.number().int().min(1).default(7),
14629
14644
  tierNewMaxDays: exports_external.number().int().default(30),
14630
14645
  tierYoungMaxDays: exports_external.number().int().default(365),
14631
14646
  tierCapNewUsd: exports_external.number().default(0.45),
@@ -14710,6 +14725,12 @@ function envList(value) {
14710
14725
  const items = value.split(",").map((s) => s.trim()).filter((s) => s.length > 0);
14711
14726
  return items.length > 0 ? items : undefined;
14712
14727
  }
14728
+ function envDecimalString(value) {
14729
+ if (value === undefined || value.trim() === "")
14730
+ return;
14731
+ const t = value.trim();
14732
+ return /^\d+(\.\d+)?$/.test(t) ? t : undefined;
14733
+ }
14713
14734
  function loadConfig(env = process.env) {
14714
14735
  return ConfigSchema.parse({
14715
14736
  perUserDailyCapUsd: envNumber(env.PER_USER_DAILY_CAP_USD),
@@ -14742,6 +14763,13 @@ function loadConfig(env = process.env) {
14742
14763
  emailProviderUrl: env.EMAIL_PROVIDER_URL?.trim() || undefined,
14743
14764
  hiveEnabled: envBoolean(env.HIVE_ENABLED),
14744
14765
  providerOrder: envList(env.PROVIDER_ORDER),
14766
+ openrouterFlashProviderOrder: envList(env.OPENROUTER_FLASH_PROVIDER_ORDER),
14767
+ openrouterFlashProviderIgnore: envList(env.OPENROUTER_FLASH_PROVIDER_IGNORE),
14768
+ openrouterFlashMaxPricePrompt: envDecimalString(env.OPENROUTER_FLASH_MAX_PRICE_PROMPT),
14769
+ openrouterFlashMaxPriceCompletion: envDecimalString(env.OPENROUTER_FLASH_MAX_PRICE_COMPLETION),
14770
+ openrouterSessionPinTtlSeconds: envNumber(env.OPENROUTER_SESSION_PIN_TTL_SECONDS),
14771
+ costAlertMultiplier: envNumber(env.COST_ALERT_MULTIPLIER),
14772
+ costAlertMinHours: envNumber(env.COST_ALERT_MIN_HOURS),
14745
14773
  tierNewMaxDays: envNumber(env.TIER_NEW_MAX_DAYS),
14746
14774
  tierYoungMaxDays: envNumber(env.TIER_YOUNG_MAX_DAYS),
14747
14775
  tierCapNewUsd: envNumber(env.TIER_CAP_NEW_USD),
@@ -15029,7 +15057,7 @@ var HiveClassifyResponseSchema = exports_external.object({
15029
15057
  // package.json
15030
15058
  var package_default = {
15031
15059
  name: "free-pi-cli",
15032
- version: "0.6.1",
15060
+ version: "0.6.2",
15033
15061
  description: "Free, ad-supported distribution of the pi coding agent. Inference paid for by ads.",
15034
15062
  license: "MIT",
15035
15063
  type: "module",
@@ -15768,6 +15796,34 @@ function createAdsExtension(opts) {
15768
15796
  import { dirname as dirname8, join as join10, resolve as resolve3 } from "node:path";
15769
15797
  import { fileURLToPath as fileURLToPath4 } from "node:url";
15770
15798
 
15799
+ // src/output-limit.ts
15800
+ var MAX_OUTPUT_TOKENS = 8192;
15801
+ var OUTPUT_LIMIT_MESSAGE = "This tool call was cut off by the output limit before it finished. It either did not run or ran with incomplete content, " + "so a file may be partly written. Check what was written, then redo the work in smaller parts " + "(for example write the first part, then add the rest with edit).";
15802
+ function createOutputLimitExtension() {
15803
+ let truncated = new Set;
15804
+ return {
15805
+ name: "free-pi-output-limit",
15806
+ factory(pi) {
15807
+ pi.on("message_end", (event) => {
15808
+ const msg = event.message;
15809
+ if (msg.role === "assistant") {
15810
+ const calls = (msg.content ?? []).filter((c) => c.type === "toolCall" && c.id);
15811
+ const last = msg.content?.[msg.content.length - 1];
15812
+ const atCap = (msg.usage?.output ?? 0) >= MAX_OUTPUT_TOKENS && last?.type === "toolCall" && last.id;
15813
+ truncated = new Set(msg.stopReason === "length" ? calls.map((c) => c.id) : atCap ? [last.id] : []);
15814
+ return;
15815
+ }
15816
+ if (msg.role !== "toolResult" || !msg.toolCallId || !truncated.has(msg.toolCallId))
15817
+ return;
15818
+ truncated.delete(msg.toolCallId);
15819
+ return {
15820
+ message: { ...event.message, content: [{ type: "text", text: OUTPUT_LIMIT_MESSAGE }], isError: true }
15821
+ };
15822
+ });
15823
+ }
15824
+ };
15825
+ }
15826
+
15771
15827
  // src/provider.ts
15772
15828
  var PROVIDER_NAME = "free-pi";
15773
15829
  var DEFENSE_PROVIDER_NAME = "free-pi-defense";
@@ -15798,7 +15854,7 @@ function buildProviderConfig(baseUrl, jwt2, sessionId, models, name = PROVIDER_N
15798
15854
  input: ["text"],
15799
15855
  cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
15800
15856
  contextWindow: 1048576,
15801
- maxTokens: 8192
15857
+ maxTokens: MAX_OUTPUT_TOKENS
15802
15858
  }))
15803
15859
  };
15804
15860
  }
@@ -20440,6 +20496,12 @@ You can run free-pi in more than one terminal. If a previous session is stuck,
20440
20496
  \`/buy-credits\` opens the buy page in your browser. The free allowance spends
20441
20497
  first. Purchased credits cover use beyond it and do not expire.
20442
20498
 
20499
+ ## Model and restarting
20500
+ \`/model\` shows the current model and lets you switch it; the model can change
20501
+ during a session. Credits are free-pi's usage unit, not model tokens. To
20502
+ restart, exit and run \`npx free-pi-cli\` again; \`--continue\` resumes the last
20503
+ session.
20504
+
20443
20505
  ## Commands
20444
20506
  - \`/usage\` — spend and remaining budget today
20445
20507
  - \`/support\` — visit today's advertiser
@@ -20771,8 +20833,40 @@ import { join as join5 } from "node:path";
20771
20833
  import { fileURLToPath as fileURLToPath2 } from "node:url";
20772
20834
  import {
20773
20835
  createBashToolDefinition,
20836
+ createLocalPowerShellOperations,
20774
20837
  createReadToolDefinition
20775
20838
  } from "@earendil-works/pi-coding-agent";
20839
+
20840
+ // src/windows-shell.ts
20841
+ import { getPowerShellConfig, getShellConfig } from "@earendil-works/pi-coding-agent";
20842
+ var works = (f) => () => {
20843
+ try {
20844
+ f();
20845
+ return true;
20846
+ } catch {
20847
+ return false;
20848
+ }
20849
+ };
20850
+ var defaultHasBash = works(() => getShellConfig());
20851
+ var defaultHasPowerShell = works(() => getPowerShellConfig());
20852
+ function detectShell(p = {}) {
20853
+ if ((p.platform ?? process.platform) !== "win32")
20854
+ return "posix";
20855
+ if ((p.hasBash ?? defaultHasBash)())
20856
+ return "bash";
20857
+ return (p.hasPowerShell ?? defaultHasPowerShell)() ? "powershell" : "none";
20858
+ }
20859
+ function shellPromptLine(kind) {
20860
+ if (kind === "bash") {
20861
+ return "Environment: Windows with a bash shell (usually Git Bash). The bash tool runs POSIX shell syntax with forward slashes; Git Bash writes drive paths as /c/Users/...";
20862
+ }
20863
+ if (kind === "powershell") {
20864
+ return "Environment: Windows without bash. The bash tool runs its command in PowerShell, so write PowerShell syntax (Get-ChildItem, $env:VAR, ;) and Windows paths like C:\\Users\\..., never POSIX shell syntax.";
20865
+ }
20866
+ return;
20867
+ }
20868
+
20869
+ // src/tool-aliases.ts
20776
20870
  function aliasArgs(input, key, aliases) {
20777
20871
  if (!input || typeof input !== "object" || key in input)
20778
20872
  return input;
@@ -20800,6 +20894,8 @@ function normalizeShellPath(input, platform = process.platform, home = homedir2(
20800
20894
  }
20801
20895
  function createToolAliasesExtension(opts = {}) {
20802
20896
  const { sandbox } = opts;
20897
+ const powershell = (opts.shell ?? detectShell()) === "powershell";
20898
+ const psOps = powershell ? createLocalPowerShellOperations() : undefined;
20803
20899
  return {
20804
20900
  name: "free-pi-tool-aliases",
20805
20901
  factory: (pi) => {
@@ -20808,6 +20904,10 @@ function createToolAliasesExtension(opts = {}) {
20808
20904
  const read = createReadToolDefinition(cwd);
20809
20905
  pi.registerTool({
20810
20906
  ...bash,
20907
+ ...powershell ? {
20908
+ description: bash.description.replaceAll("bash", "PowerShell"),
20909
+ promptSnippet: bash.promptSnippet?.replaceAll("bash", "PowerShell")
20910
+ } : {},
20811
20911
  prepareArguments: (a) => aliasArgs(bash.prepareArguments ? bash.prepareArguments(a) : a, "command", ["cmd"]),
20812
20912
  execute: async (id, params, signal, onUpdate, ctx) => {
20813
20913
  const s = pi.getSettings();
@@ -20816,7 +20916,8 @@ function createToolAliasesExtension(opts = {}) {
20816
20916
  if (taken.mode === "none")
20817
20917
  throw new Error("Hive: no approval was recorded for this shell command, so it did not run.");
20818
20918
  if (taken.mode === "unsandboxed") {
20819
- return createBashToolDefinition(cwd, { commandPrefix: s.shellCommandPrefix, shellPath }).execute(id, params, signal, onUpdate, ctx);
20919
+ const operations = powershell && !shellPath ? psOps : undefined;
20920
+ return createBashToolDefinition(cwd, { commandPrefix: s.shellCommandPrefix, shellPath, operations }).execute(id, params, signal, onUpdate, ctx);
20820
20921
  }
20821
20922
  try {
20822
20923
  return await createBashToolDefinition(cwd, { commandPrefix: s.shellCommandPrefix, shellPath: taken.shell, spawnHook: taken.hook }).execute(id, params, signal, onUpdate, ctx);
@@ -20938,7 +21039,18 @@ var AGENT_RULES = `## Working rules
20938
21039
  or full error output, or try a different approach. Do not re-read a file you already read
20939
21040
  unless it has changed.
20940
21041
  - Before saying a task is done, check the result the user will see: run the program, open
20941
- the output, or run the specific test. Say plainly what you checked and what you did not.`;
21042
+ the output, or run the specific test. Say plainly what you checked and what you did not.
21043
+ - End every turn with a short visible status: done, blocked and why, or the exact step that
21044
+ failed. Never an empty reply.
21045
+ - For questions about free-pi itself (model, credits, limits, commands, restart), call the
21046
+ ${DOCS_TOOL_NAME} tool instead of searching files. Credits are free-pi's usage unit, not model tokens.
21047
+ To restart, run npx free-pi-cli.
21048
+ - Write files longer than about 300 lines in parts, not one huge write.
21049
+ - Never print whole env, config or credential files. Search for the specific name and never
21050
+ echo secret values.
21051
+ - Ask before irreversible actions outside the project: deleting other tools' config or
21052
+ credentials, system or boot settings, bulk permission changes, remote root sessions.
21053
+ - After finishing a task, suggest one concrete next step.`;
20942
21054
 
20943
21055
  // src/tool-guard.ts
20944
21056
  async function reportToolGuardTrigger(opts, toolName) {
@@ -21158,6 +21270,18 @@ function mergeAttribution(a, b) {
21158
21270
 
21159
21271
  // src/changelog.ts
21160
21272
  var CHANGELOG_HIGHLIGHTS = [
21273
+ {
21274
+ version: "0.6.2",
21275
+ highlights: [
21276
+ "Clearer status at the end of each turn, and a suggested next step.",
21277
+ "Questions about free-pi are answered from the built-in docs.",
21278
+ "Large files are written in parts, and credential files are never printed.",
21279
+ "The agent asks before risky actions.",
21280
+ "Tool calls cut off by the output limit are retried in parts.",
21281
+ "/login reports failures clearly, and opens the browser on Termux.",
21282
+ "Windows: the bash tool falls back to PowerShell when bash is missing."
21283
+ ]
21284
+ },
21161
21285
  {
21162
21286
  version: "0.6.1",
21163
21287
  highlights: [
@@ -21468,7 +21592,8 @@ async function startGuestSession(baseUrl, consentVersion, attribution = {}) {
21468
21592
  token: parsed.token,
21469
21593
  allowance_usd: parsed.allowance_usd,
21470
21594
  remaining_usd: parsed.remaining_usd,
21471
- guestHandoffVariant: parsed.experiments?.guest_handoff
21595
+ guestHandoffVariant: parsed.experiments?.guest_handoff,
21596
+ firstTaskVariant: parsed.experiments?.first_task
21472
21597
  };
21473
21598
  }
21474
21599
  async function reportFunnelEvent(baseUrl, event, opts = {}) {
@@ -21825,6 +21950,7 @@ async function clearPendingLogin(credentialsPath) {
21825
21950
  var LOGIN_REWARD_LINE = "Sign in with GitHub to get 15,000 free credits a day";
21826
21951
  var LOGIN_WAITING_LINE = "Waiting for GitHub…";
21827
21952
  var LOGIN_TIMEOUT_LINE = "GitHub sign-in timed out. Run /login to try again.";
21953
+ var BROWSER_FAILED_LINE = "Couldn't open a browser here. Open the URL above yourself and enter the code.";
21828
21954
  function formatDeviceCodePrompt(device, variant) {
21829
21955
  const value = isHandoffTreatment(variant) ? LOGIN_VALUE_LINE : LOGIN_REWARD_LINE;
21830
21956
  return [
@@ -21866,8 +21992,8 @@ async function runDeviceLogin(deps, attribution = {}) {
21866
21992
  jwt: deps.jwt,
21867
21993
  fetchImpl: deps.fetchImpl
21868
21994
  });
21869
- } else if (!copied) {
21870
- deps.log(HEADLESS_FALLBACK_LINE);
21995
+ } else {
21996
+ deps.log(copied ? BROWSER_FAILED_LINE : `${BROWSER_FAILED_LINE} ${HEADLESS_FALLBACK_LINE}`);
21871
21997
  }
21872
21998
  await submitConsent(deps.baseUrl, device.session_id, CONSENT_VERSION, attribution);
21873
21999
  const expiresInMs = (device.expires_in ?? 900) * 1000;
@@ -22050,7 +22176,8 @@ async function runLogin(opts, ctx) {
22050
22176
  ctx.ui.notify(LOGIN_TIMEOUT_LINE, "warning");
22051
22177
  return;
22052
22178
  }
22053
- ctx.ui.notify("Couldn't finish GitHub sign-in. Run /login to try again.", "error");
22179
+ const reason = err instanceof Error && err.message ? ` (${err.message})` : "";
22180
+ ctx.ui.notify(`Couldn't finish GitHub sign-in${reason}. Run /login to try again.`, "error");
22054
22181
  }
22055
22182
  }
22056
22183
  function installLoginHandler(mode, opts) {
@@ -24251,7 +24378,8 @@ function buildRuntimeOptions(opts, sessionId) {
24251
24378
  });
24252
24379
  const todoToolExtension = createTodoToolExtension();
24253
24380
  const shellSandbox = opts.shellSandbox ?? createShellSandbox();
24254
- const toolAliasesExtension = createToolAliasesExtension({ sandbox: shellSandbox });
24381
+ const shell = opts.shell ?? detectShell();
24382
+ const toolAliasesExtension = createToolAliasesExtension({ sandbox: shellSandbox, shell });
24255
24383
  const mcpExtension = { name: "pi-mcp", factory: createMcpExtension() };
24256
24384
  const codemodeExtension = { name: "pi-codemode", factory: createCodemodeExtension({ models: false }) };
24257
24385
  const toolSearchExtension = { name: "pi-tool-search", factory: createToolSearchExtension() };
@@ -24271,6 +24399,7 @@ function buildRuntimeOptions(opts, sessionId) {
24271
24399
  webToolsExtension,
24272
24400
  imageToolExtension,
24273
24401
  errorNoticeExtension,
24402
+ createOutputLimitExtension(),
24274
24403
  toolGuardExtension,
24275
24404
  commandsExtension,
24276
24405
  headerExtension,
@@ -24289,7 +24418,7 @@ function buildRuntimeOptions(opts, sessionId) {
24289
24418
  resourceLoaderOptions: {
24290
24419
  extensionFactories,
24291
24420
  additionalSkillPaths: [SKILLS_DIR],
24292
- appendSystemPromptOverride: (base) => [...base, AGENT_RULES]
24421
+ appendSystemPromptOverride: (base) => [...base, AGENT_RULES, ...shellPromptLine(shell) ? [shellPromptLine(shell)] : []]
24293
24422
  },
24294
24423
  extensionNames: extensionFactories.map((e) => e.name),
24295
24424
  tools: SDK_TOOLS
@@ -24585,6 +24714,9 @@ async function promptResumeInteractive(session) {
24585
24714
  }
24586
24715
 
24587
24716
  // src/onboarding.ts
24717
+ import { existsSync as existsSync5, readdirSync as readdirSync3, realpathSync as realpathSync3 } from "node:fs";
24718
+ import { homedir as homedir6 } from "node:os";
24719
+ import { join as join11 } from "node:path";
24588
24720
  var INTRO_ENV_SKIP = "FREEPI_NO_INTRO";
24589
24721
  var INTRO_TEXT = `
24590
24722
  Welcome to free-pi — a free coding agent in your terminal.
@@ -24609,14 +24741,45 @@ function showIntro(log, env = process.env) {
24609
24741
  ${INTRO_TEXT}
24610
24742
  `);
24611
24743
  }
24744
+ var FIRST_TASK_EXPERIMENT = "retain.first_task";
24745
+ var PROJECT_MARKERS = [".git", "package.json", "pyproject.toml", "Cargo.toml", "go.mod", "pom.xml", "Gemfile", "composer.json"];
24746
+ function looksLikeProject(cwd, home = homedir6()) {
24747
+ try {
24748
+ if (realpathSync3(cwd) === realpathSync3(home))
24749
+ return false;
24750
+ if (PROJECT_MARKERS.some((m) => existsSync5(join11(cwd, m))))
24751
+ return true;
24752
+ return readdirSync3(cwd).some((f) => /\.(sln|csproj)$/.test(f));
24753
+ } catch {
24754
+ return false;
24755
+ }
24756
+ }
24757
+ var FIRST_TASK_PROJECT_TEXT = `
24758
+ Try one of these on this project — just type it:
24759
+ • Explain how this project is organized
24760
+ • Find and fix one bug
24761
+ • Add a test for the most important function
24762
+ `.trim();
24763
+ var FIRST_TASK_EMPTY_TEXT = `
24764
+ This folder doesn't look like a project. free-pi works best on code you
24765
+ already have: quit, "cd" into a project, and run "npx free-pi-cli" there.
24766
+ Or just type: Create a small runnable hello-world app here and run it
24767
+ `.trim();
24768
+ function showFirstTaskHint(log, cwd = process.cwd(), home = homedir6()) {
24769
+ const text = looksLikeProject(cwd, home) ? FIRST_TASK_PROJECT_TEXT : FIRST_TASK_EMPTY_TEXT;
24770
+ log(`
24771
+ ${text}
24772
+ `);
24773
+ return text;
24774
+ }
24612
24775
 
24613
24776
  // src/whats-new.ts
24614
- import { existsSync as existsSync5, mkdirSync as mkdirSync2, readFileSync as readFileSync4, writeFileSync as writeFileSync2 } from "node:fs";
24777
+ import { existsSync as existsSync6, mkdirSync as mkdirSync2, readFileSync as readFileSync4, writeFileSync as writeFileSync2 } from "node:fs";
24615
24778
  import { dirname as dirname9 } from "node:path";
24616
24779
  function maybeWhatsNew(cliVersion, agentDir) {
24617
24780
  try {
24618
24781
  const path = getLastSeenVersionPath(agentDir);
24619
- const lastSeen = existsSync5(path) ? readFileSync4(path, "utf-8").trim() : undefined;
24782
+ const lastSeen = existsSync6(path) ? readFileSync4(path, "utf-8").trim() : undefined;
24620
24783
  writeLastSeen(path, cliVersion);
24621
24784
  if (lastSeen === undefined)
24622
24785
  return;
@@ -24678,18 +24841,22 @@ function validateSessionId(value) {
24678
24841
  return `Invalid --session value: ${err instanceof Error ? err.message : String(err)}`;
24679
24842
  }
24680
24843
  }
24681
- function reportResumeExposure(deps, jwt2, event) {
24682
- if (deps.reportResumeExposure) {
24844
+ function reportResumeExposure(deps, jwt2, event, experiment = RESUME_ON_LAUNCH_EXPERIMENT) {
24845
+ if (experiment === RESUME_ON_LAUNCH_EXPERIMENT && deps.reportResumeExposure) {
24683
24846
  deps.reportResumeExposure(event);
24684
24847
  return;
24685
24848
  }
24849
+ if (experiment === FIRST_TASK_EXPERIMENT && deps.reportFirstTaskExposure) {
24850
+ deps.reportFirstTaskExposure();
24851
+ return;
24852
+ }
24686
24853
  fetch(new URL("/telemetry/experiment", deps.baseUrl), {
24687
24854
  method: "POST",
24688
24855
  headers: {
24689
24856
  authorization: `Bearer ${jwt2}`,
24690
24857
  "content-type": "application/json"
24691
24858
  },
24692
- body: JSON.stringify({ experiment: RESUME_ON_LAUNCH_EXPERIMENT, event }),
24859
+ body: JSON.stringify({ experiment, event }),
24693
24860
  signal: AbortSignal.timeout(3000)
24694
24861
  }).catch(() => {});
24695
24862
  }
@@ -24716,6 +24883,7 @@ ${FINISH_SIGNIN_LINE}
24716
24883
  `);
24717
24884
  let jwt2 = await loadJwt(deps.credentialsPath);
24718
24885
  let guestHandoffVariant;
24886
+ let showFirstTask = false;
24719
24887
  let consentCurrent = false;
24720
24888
  if (jwt2) {
24721
24889
  try {
@@ -24764,6 +24932,7 @@ You're on a short guest trial — ads pay for it. ${LOGIN_VALUE_LINE}
24764
24932
  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.
24765
24933
  `);
24766
24934
  guestHandoffVariant = guest.guestHandoffVariant;
24935
+ showFirstTask = guest.firstTaskVariant === "treatment" && !resumeFlags.hasPrompt;
24767
24936
  } catch (err) {
24768
24937
  if (!(err instanceof GuestUnavailableError))
24769
24938
  throw err;
@@ -24803,6 +24972,10 @@ Guest trial is not available right now.
24803
24972
  if (me)
24804
24973
  updateAllowanceCache(me);
24805
24974
  guestHandoffVariant = me?.experiments?.guest_handoff ?? guestHandoffVariant;
24975
+ if (showFirstTask) {
24976
+ showFirstTaskHint(deps.log);
24977
+ reportResumeExposure(deps, jwt2, "offer_shown", FIRST_TASK_EXPERIMENT);
24978
+ }
24806
24979
  const startRefill = await fetchRefillStats(deps.baseUrl, jwt2).catch(() => {
24807
24980
  return;
24808
24981
  });
@@ -24945,7 +25118,7 @@ async function checkClientVersion(baseUrl, cliVersion, fetchImpl = fetch, jwt2)
24945
25118
  // src/main.ts
24946
25119
  function resolveCliRealpath() {
24947
25120
  try {
24948
- return realpathSync3(process.argv[1] ?? "");
25121
+ return realpathSync4(process.argv[1] ?? "");
24949
25122
  } catch {
24950
25123
  return "";
24951
25124
  }
@@ -25000,7 +25173,7 @@ ${HELP_TEXT}`);
25000
25173
  launchPi,
25001
25174
  log: (message) => console.log(message),
25002
25175
  checkClientVersion: (baseUrl, cliVersion, jwt2) => checkClientVersion(baseUrl, cliVersion, fetch, jwt2),
25003
- startGravityPixel: (jwt2) => startGravityPixel(jwt2, join11(dirname10(agentDir), "gravity")),
25176
+ startGravityPixel: (jwt2) => startGravityPixel(jwt2, join12(dirname10(agentDir), "gravity")),
25004
25177
  maybeSelfUpdate: (action2, latest) => maybeSelfUpdate(action2, latest, {
25005
25178
  env: process.env,
25006
25179
  autoUpdateEnabled: () => readAutoUpdate(getConfigPath()),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "free-pi-cli",
3
- "version": "0.6.1",
3
+ "version": "0.6.2",
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",