free-pi-cli 0.6.0 → 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-98djq99p.js → main-cwwgr8bh.js} +264 -45
- 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
|
}
|
|
@@ -20433,13 +20489,19 @@ at first run, before the GitHub sign-in.
|
|
|
20433
20489
|
Each user gets a free allowance every day. The allowance can change with
|
|
20434
20490
|
supply and demand. \`/usage\` shows how much of today's allowance is spent, as
|
|
20435
20491
|
a percentage, with token counts.
|
|
20436
|
-
|
|
20492
|
+
You can run free-pi in more than one terminal. If a previous session is stuck,
|
|
20437
20493
|
\`/close-other-session\` frees it.
|
|
20438
20494
|
|
|
20439
20495
|
## Credits
|
|
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) {
|
|
@@ -21054,20 +21166,65 @@ function registerBugAutocomplete(pi) {
|
|
|
21054
21166
|
}
|
|
21055
21167
|
|
|
21056
21168
|
// src/footer-align.ts
|
|
21057
|
-
|
|
21058
|
-
var
|
|
21169
|
+
import { truncateToWidth as truncateToWidth4, visibleWidth as visibleWidth2 } from "@earendil-works/pi-tui";
|
|
21170
|
+
var MIN_GAP = 2;
|
|
21171
|
+
var MIN_CWD = 8;
|
|
21172
|
+
var LEADING_SGR = /^(?:\x1b\[[0-9;]*m)+/;
|
|
21173
|
+
function mergeStatusIntoCwdLine(lines, width) {
|
|
21174
|
+
if (lines.length < 3)
|
|
21175
|
+
return lines;
|
|
21176
|
+
const [cwd, stats] = lines;
|
|
21177
|
+
const status = lines[lines.length - 1];
|
|
21178
|
+
const statusW = visibleWidth2(status);
|
|
21179
|
+
let left = cwd;
|
|
21180
|
+
if (visibleWidth2(left) + MIN_GAP + statusW > width) {
|
|
21181
|
+
const room = width - MIN_GAP - statusW;
|
|
21182
|
+
if (room < MIN_CWD)
|
|
21183
|
+
return lines;
|
|
21184
|
+
const style = LEADING_SGR.exec(cwd)?.[0] ?? "";
|
|
21185
|
+
left = truncateToWidth4(cwd, room, style ? `${style}...\x1B[0m` : "...");
|
|
21186
|
+
}
|
|
21187
|
+
const gap = " ".repeat(Math.max(MIN_GAP, width - visibleWidth2(left) - statusW));
|
|
21188
|
+
return [left + gap + status, stats, ...lines.slice(2, -1)];
|
|
21189
|
+
}
|
|
21059
21190
|
function installFooterRightAlign(mode) {
|
|
21060
21191
|
const footer = mode.footer;
|
|
21061
21192
|
const render = footer.render.bind(footer);
|
|
21062
|
-
footer.render = (w) =>
|
|
21063
|
-
|
|
21064
|
-
|
|
21065
|
-
|
|
21066
|
-
|
|
21067
|
-
|
|
21068
|
-
|
|
21069
|
-
|
|
21070
|
-
|
|
21193
|
+
footer.render = (w) => mergeStatusIntoCwdLine(render(w), w);
|
|
21194
|
+
}
|
|
21195
|
+
|
|
21196
|
+
// src/guest-handoff.ts
|
|
21197
|
+
var LOGIN_VALUE_LINE = "sign in free with GitHub: full daily allowance, FREE abliterated DeepSeek, history saved";
|
|
21198
|
+
var GUEST_NEAR_WALL_LINE = `A few guest requests left. ${LOGIN_VALUE_LINE}`;
|
|
21199
|
+
var SIGNED_IN_CONTINUE_LINE = "Signed in. Your conversation continues — full daily allowance is unlocked.";
|
|
21200
|
+
var HEADLESS_FALLBACK_LINE = "No browser or clipboard here — open the URL and enter the code above.";
|
|
21201
|
+
function isHandoffTreatment(variant) {
|
|
21202
|
+
return variant === "treatment";
|
|
21203
|
+
}
|
|
21204
|
+
var GUEST_NOTES = [
|
|
21205
|
+
"Signed-in users get the Defense models in /model, for security work on code and systems you own. Sign in free with /login.",
|
|
21206
|
+
"With the Defense models you can audit your own repo for vulnerabilities and get fixes, without the usual refusals. /login to unlock.",
|
|
21207
|
+
"Write detection rules and review your server configs with the Defense models. Sign in free with /login.",
|
|
21208
|
+
"More on security work with free-pi: https://freepi.ai/cybersecurity. /login to unlock the Defense models."
|
|
21209
|
+
];
|
|
21210
|
+
var GUEST_NOTE_EVERY_TURNS = 3;
|
|
21211
|
+
function createGuestNotesExtension(opts) {
|
|
21212
|
+
let turns = 0;
|
|
21213
|
+
let shown = 0;
|
|
21214
|
+
return {
|
|
21215
|
+
name: "free-pi-guest-notes",
|
|
21216
|
+
factory(pi) {
|
|
21217
|
+
pi.on("agent_end", (event, ctx) => {
|
|
21218
|
+
if (!opts.isGuest() || shown >= GUEST_NOTES.length)
|
|
21219
|
+
return;
|
|
21220
|
+
const last = [...event.messages].reverse().find((m) => m.role === "assistant");
|
|
21221
|
+
if (last?.stopReason !== "stop")
|
|
21222
|
+
return;
|
|
21223
|
+
if (++turns % GUEST_NOTE_EVERY_TURNS !== 0)
|
|
21224
|
+
return;
|
|
21225
|
+
ctx.ui.notify(GUEST_NOTES[shown++], "info");
|
|
21226
|
+
});
|
|
21227
|
+
}
|
|
21071
21228
|
};
|
|
21072
21229
|
}
|
|
21073
21230
|
|
|
@@ -21113,6 +21270,27 @@ function mergeAttribution(a, b) {
|
|
|
21113
21270
|
|
|
21114
21271
|
// src/changelog.ts
|
|
21115
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
|
+
},
|
|
21285
|
+
{
|
|
21286
|
+
version: "0.6.1",
|
|
21287
|
+
highlights: [
|
|
21288
|
+
"/model shows the Defense models by their real names.",
|
|
21289
|
+
"Five more Defense models: Abliterated Large V2, Qwen 3.6 Plus Uncensored, Gemma 4 Uncensored, GLM 4.7 Flash Heretic and Uncensored 1.2.",
|
|
21290
|
+
"The footer is two lines: credits left now sit next to the folder name.",
|
|
21291
|
+
"The welcome text no longer says you can only run one session."
|
|
21292
|
+
]
|
|
21293
|
+
},
|
|
21116
21294
|
{
|
|
21117
21295
|
version: "0.6.0",
|
|
21118
21296
|
highlights: [
|
|
@@ -21414,7 +21592,8 @@ async function startGuestSession(baseUrl, consentVersion, attribution = {}) {
|
|
|
21414
21592
|
token: parsed.token,
|
|
21415
21593
|
allowance_usd: parsed.allowance_usd,
|
|
21416
21594
|
remaining_usd: parsed.remaining_usd,
|
|
21417
|
-
guestHandoffVariant: parsed.experiments?.guest_handoff
|
|
21595
|
+
guestHandoffVariant: parsed.experiments?.guest_handoff,
|
|
21596
|
+
firstTaskVariant: parsed.experiments?.first_task
|
|
21418
21597
|
};
|
|
21419
21598
|
}
|
|
21420
21599
|
async function reportFunnelEvent(baseUrl, event, opts = {}) {
|
|
@@ -21522,15 +21701,6 @@ async function submitUserConsent(baseUrl, jwt2, consentVersion) {
|
|
|
21522
21701
|
throw new Error(`consent submission failed: HTTP ${res.status}`);
|
|
21523
21702
|
}
|
|
21524
21703
|
|
|
21525
|
-
// src/guest-handoff.ts
|
|
21526
|
-
var LOGIN_VALUE_LINE = "sign in free with GitHub: full daily allowance, FREE abliterated DeepSeek, history saved";
|
|
21527
|
-
var GUEST_NEAR_WALL_LINE = `A few guest requests left. ${LOGIN_VALUE_LINE}`;
|
|
21528
|
-
var SIGNED_IN_CONTINUE_LINE = "Signed in. Your conversation continues — full daily allowance is unlocked.";
|
|
21529
|
-
var HEADLESS_FALLBACK_LINE = "No browser or clipboard here — open the URL and enter the code above.";
|
|
21530
|
-
function isHandoffTreatment(variant) {
|
|
21531
|
-
return variant === "treatment";
|
|
21532
|
-
}
|
|
21533
|
-
|
|
21534
21704
|
// src/clipboard.ts
|
|
21535
21705
|
import { spawn as spawn2 } from "node:child_process";
|
|
21536
21706
|
import { existsSync as existsSync2 } from "node:fs";
|
|
@@ -21780,6 +21950,7 @@ async function clearPendingLogin(credentialsPath) {
|
|
|
21780
21950
|
var LOGIN_REWARD_LINE = "Sign in with GitHub to get 15,000 free credits a day";
|
|
21781
21951
|
var LOGIN_WAITING_LINE = "Waiting for GitHub…";
|
|
21782
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.";
|
|
21783
21954
|
function formatDeviceCodePrompt(device, variant) {
|
|
21784
21955
|
const value = isHandoffTreatment(variant) ? LOGIN_VALUE_LINE : LOGIN_REWARD_LINE;
|
|
21785
21956
|
return [
|
|
@@ -21821,8 +21992,8 @@ async function runDeviceLogin(deps, attribution = {}) {
|
|
|
21821
21992
|
jwt: deps.jwt,
|
|
21822
21993
|
fetchImpl: deps.fetchImpl
|
|
21823
21994
|
});
|
|
21824
|
-
} else
|
|
21825
|
-
deps.log(HEADLESS_FALLBACK_LINE);
|
|
21995
|
+
} else {
|
|
21996
|
+
deps.log(copied ? BROWSER_FAILED_LINE : `${BROWSER_FAILED_LINE} ${HEADLESS_FALLBACK_LINE}`);
|
|
21826
21997
|
}
|
|
21827
21998
|
await submitConsent(deps.baseUrl, device.session_id, CONSENT_VERSION, attribution);
|
|
21828
21999
|
const expiresInMs = (device.expires_in ?? 900) * 1000;
|
|
@@ -22005,7 +22176,8 @@ async function runLogin(opts, ctx) {
|
|
|
22005
22176
|
ctx.ui.notify(LOGIN_TIMEOUT_LINE, "warning");
|
|
22006
22177
|
return;
|
|
22007
22178
|
}
|
|
22008
|
-
|
|
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");
|
|
22009
22181
|
}
|
|
22010
22182
|
}
|
|
22011
22183
|
function installLoginHandler(mode, opts) {
|
|
@@ -24206,7 +24378,8 @@ function buildRuntimeOptions(opts, sessionId) {
|
|
|
24206
24378
|
});
|
|
24207
24379
|
const todoToolExtension = createTodoToolExtension();
|
|
24208
24380
|
const shellSandbox = opts.shellSandbox ?? createShellSandbox();
|
|
24209
|
-
const
|
|
24381
|
+
const shell = opts.shell ?? detectShell();
|
|
24382
|
+
const toolAliasesExtension = createToolAliasesExtension({ sandbox: shellSandbox, shell });
|
|
24210
24383
|
const mcpExtension = { name: "pi-mcp", factory: createMcpExtension() };
|
|
24211
24384
|
const codemodeExtension = { name: "pi-codemode", factory: createCodemodeExtension({ models: false }) };
|
|
24212
24385
|
const toolSearchExtension = { name: "pi-tool-search", factory: createToolSearchExtension() };
|
|
@@ -24226,6 +24399,7 @@ function buildRuntimeOptions(opts, sessionId) {
|
|
|
24226
24399
|
webToolsExtension,
|
|
24227
24400
|
imageToolExtension,
|
|
24228
24401
|
errorNoticeExtension,
|
|
24402
|
+
createOutputLimitExtension(),
|
|
24229
24403
|
toolGuardExtension,
|
|
24230
24404
|
commandsExtension,
|
|
24231
24405
|
headerExtension,
|
|
@@ -24235,7 +24409,8 @@ function buildRuntimeOptions(opts, sessionId) {
|
|
|
24235
24409
|
codemodeExtension,
|
|
24236
24410
|
toolSearchExtension,
|
|
24237
24411
|
hiveExtension,
|
|
24238
|
-
...guestLoginExtension ? [guestLoginExtension] : []
|
|
24412
|
+
...guestLoginExtension ? [guestLoginExtension] : [],
|
|
24413
|
+
...opts.guest ? [createGuestNotesExtension({ isGuest: () => auth.isGuest() })] : []
|
|
24239
24414
|
];
|
|
24240
24415
|
const settingsManager = buildSettingsManager(opts, models, opts.projectTrusted === true);
|
|
24241
24416
|
return {
|
|
@@ -24243,7 +24418,7 @@ function buildRuntimeOptions(opts, sessionId) {
|
|
|
24243
24418
|
resourceLoaderOptions: {
|
|
24244
24419
|
extensionFactories,
|
|
24245
24420
|
additionalSkillPaths: [SKILLS_DIR],
|
|
24246
|
-
appendSystemPromptOverride: (base) => [...base, AGENT_RULES]
|
|
24421
|
+
appendSystemPromptOverride: (base) => [...base, AGENT_RULES, ...shellPromptLine(shell) ? [shellPromptLine(shell)] : []]
|
|
24247
24422
|
},
|
|
24248
24423
|
extensionNames: extensionFactories.map((e) => e.name),
|
|
24249
24424
|
tools: SDK_TOOLS
|
|
@@ -24539,6 +24714,9 @@ async function promptResumeInteractive(session) {
|
|
|
24539
24714
|
}
|
|
24540
24715
|
|
|
24541
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";
|
|
24542
24720
|
var INTRO_ENV_SKIP = "FREEPI_NO_INTRO";
|
|
24543
24721
|
var INTRO_TEXT = `
|
|
24544
24722
|
Welcome to free-pi — a free coding agent in your terminal.
|
|
@@ -24546,8 +24724,8 @@ Welcome to free-pi — a free coding agent in your terminal.
|
|
|
24546
24724
|
• It's the pi coding agent, free to use. Ads in the terminal pay for the
|
|
24547
24725
|
inference — ad content never reaches the model.
|
|
24548
24726
|
• Tell it what to build, fix, or explain, like any coding assistant.
|
|
24549
|
-
•
|
|
24550
|
-
with "npx free-pi-cli" — or resume where you left off with
|
|
24727
|
+
• You can run free-pi in more than one terminal. If you close the terminal,
|
|
24728
|
+
relaunch with "npx free-pi-cli" — or resume where you left off with
|
|
24551
24729
|
"npx free-pi-cli --session <id>" (the id prints when you exit).
|
|
24552
24730
|
|
|
24553
24731
|
Set ${INTRO_ENV_SKIP}=1 to skip this next time.
|
|
@@ -24563,14 +24741,45 @@ function showIntro(log, env = process.env) {
|
|
|
24563
24741
|
${INTRO_TEXT}
|
|
24564
24742
|
`);
|
|
24565
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
|
+
}
|
|
24566
24775
|
|
|
24567
24776
|
// src/whats-new.ts
|
|
24568
|
-
import { existsSync as
|
|
24777
|
+
import { existsSync as existsSync6, mkdirSync as mkdirSync2, readFileSync as readFileSync4, writeFileSync as writeFileSync2 } from "node:fs";
|
|
24569
24778
|
import { dirname as dirname9 } from "node:path";
|
|
24570
24779
|
function maybeWhatsNew(cliVersion, agentDir) {
|
|
24571
24780
|
try {
|
|
24572
24781
|
const path = getLastSeenVersionPath(agentDir);
|
|
24573
|
-
const lastSeen =
|
|
24782
|
+
const lastSeen = existsSync6(path) ? readFileSync4(path, "utf-8").trim() : undefined;
|
|
24574
24783
|
writeLastSeen(path, cliVersion);
|
|
24575
24784
|
if (lastSeen === undefined)
|
|
24576
24785
|
return;
|
|
@@ -24632,18 +24841,22 @@ function validateSessionId(value) {
|
|
|
24632
24841
|
return `Invalid --session value: ${err instanceof Error ? err.message : String(err)}`;
|
|
24633
24842
|
}
|
|
24634
24843
|
}
|
|
24635
|
-
function reportResumeExposure(deps, jwt2, event) {
|
|
24636
|
-
if (deps.reportResumeExposure) {
|
|
24844
|
+
function reportResumeExposure(deps, jwt2, event, experiment = RESUME_ON_LAUNCH_EXPERIMENT) {
|
|
24845
|
+
if (experiment === RESUME_ON_LAUNCH_EXPERIMENT && deps.reportResumeExposure) {
|
|
24637
24846
|
deps.reportResumeExposure(event);
|
|
24638
24847
|
return;
|
|
24639
24848
|
}
|
|
24849
|
+
if (experiment === FIRST_TASK_EXPERIMENT && deps.reportFirstTaskExposure) {
|
|
24850
|
+
deps.reportFirstTaskExposure();
|
|
24851
|
+
return;
|
|
24852
|
+
}
|
|
24640
24853
|
fetch(new URL("/telemetry/experiment", deps.baseUrl), {
|
|
24641
24854
|
method: "POST",
|
|
24642
24855
|
headers: {
|
|
24643
24856
|
authorization: `Bearer ${jwt2}`,
|
|
24644
24857
|
"content-type": "application/json"
|
|
24645
24858
|
},
|
|
24646
|
-
body: JSON.stringify({ experiment
|
|
24859
|
+
body: JSON.stringify({ experiment, event }),
|
|
24647
24860
|
signal: AbortSignal.timeout(3000)
|
|
24648
24861
|
}).catch(() => {});
|
|
24649
24862
|
}
|
|
@@ -24670,6 +24883,7 @@ ${FINISH_SIGNIN_LINE}
|
|
|
24670
24883
|
`);
|
|
24671
24884
|
let jwt2 = await loadJwt(deps.credentialsPath);
|
|
24672
24885
|
let guestHandoffVariant;
|
|
24886
|
+
let showFirstTask = false;
|
|
24673
24887
|
let consentCurrent = false;
|
|
24674
24888
|
if (jwt2) {
|
|
24675
24889
|
try {
|
|
@@ -24718,6 +24932,7 @@ You're on a short guest trial — ads pay for it. ${LOGIN_VALUE_LINE}
|
|
|
24718
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.
|
|
24719
24933
|
`);
|
|
24720
24934
|
guestHandoffVariant = guest.guestHandoffVariant;
|
|
24935
|
+
showFirstTask = guest.firstTaskVariant === "treatment" && !resumeFlags.hasPrompt;
|
|
24721
24936
|
} catch (err) {
|
|
24722
24937
|
if (!(err instanceof GuestUnavailableError))
|
|
24723
24938
|
throw err;
|
|
@@ -24757,6 +24972,10 @@ Guest trial is not available right now.
|
|
|
24757
24972
|
if (me)
|
|
24758
24973
|
updateAllowanceCache(me);
|
|
24759
24974
|
guestHandoffVariant = me?.experiments?.guest_handoff ?? guestHandoffVariant;
|
|
24975
|
+
if (showFirstTask) {
|
|
24976
|
+
showFirstTaskHint(deps.log);
|
|
24977
|
+
reportResumeExposure(deps, jwt2, "offer_shown", FIRST_TASK_EXPERIMENT);
|
|
24978
|
+
}
|
|
24760
24979
|
const startRefill = await fetchRefillStats(deps.baseUrl, jwt2).catch(() => {
|
|
24761
24980
|
return;
|
|
24762
24981
|
});
|
|
@@ -24899,7 +25118,7 @@ async function checkClientVersion(baseUrl, cliVersion, fetchImpl = fetch, jwt2)
|
|
|
24899
25118
|
// src/main.ts
|
|
24900
25119
|
function resolveCliRealpath() {
|
|
24901
25120
|
try {
|
|
24902
|
-
return
|
|
25121
|
+
return realpathSync4(process.argv[1] ?? "");
|
|
24903
25122
|
} catch {
|
|
24904
25123
|
return "";
|
|
24905
25124
|
}
|
|
@@ -24954,7 +25173,7 @@ ${HELP_TEXT}`);
|
|
|
24954
25173
|
launchPi,
|
|
24955
25174
|
log: (message) => console.log(message),
|
|
24956
25175
|
checkClientVersion: (baseUrl, cliVersion, jwt2) => checkClientVersion(baseUrl, cliVersion, fetch, jwt2),
|
|
24957
|
-
startGravityPixel: (jwt2) => startGravityPixel(jwt2,
|
|
25176
|
+
startGravityPixel: (jwt2) => startGravityPixel(jwt2, join12(dirname10(agentDir), "gravity")),
|
|
24958
25177
|
maybeSelfUpdate: (action2, latest) => maybeSelfUpdate(action2, latest, {
|
|
24959
25178
|
env: process.env,
|
|
24960
25179
|
autoUpdateEnabled: () => readAutoUpdate(getConfigPath()),
|