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 +1 -1
- package/dist/{main-zv5s2zn8.js → main-cwwgr8bh.js} +194 -21
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -5,8 +5,8 @@ import {
|
|
|
5
5
|
} from "./index-n5qs9pq1.js";
|
|
6
6
|
|
|
7
7
|
// src/main.ts
|
|
8
|
-
import { realpathSync as
|
|
9
|
-
import { dirname as dirname10, join as
|
|
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.
|
|
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:
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
|
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 =
|
|
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
|
|
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
|
|
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,
|
|
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()),
|