free-pi-cli 0.6.4 → 0.6.7
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/README.md +4 -4
- package/dist/index.js +2 -2
- package/dist/{main-2mvzsazb.js → main-tm302552.js} +293 -91
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -5,7 +5,7 @@ for the user because ads pay for inference. All model calls route through the
|
|
|
5
5
|
free-pi proxy — you never handle a model API key.
|
|
6
6
|
|
|
7
7
|
```
|
|
8
|
-
npx free-pi-cli
|
|
8
|
+
npx free-pi-cli@latest
|
|
9
9
|
```
|
|
10
10
|
|
|
11
11
|
First run shows a consent screen (free inference, ads pay for it, sessions
|
|
@@ -18,7 +18,7 @@ existing `~/.pi/agent` install.
|
|
|
18
18
|
Closed the terminal? Just run the same command again — you stay signed in:
|
|
19
19
|
|
|
20
20
|
```
|
|
21
|
-
npx free-pi-cli
|
|
21
|
+
npx free-pi-cli@latest
|
|
22
22
|
```
|
|
23
23
|
|
|
24
24
|
Prefer a shorter command? Install it once, globally, then call it by name:
|
|
@@ -29,7 +29,7 @@ free-pi-cli
|
|
|
29
29
|
```
|
|
30
30
|
|
|
31
31
|
Note: `free-pi-cli` is the command — **not** `pi`. If you see the built-in
|
|
32
|
-
agent suggest `pi --session …` on exit, use `npx free-pi-cli --session <id>`
|
|
32
|
+
agent suggest `pi --session …` on exit, use `npx free-pi-cli@latest --session <id>`
|
|
33
33
|
instead. In a folder with a recent session, some users are offered a
|
|
34
34
|
one-keypress continue (default Yes). `--continue` resumes the last session
|
|
35
35
|
here; `--new` starts fresh.
|
|
@@ -48,7 +48,7 @@ here; `--new` starts fresh.
|
|
|
48
48
|
| `src/index.ts` | bin entry — wires real dependencies, calls `run()` |
|
|
49
49
|
| `src/run.ts` | orchestration: consent → login → token check → launch |
|
|
50
50
|
| `src/consent.ts` | consent screen text/parsing |
|
|
51
|
-
| `src/auth-flow.ts` | GitHub device flow
|
|
51
|
+
| `src/auth-flow.ts` | Sign-in clients: browser "Connect this terminal" (`/auth/connect/start`, `/auth/connect/poll`) and the GitHub device flow fallback (`/auth/github/device`, `/auth/consent`, `/auth/token`), `/me` token check |
|
|
52
52
|
| `src/credentials.ts` | JWT file storage (mode 0600) under the free-pi config dir |
|
|
53
53
|
| `src/provider.ts` | pure builder for the `pi.registerProvider` config |
|
|
54
54
|
| `src/pi-launch.ts` | the actual pi SDK integration (registers the provider, launches the TUI) |
|
package/dist/index.js
CHANGED
|
@@ -17,7 +17,7 @@ function zstdSupportError(zlibModule = zlib, nodeVersion = process.versions.node
|
|
|
17
17
|
`The pi agent needs Node with zstd support (Node >= 22.15, or >= 23.8 on the`,
|
|
18
18
|
`Current line) — older Node crashes mid-request inside the agent. Upgrade Node:`,
|
|
19
19
|
``,
|
|
20
|
-
` nvm install 22 # then re-run: npx free-pi-cli`,
|
|
20
|
+
` nvm install 22 # then re-run: npx free-pi-cli@latest`,
|
|
21
21
|
``,
|
|
22
22
|
`(or update your system Node to the latest LTS.)`
|
|
23
23
|
].join(`
|
|
@@ -30,4 +30,4 @@ if (err) {
|
|
|
30
30
|
console.error(err);
|
|
31
31
|
process.exit(1);
|
|
32
32
|
}
|
|
33
|
-
await import("./main-
|
|
33
|
+
await import("./main-tm302552.js");
|
|
@@ -14407,6 +14407,21 @@ var AuthConsentRequestSchema = exports_external.object({
|
|
|
14407
14407
|
signup_src: exports_external.string().optional(),
|
|
14408
14408
|
referral_code: exports_external.string().optional()
|
|
14409
14409
|
});
|
|
14410
|
+
var ConnectStartRequestSchema = exports_external.object({
|
|
14411
|
+
consent_version: exports_external.string().min(1).max(32),
|
|
14412
|
+
signup_src: exports_external.string().optional(),
|
|
14413
|
+
referral_code: exports_external.string().optional(),
|
|
14414
|
+
client: exports_external.object({ os: exports_external.string().max(64).optional(), cli_version: exports_external.string().max(64).optional() }).optional()
|
|
14415
|
+
});
|
|
14416
|
+
var ConnectStartResponseSchema = exports_external.object({
|
|
14417
|
+
session_id: exports_external.string().uuid(),
|
|
14418
|
+
poll_secret: exports_external.string(),
|
|
14419
|
+
token: exports_external.string(),
|
|
14420
|
+
confirm_code: exports_external.string(),
|
|
14421
|
+
connect_url: exports_external.string(),
|
|
14422
|
+
expires_in: exports_external.number(),
|
|
14423
|
+
interval: exports_external.number()
|
|
14424
|
+
});
|
|
14410
14425
|
var GuestStartRequestSchema = exports_external.object({
|
|
14411
14426
|
consent_version: exports_external.string().min(1).max(32),
|
|
14412
14427
|
signup_src: exports_external.string().optional(),
|
|
@@ -14692,7 +14707,12 @@ var ConfigSchema = exports_external.object({
|
|
|
14692
14707
|
referralMaxActivePerReferrer: exports_external.number().int().default(10),
|
|
14693
14708
|
referralGithubMinAgeDays: exports_external.number().int().default(30),
|
|
14694
14709
|
referralCreditGrantCredits: exports_external.number().int().default(0),
|
|
14695
|
-
webClaimCredits: exports_external.number().int().default(0)
|
|
14710
|
+
webClaimCredits: exports_external.number().int().default(0),
|
|
14711
|
+
geoipDbPath: exports_external.string().optional(),
|
|
14712
|
+
sanctionedCountries: exports_external.array(exports_external.string()).default(["IR", "CU", "KP", "SY"]),
|
|
14713
|
+
regionLimitsStart: exports_external.date().optional(),
|
|
14714
|
+
fullAllowanceCountries: exports_external.array(exports_external.string()).default(["US", "CA", "GB", "AU", "NZ", "IE"]),
|
|
14715
|
+
regionLowCapMicros: exports_external.bigint().default(100000n)
|
|
14696
14716
|
});
|
|
14697
14717
|
function envNumber(value) {
|
|
14698
14718
|
if (value === undefined || value === "")
|
|
@@ -14728,6 +14748,15 @@ function envList(value) {
|
|
|
14728
14748
|
const items = value.split(",").map((s) => s.trim()).filter((s) => s.length > 0);
|
|
14729
14749
|
return items.length > 0 ? items : undefined;
|
|
14730
14750
|
}
|
|
14751
|
+
function envCountries(value) {
|
|
14752
|
+
return envList(value)?.map((s) => s.toUpperCase());
|
|
14753
|
+
}
|
|
14754
|
+
function envDate(value) {
|
|
14755
|
+
if (!value?.trim())
|
|
14756
|
+
return;
|
|
14757
|
+
const d = new Date(value.trim());
|
|
14758
|
+
return Number.isNaN(d.getTime()) ? undefined : d;
|
|
14759
|
+
}
|
|
14731
14760
|
function envDecimalString(value) {
|
|
14732
14761
|
if (value === undefined || value.trim() === "")
|
|
14733
14762
|
return;
|
|
@@ -14822,7 +14851,12 @@ function loadConfig(env = process.env) {
|
|
|
14822
14851
|
referralMaxActivePerReferrer: envStrictInt("REFERRAL_MAX_ACTIVE_PER_REFERRER", env.REFERRAL_MAX_ACTIVE_PER_REFERRER),
|
|
14823
14852
|
referralGithubMinAgeDays: envStrictInt("REFERRAL_GITHUB_MIN_AGE_DAYS", env.REFERRAL_GITHUB_MIN_AGE_DAYS),
|
|
14824
14853
|
referralCreditGrantCredits: envStrictInt("REFERRAL_CREDIT_GRANT_CREDITS", env.REFERRAL_CREDIT_GRANT_CREDITS),
|
|
14825
|
-
webClaimCredits: envStrictInt("WEB_CLAIM_CREDITS", env.WEB_CLAIM_CREDITS)
|
|
14854
|
+
webClaimCredits: envStrictInt("WEB_CLAIM_CREDITS", env.WEB_CLAIM_CREDITS),
|
|
14855
|
+
geoipDbPath: env.GEOIP_DB_PATH?.trim() || undefined,
|
|
14856
|
+
sanctionedCountries: envCountries(env.SANCTIONED_COUNTRIES),
|
|
14857
|
+
regionLimitsStart: envDate(env.REGION_LIMITS_START),
|
|
14858
|
+
fullAllowanceCountries: envCountries(env.FULL_ALLOWANCE_COUNTRIES),
|
|
14859
|
+
regionLowCapMicros: envMicros("REGION_LOW_CAP_USD", env.REGION_LOW_CAP_USD)
|
|
14826
14860
|
});
|
|
14827
14861
|
}
|
|
14828
14862
|
// ../shared/src/credits.ts
|
|
@@ -15061,7 +15095,7 @@ var HiveClassifyResponseSchema = exports_external.object({
|
|
|
15061
15095
|
// package.json
|
|
15062
15096
|
var package_default = {
|
|
15063
15097
|
name: "free-pi-cli",
|
|
15064
|
-
version: "0.6.
|
|
15098
|
+
version: "0.6.7",
|
|
15065
15099
|
description: "Free, ad-supported distribution of the pi coding agent. Inference paid for by ads.",
|
|
15066
15100
|
license: "MIT",
|
|
15067
15101
|
type: "module",
|
|
@@ -15104,6 +15138,7 @@ Usage:
|
|
|
15104
15138
|
free-pi-cli --session <id> resume a previous session
|
|
15105
15139
|
free-pi-cli --continue resume the last session in this folder
|
|
15106
15140
|
free-pi-cli --new start a new session
|
|
15141
|
+
free-pi-cli --device-login sign in with a GitHub device code instead of the browser page
|
|
15107
15142
|
free-pi-cli --ref <code> first-run referral code (from freepi.ai/?ref=)
|
|
15108
15143
|
free-pi-cli --src <channel> first-run campaign attribution (from freepi.ai/?src=)
|
|
15109
15144
|
free-pi-cli logout remove the stored sign-in token; the next run signs in again
|
|
@@ -15114,6 +15149,7 @@ Usage:
|
|
|
15114
15149
|
|
|
15115
15150
|
Environment:
|
|
15116
15151
|
FREEPI_BASE_URL override the backend base URL (default https://api.freepi.ai)
|
|
15152
|
+
FREEPI_DEVICE_LOGIN=1 same as --device-login
|
|
15117
15153
|
FREEPI_NO_AUTO_UPDATE disable in-place self-update for this run
|
|
15118
15154
|
FREEPI_NO_GRAVITY_PIXEL=1 do not run Gravity's ad-measurement pixel
|
|
15119
15155
|
FREEPI_REF same as --ref when the flag is omitted
|
|
@@ -15139,7 +15175,7 @@ function parseCliArgs(argv) {
|
|
|
15139
15175
|
let i = 0;
|
|
15140
15176
|
while (i < argv.length) {
|
|
15141
15177
|
const arg = argv[i];
|
|
15142
|
-
if (arg === "--continue" || arg === "--resume" || arg === "--new") {
|
|
15178
|
+
if (arg === "--continue" || arg === "--resume" || arg === "--new" || arg === "--device-login") {
|
|
15143
15179
|
i += 1;
|
|
15144
15180
|
continue;
|
|
15145
15181
|
}
|
|
@@ -15641,12 +15677,16 @@ function mapErrorCode(code) {
|
|
|
15641
15677
|
case "account_review":
|
|
15642
15678
|
return "this account is under review — contact support";
|
|
15643
15679
|
case "ads_required":
|
|
15644
|
-
return "ads aren't displaying for this account, so free completions are paused — run free-pi through `npx free-pi-cli`, then retry";
|
|
15680
|
+
return "ads aren't displaying for this account, so free completions are paused — run free-pi through `npx free-pi-cli@latest`, then retry";
|
|
15645
15681
|
case "guest_allowance":
|
|
15646
15682
|
return "guest trial used up — sign in with GitHub (/login) for 15,000 free credits a day";
|
|
15647
15683
|
case "guest_unavailable":
|
|
15648
15684
|
case "guest_login_required":
|
|
15649
15685
|
return "this needs a GitHub sign-in — run /login for 15,000 free credits a day";
|
|
15686
|
+
case "region_unavailable":
|
|
15687
|
+
return "free-pi is not available in your region";
|
|
15688
|
+
case "model_requires_credits":
|
|
15689
|
+
return "this model needs purchased credits in your region — run /buy-credits, or switch to the default model";
|
|
15650
15690
|
default:
|
|
15651
15691
|
return assertExhaustiveErrorCode(code);
|
|
15652
15692
|
}
|
|
@@ -15666,6 +15706,7 @@ function buyMore(buyUrl) {
|
|
|
15666
15706
|
}
|
|
15667
15707
|
var CONFIRM_TURNS = 3;
|
|
15668
15708
|
var lastCreditUsd;
|
|
15709
|
+
var lastToken;
|
|
15669
15710
|
var confirm;
|
|
15670
15711
|
function creditAddedMessage(addedUsd) {
|
|
15671
15712
|
return `Payment received — ${usdToCredits(addedUsd).toLocaleString("en-US")} credits added to your balance.`;
|
|
@@ -15679,6 +15720,12 @@ async function renderMeter(deps, ctx) {
|
|
|
15679
15720
|
}
|
|
15680
15721
|
const theme = ctx.ui.theme;
|
|
15681
15722
|
const credit = typeof me.credit_usd === "number" ? me.credit_usd : undefined;
|
|
15723
|
+
const token = await deps.getToken();
|
|
15724
|
+
if (token !== lastToken) {
|
|
15725
|
+
lastToken = token;
|
|
15726
|
+
lastCreditUsd = undefined;
|
|
15727
|
+
confirm = undefined;
|
|
15728
|
+
}
|
|
15682
15729
|
const added = credit !== undefined && lastCreditUsd !== undefined ? credit - lastCreditUsd : 0;
|
|
15683
15730
|
lastCreditUsd = credit;
|
|
15684
15731
|
if (added > 0)
|
|
@@ -15773,6 +15820,7 @@ function createAdsExtension(opts) {
|
|
|
15773
15820
|
pi.on("session_start", async (_event, ctx) => {
|
|
15774
15821
|
stopped = false;
|
|
15775
15822
|
inline.reset(ctx);
|
|
15823
|
+
opts.registerMeterRefresh?.(() => renderMeter(deps, ctx));
|
|
15776
15824
|
await Promise.all([renderBanner(ctx, true), renderMeter(deps, ctx)]);
|
|
15777
15825
|
if (bannerInterval)
|
|
15778
15826
|
clearInterval(bannerInterval);
|
|
@@ -20198,6 +20246,8 @@ var IMAGE_GUIDELINES = [
|
|
|
20198
20246
|
"Each image uses part of the user's daily allowance, so do not regenerate unless the user asks."
|
|
20199
20247
|
];
|
|
20200
20248
|
var ERROR_TEXT = {
|
|
20249
|
+
guest_login_required: "This tool needs a GitHub sign-in; the guest trial cannot use it. Do not retry or work around it; tell the user to run /login (it also gives 15,000 free credits a day), and continue without this tool.",
|
|
20250
|
+
guest_unavailable: "The guest trial is not available right now. Do not retry; tell the user to run /login to sign in with GitHub, and continue without this tool.",
|
|
20201
20251
|
daily_cap: "The user's free allowance for today is used up. Do not retry. Tell the user they can get credits at freepi.ai/extra-credits, and continue without this tool.",
|
|
20202
20252
|
rolling_cap: "The user's short-term usage limit is reached. Do not retry; continue without this tool.",
|
|
20203
20253
|
rate_limited: "Too many image requests right now. Do not call generate_image again in this turn unless the user asks.",
|
|
@@ -20325,6 +20375,8 @@ var VISION_GUIDELINES = [
|
|
|
20325
20375
|
"The answer comes from another model and can be wrong. Verify layout claims with DOM measurements when you can."
|
|
20326
20376
|
];
|
|
20327
20377
|
var ERROR_TEXT2 = {
|
|
20378
|
+
guest_login_required: "This tool needs a GitHub sign-in; the guest trial cannot use it. Do not retry or work around it; tell the user to run /login (it also gives 15,000 free credits a day), and continue without this tool.",
|
|
20379
|
+
guest_unavailable: "The guest trial is not available right now. Do not retry; tell the user to run /login to sign in with GitHub, and continue without this tool.",
|
|
20328
20380
|
daily_cap: "The user's free allowance for today is used up. Do not retry. Tell the user they can get credits at freepi.ai/extra-credits, and continue without this tool.",
|
|
20329
20381
|
rolling_cap: "The user's short-term usage limit is reached. Do not retry; continue without this tool.",
|
|
20330
20382
|
rate_limited: "Another image question is already running or requests are too frequent. Do not call look_at_image again in this turn unless the user asks.",
|
|
@@ -20603,7 +20655,7 @@ var FREE_PI_DOCS = `# free-pi
|
|
|
20603
20655
|
|
|
20604
20656
|
## What free-pi is
|
|
20605
20657
|
free-pi is a free, ad-supported distribution of the pi coding agent.
|
|
20606
|
-
You run \`npx free-pi-cli\`, sign in with GitHub, and get free inference with
|
|
20658
|
+
You run \`npx free-pi-cli@latest\`, sign in with GitHub, and get free inference with
|
|
20607
20659
|
exciting models. Ads shown in the terminal pay for it. You never handle a
|
|
20608
20660
|
model API key. Config and the sign-in token live under \`~/.free-pi/agent\`,
|
|
20609
20661
|
separate from any \`~/.pi/agent\` install.
|
|
@@ -20629,7 +20681,7 @@ first. Purchased credits cover use beyond it and do not expire.
|
|
|
20629
20681
|
## Model and restarting
|
|
20630
20682
|
\`/model\` shows the current model and lets you switch it; the model can change
|
|
20631
20683
|
during a session. Credits are free-pi's usage unit, not model tokens. To
|
|
20632
|
-
restart, exit and run \`npx free-pi-cli\` again; \`--continue\` resumes the last
|
|
20684
|
+
restart, exit and run \`npx free-pi-cli@latest\` again; \`--continue\` resumes the last
|
|
20633
20685
|
session.
|
|
20634
20686
|
|
|
20635
20687
|
## Commands
|
|
@@ -21174,7 +21226,7 @@ var AGENT_RULES = `## Working rules
|
|
|
21174
21226
|
failed. Never an empty reply.
|
|
21175
21227
|
- For questions about free-pi itself (model, credits, limits, commands, restart), call the
|
|
21176
21228
|
${DOCS_TOOL_NAME} tool instead of searching files. Credits are free-pi's usage unit, not model tokens.
|
|
21177
|
-
To restart, run npx free-pi-cli.
|
|
21229
|
+
To restart, run npx free-pi-cli@latest.
|
|
21178
21230
|
- Write files longer than about 300 lines in parts, not one huge write.
|
|
21179
21231
|
- Never print whole env, config or credential files. Search for the specific name and never
|
|
21180
21232
|
echo secret values.
|
|
@@ -21400,6 +21452,22 @@ function mergeAttribution(a, b) {
|
|
|
21400
21452
|
|
|
21401
21453
|
// src/changelog.ts
|
|
21402
21454
|
var CHANGELOG_HIGHLIGHTS = [
|
|
21455
|
+
{
|
|
21456
|
+
version: "0.6.7",
|
|
21457
|
+
highlights: [
|
|
21458
|
+
"/logout now signs you out of free-pi: it removes your saved sign-in and exits. Restart free-pi to sign in again. Guests are told they are not signed in.",
|
|
21459
|
+
"The confirmation code shown during browser sign-in is now bold and bright, so it is easy to compare with the one on freepi.ai.",
|
|
21460
|
+
'Fixed a wrong "Payment received" line that could appear right after signing in.'
|
|
21461
|
+
]
|
|
21462
|
+
},
|
|
21463
|
+
{
|
|
21464
|
+
version: "0.6.6",
|
|
21465
|
+
highlights: [
|
|
21466
|
+
'Sign-in is now one click in your browser: press "Connect this terminal" on freepi.ai after checking the confirmation code. The GitHub device code is still used over SSH or without a browser, or with --device-login.',
|
|
21467
|
+
"Shorter consent screen and a one-line intro. The version check now runs before consent, and the footer credits refresh right after /login.",
|
|
21468
|
+
'If free-pi is not available in your region, sign-in says so instead of showing "HTTP 403". Guests using look_at_image or generate_image are told to run /login.'
|
|
21469
|
+
]
|
|
21470
|
+
},
|
|
21403
21471
|
{
|
|
21404
21472
|
version: "0.6.4",
|
|
21405
21473
|
highlights: [
|
|
@@ -21668,6 +21736,9 @@ function highlightsFor(version3) {
|
|
|
21668
21736
|
return CHANGELOG_HIGHLIGHTS.find((e) => e.version === version3)?.highlights;
|
|
21669
21737
|
}
|
|
21670
21738
|
|
|
21739
|
+
// src/commands.ts
|
|
21740
|
+
import { access } from "node:fs/promises";
|
|
21741
|
+
|
|
21671
21742
|
// src/auth-flow.ts
|
|
21672
21743
|
class UnauthorizedError extends Error {
|
|
21673
21744
|
constructor() {
|
|
@@ -21693,12 +21764,39 @@ async function postJsonRetry(baseUrl, path, body, attempts = 4, sleep = (ms) =>
|
|
|
21693
21764
|
}
|
|
21694
21765
|
}
|
|
21695
21766
|
}
|
|
21767
|
+
var REGION_UNAVAILABLE_LINE = "free-pi is not available in your region.";
|
|
21768
|
+
function refusalMessage(status, body) {
|
|
21769
|
+
if (body?.code === "region_unavailable") {
|
|
21770
|
+
return typeof body.message === "string" && body.message.trim() ? body.message : REGION_UNAVAILABLE_LINE;
|
|
21771
|
+
}
|
|
21772
|
+
if (status >= 400 && status < 500 && typeof body?.message === "string" && body.message.trim()) {
|
|
21773
|
+
return body.message;
|
|
21774
|
+
}
|
|
21775
|
+
return;
|
|
21776
|
+
}
|
|
21777
|
+
async function readRefusal(res) {
|
|
21778
|
+
return await res.json().catch(() => {
|
|
21779
|
+
return;
|
|
21780
|
+
});
|
|
21781
|
+
}
|
|
21696
21782
|
async function startDeviceFlow(baseUrl) {
|
|
21697
21783
|
const res = await postJsonRetry(baseUrl, "/auth/github/device", {});
|
|
21698
|
-
if (!res.ok)
|
|
21699
|
-
throw new Error(`device flow start failed: HTTP ${res.status}`);
|
|
21784
|
+
if (!res.ok) {
|
|
21785
|
+
throw new Error(refusalMessage(res.status, await readRefusal(res)) ?? `device flow start failed: HTTP ${res.status}`);
|
|
21786
|
+
}
|
|
21700
21787
|
return GithubDeviceResponseSchema.parse(await res.json());
|
|
21701
21788
|
}
|
|
21789
|
+
async function startConnect(baseUrl, consentVersion, attribution = {}, client = {}) {
|
|
21790
|
+
const res = await postJsonRetry(baseUrl, "/auth/connect/start", {
|
|
21791
|
+
consent_version: consentVersion,
|
|
21792
|
+
client,
|
|
21793
|
+
...attribution.signup_src ? { signup_src: attribution.signup_src } : {},
|
|
21794
|
+
...attribution.referral_code ? { referral_code: attribution.referral_code } : {}
|
|
21795
|
+
});
|
|
21796
|
+
if (!res.ok)
|
|
21797
|
+
throw new Error(refusalMessage(res.status, await readRefusal(res)) ?? `connect start failed: HTTP ${res.status}`);
|
|
21798
|
+
return ConnectStartResponseSchema.parse(await res.json());
|
|
21799
|
+
}
|
|
21702
21800
|
async function submitConsent(baseUrl, sessionId, consentVersion, attribution = {}) {
|
|
21703
21801
|
const res = await postJsonRetry(baseUrl, "/auth/consent", {
|
|
21704
21802
|
session_id: sessionId,
|
|
@@ -21706,8 +21804,9 @@ async function submitConsent(baseUrl, sessionId, consentVersion, attribution = {
|
|
|
21706
21804
|
...attribution.signup_src ? { signup_src: attribution.signup_src } : {},
|
|
21707
21805
|
...attribution.referral_code ? { referral_code: attribution.referral_code } : {}
|
|
21708
21806
|
});
|
|
21709
|
-
if (!res.ok)
|
|
21710
|
-
throw new Error(`consent submission failed: HTTP ${res.status}`);
|
|
21807
|
+
if (!res.ok) {
|
|
21808
|
+
throw new Error(refusalMessage(res.status, await readRefusal(res)) ?? `consent submission failed: HTTP ${res.status}`);
|
|
21809
|
+
}
|
|
21711
21810
|
}
|
|
21712
21811
|
|
|
21713
21812
|
class GuestUnavailableError extends Error {
|
|
@@ -21724,9 +21823,9 @@ async function startGuestSession(baseUrl, consentVersion, attribution = {}) {
|
|
|
21724
21823
|
...attribution.referral_code ? { referral_code: attribution.referral_code } : {}
|
|
21725
21824
|
});
|
|
21726
21825
|
if (res.status === 403) {
|
|
21727
|
-
const body = await res
|
|
21728
|
-
|
|
21729
|
-
|
|
21826
|
+
const body = await readRefusal(res);
|
|
21827
|
+
if (body?.code === "region_unavailable")
|
|
21828
|
+
throw new Error(refusalMessage(res.status, body));
|
|
21730
21829
|
throw new GuestUnavailableError(body?.reason ?? "disabled");
|
|
21731
21830
|
}
|
|
21732
21831
|
if (!res.ok)
|
|
@@ -21759,6 +21858,12 @@ class LoginTimeoutError extends Error {
|
|
|
21759
21858
|
super("github_login_timeout");
|
|
21760
21859
|
}
|
|
21761
21860
|
}
|
|
21861
|
+
|
|
21862
|
+
class LoginDeniedError extends Error {
|
|
21863
|
+
constructor() {
|
|
21864
|
+
super("login_denied");
|
|
21865
|
+
}
|
|
21866
|
+
}
|
|
21762
21867
|
async function pollForToken(baseUrl, sessionId, interval, sleep, opts = {}) {
|
|
21763
21868
|
let consecutiveNetworkFailures = 0;
|
|
21764
21869
|
const now = opts.now ?? Date.now;
|
|
@@ -21772,10 +21877,10 @@ async function pollForToken(baseUrl, sessionId, interval, sleep, opts = {}) {
|
|
|
21772
21877
|
if (opts.jwt)
|
|
21773
21878
|
headers.authorization = `Bearer ${opts.jwt}`;
|
|
21774
21879
|
const doFetch = opts.fetchImpl ?? fetch;
|
|
21775
|
-
res = await doFetch(new URL("/auth/token", baseUrl), {
|
|
21880
|
+
res = await doFetch(new URL(opts.endpoint ?? "/auth/token", baseUrl), {
|
|
21776
21881
|
method: "POST",
|
|
21777
21882
|
headers,
|
|
21778
|
-
body: JSON.stringify({ session_id: sessionId })
|
|
21883
|
+
body: JSON.stringify({ session_id: sessionId, ...opts.pollSecret ? { poll_secret: opts.pollSecret } : {} })
|
|
21779
21884
|
});
|
|
21780
21885
|
consecutiveNetworkFailures = 0;
|
|
21781
21886
|
} catch (err) {
|
|
@@ -21786,16 +21891,17 @@ async function pollForToken(baseUrl, sessionId, interval, sleep, opts = {}) {
|
|
|
21786
21891
|
await sleep(Math.max(interval, 5) * 1000);
|
|
21787
21892
|
continue;
|
|
21788
21893
|
}
|
|
21789
|
-
if (res.
|
|
21790
|
-
const body = await res
|
|
21791
|
-
|
|
21792
|
-
});
|
|
21793
|
-
if (body?.code === "consent_required") {
|
|
21894
|
+
if (!res.ok) {
|
|
21895
|
+
const body = await readRefusal(res);
|
|
21896
|
+
if (res.status === 403 && body?.code === "consent_required") {
|
|
21794
21897
|
throw new Error("consent_required: server did not record consent before polling");
|
|
21795
21898
|
}
|
|
21899
|
+
if (res.status === 403 && body?.code === "denied")
|
|
21900
|
+
throw new LoginDeniedError;
|
|
21901
|
+
if (res.status === 410 && body?.code === "expired")
|
|
21902
|
+
throw new LoginTimeoutError;
|
|
21903
|
+
throw new Error(refusalMessage(res.status, body) ?? `token poll failed: HTTP ${res.status}`);
|
|
21796
21904
|
}
|
|
21797
|
-
if (!res.ok)
|
|
21798
|
-
throw new Error(`token poll failed: HTTP ${res.status}`);
|
|
21799
21905
|
const parsed = AuthTokenResponseSchema.parse(await res.json());
|
|
21800
21906
|
if ("token" in parsed)
|
|
21801
21907
|
return parsed.token;
|
|
@@ -21894,40 +22000,18 @@ function clipboardCommand(platform) {
|
|
|
21894
22000
|
|
|
21895
22001
|
// src/consent.ts
|
|
21896
22002
|
var CONSENT_TEXT = `
|
|
21897
|
-
free-pi
|
|
21898
|
-
|
|
21899
|
-
|
|
21900
|
-
* Ad cards render in the terminal UI. Some ads come from a third-party ad
|
|
21901
|
-
provider (Gravity), which receives excerpts of your recent conversation
|
|
21902
|
-
to pick a relevant ad.
|
|
21903
|
-
* Gravity also keeps a session and visitor ID on your machine (in
|
|
21904
|
-
~/.free-pi) and receives usage events from free-pi to measure and
|
|
21905
|
-
attribute ads.
|
|
21906
|
-
* Your coding sessions (prompts, code, files, and model responses) are saved
|
|
21907
|
-
as traces and used to train models. We may share or sell that data to
|
|
21908
|
-
third parties. Once you accept, there is no opt-out, except where the law
|
|
21909
|
-
gives you one.
|
|
21910
|
-
* We do our best to remove personal information from traces before we store
|
|
21911
|
-
them. We cannot promise it is perfectly clean.
|
|
21912
|
-
* free-pi is in alpha and testing. There are no warranties or guarantees,
|
|
21913
|
-
expressed or implied, and no guarantee of uptime.
|
|
21914
|
-
* If GitHub email capture is enabled, we collect your GitHub email and may
|
|
21915
|
-
use it to contact you about the service or product.
|
|
21916
|
-
* free-pi-cli auto-updates itself when a new version is available. Disable
|
|
21917
|
-
with FREEPI_NO_AUTO_UPDATE or {"autoUpdate":false} in ~/.free-pi/config.json.
|
|
21918
|
-
|
|
21919
|
-
By accepting you agree to the Terms of Service (https://freepi.ai/terms)
|
|
21920
|
-
and the Privacy Policy (https://freepi.ai/privacy).
|
|
21921
|
-
|
|
21922
|
-
Decline and nothing is sent to the free-pi server.
|
|
22003
|
+
free-pi is free. In exchange, we show you ads and may train on your data.
|
|
22004
|
+
If you want to use free-pi, you accept that. If you don't, don't use it.
|
|
22005
|
+
Terms: https://freepi.ai/terms Privacy: https://freepi.ai/privacy
|
|
21923
22006
|
`.trim();
|
|
21924
22007
|
var CONSENT_VERSION = "4";
|
|
21925
|
-
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.";
|
|
22008
|
+
var NON_INTERACTIVE_CONSENT_TEXT = "free-pi first-run consent needs an interactive terminal (stdin is not a TTY). " + "Run `npx free-pi-cli@latest` in a terminal once to review and accept; " + "after that, non-interactive runs work.";
|
|
21926
22009
|
var CONSENT_PROMPT = "Accept? [y/N] ";
|
|
21927
22010
|
function parseConsentAnswer(answer) {
|
|
21928
22011
|
return /^\s*y(es)?\s*$/i.test(answer);
|
|
21929
22012
|
}
|
|
21930
|
-
var REPROMPT_CONSENT_TEXT = CONSENT_TEXT
|
|
22013
|
+
var REPROMPT_CONSENT_TEXT = `${CONSENT_TEXT}
|
|
22014
|
+
If you decline, free-pi exits and asks again next time.`;
|
|
21931
22015
|
|
|
21932
22016
|
// src/refill-card.ts
|
|
21933
22017
|
var LOGIN_BONUS_UNLOCK_LINE = "Sign in with GitHub to unlock a one-time comeback bonus.";
|
|
@@ -22151,6 +22235,79 @@ async function runDeviceLogin(deps, attribution = {}) {
|
|
|
22151
22235
|
throw err;
|
|
22152
22236
|
}
|
|
22153
22237
|
}
|
|
22238
|
+
var CONNECT_BROWSER_FAILED_LINE = "Couldn't open a browser here, so using the GitHub device code instead.";
|
|
22239
|
+
var CONNECT_FALLBACK_LINE = "Couldn't start the browser sign-in, so using the GitHub device code instead.";
|
|
22240
|
+
var LOGIN_DENIED_LINE = "Sign-in was cancelled in the browser. Run /login to try again.";
|
|
22241
|
+
var DEVICE_LOGIN_HINT = "Prefer a GitHub device code? Run: npx free-pi-cli@latest --device-login";
|
|
22242
|
+
function browserLikelyAvailable(env = process.env, platform = process.platform) {
|
|
22243
|
+
if (env.SSH_CONNECTION || env.SSH_CLIENT || env.SSH_TTY)
|
|
22244
|
+
return false;
|
|
22245
|
+
if (platform === "linux" && !env.DISPLAY && !env.WAYLAND_DISPLAY)
|
|
22246
|
+
return false;
|
|
22247
|
+
return true;
|
|
22248
|
+
}
|
|
22249
|
+
function emphasize(text, color = !process.env.NO_COLOR && process.stdout.isTTY === true) {
|
|
22250
|
+
return color ? `\x1B[1;97m${text}\x1B[0m` : text;
|
|
22251
|
+
}
|
|
22252
|
+
function formatConnectPrompt(connect, color) {
|
|
22253
|
+
return [
|
|
22254
|
+
"",
|
|
22255
|
+
LOGIN_REWARD_LINE,
|
|
22256
|
+
"",
|
|
22257
|
+
" Opening your browser to connect this terminal. If it doesn't open, go to:",
|
|
22258
|
+
` ${connect.connect_url}`,
|
|
22259
|
+
"",
|
|
22260
|
+
` Confirmation code: ${emphasize(connect.confirm_code, color)}`,
|
|
22261
|
+
" (the page must show the same code)",
|
|
22262
|
+
"",
|
|
22263
|
+
DEVICE_LOGIN_HINT,
|
|
22264
|
+
""
|
|
22265
|
+
].join(`
|
|
22266
|
+
`);
|
|
22267
|
+
}
|
|
22268
|
+
async function runConnectLogin(deps, attribution = {}) {
|
|
22269
|
+
const connect = await startConnect(deps.baseUrl, CONSENT_VERSION, attribution, {
|
|
22270
|
+
os: process.platform,
|
|
22271
|
+
cli_version: CLI_VERSION
|
|
22272
|
+
});
|
|
22273
|
+
deps.log(formatConnectPrompt(connect));
|
|
22274
|
+
let browserOpened = false;
|
|
22275
|
+
try {
|
|
22276
|
+
browserOpened = await deps.openBrowser(connect.connect_url) !== false;
|
|
22277
|
+
} catch {
|
|
22278
|
+
browserOpened = false;
|
|
22279
|
+
}
|
|
22280
|
+
if (!browserOpened) {
|
|
22281
|
+
deps.log(CONNECT_BROWSER_FAILED_LINE);
|
|
22282
|
+
return;
|
|
22283
|
+
}
|
|
22284
|
+
deps.log(LOGIN_WAITING_LINE.replace("GitHub", "browser sign-in"));
|
|
22285
|
+
const deadline = (deps.now ?? Date.now)() + connect.expires_in * 1000;
|
|
22286
|
+
return pollForToken(deps.baseUrl, connect.session_id, connect.interval, deps.sleep, {
|
|
22287
|
+
deadline,
|
|
22288
|
+
now: deps.now,
|
|
22289
|
+
jwt: deps.jwt,
|
|
22290
|
+
fetchImpl: deps.fetchImpl,
|
|
22291
|
+
endpoint: "/auth/connect/poll",
|
|
22292
|
+
pollSecret: connect.poll_secret
|
|
22293
|
+
});
|
|
22294
|
+
}
|
|
22295
|
+
async function runLoginAuto(deps, attribution = {}, opts = {}) {
|
|
22296
|
+
const env = opts.env ?? process.env;
|
|
22297
|
+
const forced = opts.forceDevice || env.FREEPI_DEVICE_LOGIN === "1" || env.FREEPI_DEVICE_LOGIN === "true";
|
|
22298
|
+
if (!forced && browserLikelyAvailable(env, opts.platform)) {
|
|
22299
|
+
try {
|
|
22300
|
+
const token = await runConnectLogin(deps, attribution);
|
|
22301
|
+
if (token)
|
|
22302
|
+
return token;
|
|
22303
|
+
} catch (err) {
|
|
22304
|
+
if (err instanceof LoginTimeoutError || err instanceof LoginDeniedError)
|
|
22305
|
+
throw err;
|
|
22306
|
+
deps.log(CONNECT_FALLBACK_LINE);
|
|
22307
|
+
}
|
|
22308
|
+
}
|
|
22309
|
+
return runDeviceLogin(deps, attribution);
|
|
22310
|
+
}
|
|
22154
22311
|
|
|
22155
22312
|
// src/allowance-counter.ts
|
|
22156
22313
|
var cached2;
|
|
@@ -22301,7 +22458,7 @@ async function runLogin(opts, ctx) {
|
|
|
22301
22458
|
try {
|
|
22302
22459
|
const attribution = opts.agentDir ? await loadAttribution(getAttributionPath(opts.agentDir)) : {};
|
|
22303
22460
|
const variant = resolveHandoffVariant(opts);
|
|
22304
|
-
const token = await
|
|
22461
|
+
const token = await runLoginAuto({
|
|
22305
22462
|
baseUrl: opts.baseUrl,
|
|
22306
22463
|
openBrowser: opts.openBrowserImpl ?? openBrowser,
|
|
22307
22464
|
sleep: (ms) => new Promise((r) => setTimeout(r, ms)),
|
|
@@ -22314,12 +22471,16 @@ async function runLogin(opts, ctx) {
|
|
|
22314
22471
|
if (opts.credentialsPath)
|
|
22315
22472
|
await saveJwt(opts.credentialsPath, token);
|
|
22316
22473
|
opts.onLogin?.(token);
|
|
22317
|
-
ctx.ui.notify(isHandoffTreatment(variant) ? SIGNED_IN_CONTINUE_LINE : "Signed in.
|
|
22474
|
+
ctx.ui.notify(isHandoffTreatment(variant) ? SIGNED_IN_CONTINUE_LINE : "Signed in. You now have 15,000 free credits a day, plus a one-time comeback bonus.", "info");
|
|
22318
22475
|
} catch (err) {
|
|
22319
22476
|
if (err instanceof LoginTimeoutError) {
|
|
22320
22477
|
ctx.ui.notify(LOGIN_TIMEOUT_LINE, "warning");
|
|
22321
22478
|
return;
|
|
22322
22479
|
}
|
|
22480
|
+
if (err instanceof LoginDeniedError) {
|
|
22481
|
+
ctx.ui.notify(LOGIN_DENIED_LINE, "warning");
|
|
22482
|
+
return;
|
|
22483
|
+
}
|
|
22323
22484
|
const reason = err instanceof Error && err.message ? ` (${err.message})` : "";
|
|
22324
22485
|
ctx.ui.notify(`Couldn't finish GitHub sign-in${reason}. Run /login to try again.`, "error");
|
|
22325
22486
|
}
|
|
@@ -22340,6 +22501,33 @@ function installLoginHandler(mode, opts) {
|
|
|
22340
22501
|
return runLogin(opts, { ui: { notify } });
|
|
22341
22502
|
};
|
|
22342
22503
|
}
|
|
22504
|
+
var LOGOUT_DONE_LINE = "Signed out of free-pi. Restart free-pi to sign in again.";
|
|
22505
|
+
var LOGOUT_NOT_SIGNED_IN_LINE = "You're not signed in to free-pi.";
|
|
22506
|
+
function installLogoutHandler(mode, opts) {
|
|
22507
|
+
const printAtExit = opts.printAtExit ?? ((line) => void process.once("exit", () => process.stdout.write(`${line}
|
|
22508
|
+
`)));
|
|
22509
|
+
const m = mode;
|
|
22510
|
+
mode.showOAuthSelector = async () => {
|
|
22511
|
+
if (!opts.credentialsPath || opts.isGuest?.()) {
|
|
22512
|
+
m.showStatus(LOGOUT_NOT_SIGNED_IN_LINE);
|
|
22513
|
+
return;
|
|
22514
|
+
}
|
|
22515
|
+
try {
|
|
22516
|
+
try {
|
|
22517
|
+
await runLogout({ credentialsPath: opts.credentialsPath, loadJwt, clearJwt, log: () => {} });
|
|
22518
|
+
} catch (err) {
|
|
22519
|
+
if (await access(opts.credentialsPath).then(() => true, () => false)) {
|
|
22520
|
+
const reason = err instanceof Error && err.message ? err.message : "unknown error";
|
|
22521
|
+
m.showError(`Couldn't sign out: ${reason}. Try: npx free-pi-cli@latest logout`);
|
|
22522
|
+
return;
|
|
22523
|
+
}
|
|
22524
|
+
}
|
|
22525
|
+
m.showStatus(LOGOUT_DONE_LINE);
|
|
22526
|
+
printAtExit(LOGOUT_DONE_LINE);
|
|
22527
|
+
await m.shutdown();
|
|
22528
|
+
} catch {}
|
|
22529
|
+
};
|
|
22530
|
+
}
|
|
22343
22531
|
async function showUsage(opts, ctx) {
|
|
22344
22532
|
const r = await fetchUsageText(opts);
|
|
22345
22533
|
ctx.ui.notify(r.text, r.ok ? "info" : "error");
|
|
@@ -24307,7 +24495,7 @@ var RESUME_COMMAND_RE = /(To resume this session:(?:\u001b\[[0-9;]*m|\s)*)pi(?=\
|
|
|
24307
24495
|
function rewriteResumeLine(chunk) {
|
|
24308
24496
|
if (!chunk.includes(RESUME_LABEL))
|
|
24309
24497
|
return chunk;
|
|
24310
|
-
return chunk.replace(RESUME_COMMAND_RE, "$1npx free-pi-cli");
|
|
24498
|
+
return chunk.replace(RESUME_COMMAND_RE, "$1npx free-pi-cli@latest");
|
|
24311
24499
|
}
|
|
24312
24500
|
function patchResumeLine(stream) {
|
|
24313
24501
|
const original = stream.write.bind(stream);
|
|
@@ -24367,12 +24555,17 @@ function projectTrustForCwd(cwd, launch, agentDir, hasTrustRequiringResources =
|
|
|
24367
24555
|
function createSessionAuth(initial) {
|
|
24368
24556
|
let jwt2 = initial.jwt;
|
|
24369
24557
|
let guest = initial.guest;
|
|
24558
|
+
let onSignedIn;
|
|
24370
24559
|
return {
|
|
24371
24560
|
getJwt: () => jwt2,
|
|
24372
24561
|
isGuest: () => guest,
|
|
24373
24562
|
becomeSignedIn(token) {
|
|
24374
24563
|
jwt2 = token;
|
|
24375
24564
|
guest = false;
|
|
24565
|
+
onSignedIn?.();
|
|
24566
|
+
},
|
|
24567
|
+
setOnSignedIn(fn) {
|
|
24568
|
+
onSignedIn = fn;
|
|
24376
24569
|
}
|
|
24377
24570
|
};
|
|
24378
24571
|
}
|
|
@@ -24450,6 +24643,7 @@ function buildRuntimeOptions(opts, sessionId) {
|
|
|
24450
24643
|
};
|
|
24451
24644
|
let advertiserUrl;
|
|
24452
24645
|
const adsExtension = createAdsExtension({
|
|
24646
|
+
registerMeterRefresh: (refresh) => auth.setOnSignedIn(() => void refresh().catch(() => {})),
|
|
24453
24647
|
baseUrl: opts.baseUrl,
|
|
24454
24648
|
getToken: () => auth.getJwt(),
|
|
24455
24649
|
sessionId,
|
|
@@ -24665,6 +24859,7 @@ async function launchPi(opts) {
|
|
|
24665
24859
|
onLogin: (token) => auth.becomeSignedIn(token),
|
|
24666
24860
|
variant: () => opts.guestHandoffVariant
|
|
24667
24861
|
});
|
|
24862
|
+
installLogoutHandler(mode, { credentialsPath: opts.credentialsPath, isGuest: () => auth.isGuest() });
|
|
24668
24863
|
installModelAllowanceHandler(mode);
|
|
24669
24864
|
installFooterRightAlign(mode);
|
|
24670
24865
|
await mode.run();
|
|
@@ -25008,18 +25203,7 @@ import { existsSync as existsSync6, readdirSync as readdirSync3, realpathSync as
|
|
|
25008
25203
|
import { homedir as homedir6 } from "node:os";
|
|
25009
25204
|
import { join as join12 } from "node:path";
|
|
25010
25205
|
var INTRO_ENV_SKIP = "FREEPI_NO_INTRO";
|
|
25011
|
-
var INTRO_TEXT =
|
|
25012
|
-
Welcome to free-pi — a free coding agent in your terminal.
|
|
25013
|
-
|
|
25014
|
-
• It's the pi coding agent, free to use. Ads in the terminal pay for the
|
|
25015
|
-
inference — ad content never reaches the model.
|
|
25016
|
-
• Tell it what to build, fix, or explain, like any coding assistant.
|
|
25017
|
-
• You can run free-pi in more than one terminal. If you close the terminal,
|
|
25018
|
-
relaunch with "npx free-pi-cli" — or resume where you left off with
|
|
25019
|
-
"npx free-pi-cli --session <id>" (the id prints when you exit).
|
|
25020
|
-
|
|
25021
|
-
Set ${INTRO_ENV_SKIP}=1 to skip this next time.
|
|
25022
|
-
`.trim();
|
|
25206
|
+
var INTRO_TEXT = "Tell free-pi what to build, fix, or explain. Resume later with: npx free-pi-cli@latest --continue";
|
|
25023
25207
|
function introSkipped(env = process.env) {
|
|
25024
25208
|
const v = env[INTRO_ENV_SKIP];
|
|
25025
25209
|
return v === "1" || v === "true";
|
|
@@ -25052,7 +25236,7 @@ Try one of these on this project — just type it:
|
|
|
25052
25236
|
`.trim();
|
|
25053
25237
|
var FIRST_TASK_EMPTY_TEXT = `
|
|
25054
25238
|
This folder doesn't look like a project. free-pi works best on code you
|
|
25055
|
-
already have: quit, "cd" into a project, and run "npx free-pi-cli" there.
|
|
25239
|
+
already have: quit, "cd" into a project, and run "npx free-pi-cli@latest" there.
|
|
25056
25240
|
Or just type: Create a small runnable hello-world app here and run it
|
|
25057
25241
|
`.trim();
|
|
25058
25242
|
function showFirstTaskHint(log, cwd = process.cwd(), home = homedir6()) {
|
|
@@ -25106,20 +25290,23 @@ async function loginFlow(deps, argv = []) {
|
|
|
25106
25290
|
throw new Error(resolved.error);
|
|
25107
25291
|
}
|
|
25108
25292
|
try {
|
|
25109
|
-
const token = await
|
|
25293
|
+
const token = await runLoginAuto({
|
|
25110
25294
|
baseUrl: deps.baseUrl,
|
|
25111
25295
|
openBrowser: deps.openBrowser,
|
|
25112
25296
|
sleep: deps.sleep,
|
|
25113
25297
|
log: deps.log,
|
|
25114
25298
|
jwt: await loadJwt(deps.credentialsPath),
|
|
25115
25299
|
credentialsPath: deps.credentialsPath
|
|
25116
|
-
}, resolved.attribution ?? {});
|
|
25300
|
+
}, resolved.attribution ?? {}, { forceDevice: argv.includes("--device-login"), env: deps.env, platform: deps.platform });
|
|
25117
25301
|
await saveJwt(deps.credentialsPath, token);
|
|
25118
25302
|
return token;
|
|
25119
25303
|
} catch (err) {
|
|
25120
25304
|
if (err instanceof LoginTimeoutError) {
|
|
25121
25305
|
deps.log(LOGIN_TIMEOUT_LINE);
|
|
25122
25306
|
}
|
|
25307
|
+
if (err instanceof LoginDeniedError) {
|
|
25308
|
+
deps.log(LOGIN_DENIED_LINE);
|
|
25309
|
+
}
|
|
25123
25310
|
throw err;
|
|
25124
25311
|
}
|
|
25125
25312
|
}
|
|
@@ -25153,6 +25340,21 @@ function reportResumeExposure(deps, jwt2, event, experiment = RESUME_ON_LAUNCH_E
|
|
|
25153
25340
|
async function defaultFindLatestSession(cwd, agentDir) {
|
|
25154
25341
|
return findLatestSession(cwd, getSessionDir(cwd, agentDir));
|
|
25155
25342
|
}
|
|
25343
|
+
async function handleVersionAction(deps, versionCheck) {
|
|
25344
|
+
const outcome = await deps.maybeSelfUpdate(versionCheck.action, versionCheck.latest);
|
|
25345
|
+
if (outcome === "updated-exit")
|
|
25346
|
+
return { code: 0 };
|
|
25347
|
+
if (outcome === "blocked") {
|
|
25348
|
+
const relaunched = await deps.relaunchLatest?.(versionCheck.latest);
|
|
25349
|
+
if (relaunched !== undefined)
|
|
25350
|
+
return { code: relaunched };
|
|
25351
|
+
deps.log(`free-pi-cli needs an update. From a trusted folder (for example: cd ~), run:
|
|
25352
|
+
npx --yes free-pi-cli@latest
|
|
25353
|
+
or: npm i -g free-pi-cli@latest`);
|
|
25354
|
+
return { code: 1 };
|
|
25355
|
+
}
|
|
25356
|
+
return outcome === "notice" ? { updateLatest: versionCheck.latest } : {};
|
|
25357
|
+
}
|
|
25156
25358
|
async function run(deps, argv = process.argv) {
|
|
25157
25359
|
const resumeFlags = parseResumeFlags(argv);
|
|
25158
25360
|
if (resumeFlags.sessionError) {
|
|
@@ -25172,6 +25374,17 @@ async function run(deps, argv = process.argv) {
|
|
|
25172
25374
|
${FINISH_SIGNIN_LINE}
|
|
25173
25375
|
`);
|
|
25174
25376
|
let jwt2 = await loadJwt(deps.credentialsPath);
|
|
25377
|
+
const hadJwt = Boolean(jwt2);
|
|
25378
|
+
let versionCheck = await deps.checkClientVersion(deps.baseUrl, CLI_VERSION, jwt2);
|
|
25379
|
+
let updateLatest;
|
|
25380
|
+
let versionHandled = false;
|
|
25381
|
+
if (versionCheck.action === "block") {
|
|
25382
|
+
const handled = await handleVersionAction(deps, versionCheck);
|
|
25383
|
+
if (handled.code !== undefined)
|
|
25384
|
+
return handled.code;
|
|
25385
|
+
updateLatest = handled.updateLatest;
|
|
25386
|
+
versionHandled = true;
|
|
25387
|
+
}
|
|
25175
25388
|
let guestHandoffVariant;
|
|
25176
25389
|
let showFirstTask = false;
|
|
25177
25390
|
let consentCurrent = false;
|
|
@@ -25202,7 +25415,7 @@ ${FINISH_SIGNIN_LINE}
|
|
|
25202
25415
|
if (!jwt2) {
|
|
25203
25416
|
const accepted = await deps.promptConsent();
|
|
25204
25417
|
if (!accepted) {
|
|
25205
|
-
deps.log("Declined. Exiting
|
|
25418
|
+
deps.log("Declined. Exiting.");
|
|
25206
25419
|
return 1;
|
|
25207
25420
|
}
|
|
25208
25421
|
showIntro(deps.log);
|
|
@@ -25233,24 +25446,13 @@ Guest trial is not available right now.
|
|
|
25233
25446
|
}
|
|
25234
25447
|
consentCurrent = true;
|
|
25235
25448
|
}
|
|
25236
|
-
|
|
25237
|
-
|
|
25238
|
-
if (versionCheck.action !== "ok") {
|
|
25239
|
-
const
|
|
25240
|
-
if (
|
|
25241
|
-
return
|
|
25242
|
-
|
|
25243
|
-
const relaunched = await deps.relaunchLatest?.(versionCheck.latest);
|
|
25244
|
-
if (relaunched !== undefined)
|
|
25245
|
-
return relaunched;
|
|
25246
|
-
deps.log(`free-pi-cli needs an update. From a trusted folder (for example: cd ~), run:
|
|
25247
|
-
npx --yes free-pi-cli@latest
|
|
25248
|
-
or: npm i -g free-pi-cli@latest`);
|
|
25249
|
-
return 1;
|
|
25250
|
-
}
|
|
25251
|
-
if (outcome === "notice") {
|
|
25252
|
-
updateLatest = versionCheck.latest;
|
|
25253
|
-
}
|
|
25449
|
+
if (!hadJwt)
|
|
25450
|
+
versionCheck = await deps.checkClientVersion(deps.baseUrl, CLI_VERSION, jwt2);
|
|
25451
|
+
if (!versionHandled && versionCheck.action !== "ok") {
|
|
25452
|
+
const handled = await handleVersionAction(deps, versionCheck);
|
|
25453
|
+
if (handled.code !== undefined)
|
|
25454
|
+
return handled.code;
|
|
25455
|
+
updateLatest = handled.updateLatest;
|
|
25254
25456
|
}
|
|
25255
25457
|
const whatsNew = maybeWhatsNew(CLI_VERSION, deps.agentDir);
|
|
25256
25458
|
if (whatsNew)
|
|
@@ -25335,8 +25537,8 @@ ${exitCard}`);
|
|
|
25335
25537
|
reportRefillCardShown({ baseUrl: deps.baseUrl, jwt: jwt2 });
|
|
25336
25538
|
}
|
|
25337
25539
|
deps.log(finalSessionId ? `
|
|
25338
|
-
free-pi session ended. Start it again anytime: npx free-pi-cli --session ${finalSessionId}` : `
|
|
25339
|
-
free-pi session ended. Start it again anytime: npx free-pi-cli`);
|
|
25540
|
+
free-pi session ended. Start it again anytime: npx free-pi-cli@latest --session ${finalSessionId}` : `
|
|
25541
|
+
free-pi session ended. Start it again anytime: npx free-pi-cli@latest`);
|
|
25340
25542
|
deps.log(`(or install once for a shorter command: npm i -g free-pi-cli → free-pi-cli)
|
|
25341
25543
|
`);
|
|
25342
25544
|
return 0;
|