free-pi-cli 0.6.4 → 0.6.6
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-m7ct3546.js} +244 -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-m7ct3546.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.6",
|
|
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
|
}
|
|
@@ -15773,6 +15813,7 @@ function createAdsExtension(opts) {
|
|
|
15773
15813
|
pi.on("session_start", async (_event, ctx) => {
|
|
15774
15814
|
stopped = false;
|
|
15775
15815
|
inline.reset(ctx);
|
|
15816
|
+
opts.registerMeterRefresh?.(() => renderMeter(deps, ctx));
|
|
15776
15817
|
await Promise.all([renderBanner(ctx, true), renderMeter(deps, ctx)]);
|
|
15777
15818
|
if (bannerInterval)
|
|
15778
15819
|
clearInterval(bannerInterval);
|
|
@@ -20198,6 +20239,8 @@ var IMAGE_GUIDELINES = [
|
|
|
20198
20239
|
"Each image uses part of the user's daily allowance, so do not regenerate unless the user asks."
|
|
20199
20240
|
];
|
|
20200
20241
|
var ERROR_TEXT = {
|
|
20242
|
+
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.",
|
|
20243
|
+
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
20244
|
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
20245
|
rolling_cap: "The user's short-term usage limit is reached. Do not retry; continue without this tool.",
|
|
20203
20246
|
rate_limited: "Too many image requests right now. Do not call generate_image again in this turn unless the user asks.",
|
|
@@ -20325,6 +20368,8 @@ var VISION_GUIDELINES = [
|
|
|
20325
20368
|
"The answer comes from another model and can be wrong. Verify layout claims with DOM measurements when you can."
|
|
20326
20369
|
];
|
|
20327
20370
|
var ERROR_TEXT2 = {
|
|
20371
|
+
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.",
|
|
20372
|
+
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
20373
|
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
20374
|
rolling_cap: "The user's short-term usage limit is reached. Do not retry; continue without this tool.",
|
|
20330
20375
|
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 +20648,7 @@ var FREE_PI_DOCS = `# free-pi
|
|
|
20603
20648
|
|
|
20604
20649
|
## What free-pi is
|
|
20605
20650
|
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
|
|
20651
|
+
You run \`npx free-pi-cli@latest\`, sign in with GitHub, and get free inference with
|
|
20607
20652
|
exciting models. Ads shown in the terminal pay for it. You never handle a
|
|
20608
20653
|
model API key. Config and the sign-in token live under \`~/.free-pi/agent\`,
|
|
20609
20654
|
separate from any \`~/.pi/agent\` install.
|
|
@@ -20629,7 +20674,7 @@ first. Purchased credits cover use beyond it and do not expire.
|
|
|
20629
20674
|
## Model and restarting
|
|
20630
20675
|
\`/model\` shows the current model and lets you switch it; the model can change
|
|
20631
20676
|
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
|
|
20677
|
+
restart, exit and run \`npx free-pi-cli@latest\` again; \`--continue\` resumes the last
|
|
20633
20678
|
session.
|
|
20634
20679
|
|
|
20635
20680
|
## Commands
|
|
@@ -21174,7 +21219,7 @@ var AGENT_RULES = `## Working rules
|
|
|
21174
21219
|
failed. Never an empty reply.
|
|
21175
21220
|
- For questions about free-pi itself (model, credits, limits, commands, restart), call the
|
|
21176
21221
|
${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.
|
|
21222
|
+
To restart, run npx free-pi-cli@latest.
|
|
21178
21223
|
- Write files longer than about 300 lines in parts, not one huge write.
|
|
21179
21224
|
- Never print whole env, config or credential files. Search for the specific name and never
|
|
21180
21225
|
echo secret values.
|
|
@@ -21400,6 +21445,14 @@ function mergeAttribution(a, b) {
|
|
|
21400
21445
|
|
|
21401
21446
|
// src/changelog.ts
|
|
21402
21447
|
var CHANGELOG_HIGHLIGHTS = [
|
|
21448
|
+
{
|
|
21449
|
+
version: "0.6.6",
|
|
21450
|
+
highlights: [
|
|
21451
|
+
'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.',
|
|
21452
|
+
"Shorter consent screen and a one-line intro. The version check now runs before consent, and the footer credits refresh right after /login.",
|
|
21453
|
+
'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.'
|
|
21454
|
+
]
|
|
21455
|
+
},
|
|
21403
21456
|
{
|
|
21404
21457
|
version: "0.6.4",
|
|
21405
21458
|
highlights: [
|
|
@@ -21693,12 +21746,39 @@ async function postJsonRetry(baseUrl, path, body, attempts = 4, sleep = (ms) =>
|
|
|
21693
21746
|
}
|
|
21694
21747
|
}
|
|
21695
21748
|
}
|
|
21749
|
+
var REGION_UNAVAILABLE_LINE = "free-pi is not available in your region.";
|
|
21750
|
+
function refusalMessage(status, body) {
|
|
21751
|
+
if (body?.code === "region_unavailable") {
|
|
21752
|
+
return typeof body.message === "string" && body.message.trim() ? body.message : REGION_UNAVAILABLE_LINE;
|
|
21753
|
+
}
|
|
21754
|
+
if (status >= 400 && status < 500 && typeof body?.message === "string" && body.message.trim()) {
|
|
21755
|
+
return body.message;
|
|
21756
|
+
}
|
|
21757
|
+
return;
|
|
21758
|
+
}
|
|
21759
|
+
async function readRefusal(res) {
|
|
21760
|
+
return await res.json().catch(() => {
|
|
21761
|
+
return;
|
|
21762
|
+
});
|
|
21763
|
+
}
|
|
21696
21764
|
async function startDeviceFlow(baseUrl) {
|
|
21697
21765
|
const res = await postJsonRetry(baseUrl, "/auth/github/device", {});
|
|
21698
|
-
if (!res.ok)
|
|
21699
|
-
throw new Error(`device flow start failed: HTTP ${res.status}`);
|
|
21766
|
+
if (!res.ok) {
|
|
21767
|
+
throw new Error(refusalMessage(res.status, await readRefusal(res)) ?? `device flow start failed: HTTP ${res.status}`);
|
|
21768
|
+
}
|
|
21700
21769
|
return GithubDeviceResponseSchema.parse(await res.json());
|
|
21701
21770
|
}
|
|
21771
|
+
async function startConnect(baseUrl, consentVersion, attribution = {}, client = {}) {
|
|
21772
|
+
const res = await postJsonRetry(baseUrl, "/auth/connect/start", {
|
|
21773
|
+
consent_version: consentVersion,
|
|
21774
|
+
client,
|
|
21775
|
+
...attribution.signup_src ? { signup_src: attribution.signup_src } : {},
|
|
21776
|
+
...attribution.referral_code ? { referral_code: attribution.referral_code } : {}
|
|
21777
|
+
});
|
|
21778
|
+
if (!res.ok)
|
|
21779
|
+
throw new Error(refusalMessage(res.status, await readRefusal(res)) ?? `connect start failed: HTTP ${res.status}`);
|
|
21780
|
+
return ConnectStartResponseSchema.parse(await res.json());
|
|
21781
|
+
}
|
|
21702
21782
|
async function submitConsent(baseUrl, sessionId, consentVersion, attribution = {}) {
|
|
21703
21783
|
const res = await postJsonRetry(baseUrl, "/auth/consent", {
|
|
21704
21784
|
session_id: sessionId,
|
|
@@ -21706,8 +21786,9 @@ async function submitConsent(baseUrl, sessionId, consentVersion, attribution = {
|
|
|
21706
21786
|
...attribution.signup_src ? { signup_src: attribution.signup_src } : {},
|
|
21707
21787
|
...attribution.referral_code ? { referral_code: attribution.referral_code } : {}
|
|
21708
21788
|
});
|
|
21709
|
-
if (!res.ok)
|
|
21710
|
-
throw new Error(`consent submission failed: HTTP ${res.status}`);
|
|
21789
|
+
if (!res.ok) {
|
|
21790
|
+
throw new Error(refusalMessage(res.status, await readRefusal(res)) ?? `consent submission failed: HTTP ${res.status}`);
|
|
21791
|
+
}
|
|
21711
21792
|
}
|
|
21712
21793
|
|
|
21713
21794
|
class GuestUnavailableError extends Error {
|
|
@@ -21724,9 +21805,9 @@ async function startGuestSession(baseUrl, consentVersion, attribution = {}) {
|
|
|
21724
21805
|
...attribution.referral_code ? { referral_code: attribution.referral_code } : {}
|
|
21725
21806
|
});
|
|
21726
21807
|
if (res.status === 403) {
|
|
21727
|
-
const body = await res
|
|
21728
|
-
|
|
21729
|
-
|
|
21808
|
+
const body = await readRefusal(res);
|
|
21809
|
+
if (body?.code === "region_unavailable")
|
|
21810
|
+
throw new Error(refusalMessage(res.status, body));
|
|
21730
21811
|
throw new GuestUnavailableError(body?.reason ?? "disabled");
|
|
21731
21812
|
}
|
|
21732
21813
|
if (!res.ok)
|
|
@@ -21759,6 +21840,12 @@ class LoginTimeoutError extends Error {
|
|
|
21759
21840
|
super("github_login_timeout");
|
|
21760
21841
|
}
|
|
21761
21842
|
}
|
|
21843
|
+
|
|
21844
|
+
class LoginDeniedError extends Error {
|
|
21845
|
+
constructor() {
|
|
21846
|
+
super("login_denied");
|
|
21847
|
+
}
|
|
21848
|
+
}
|
|
21762
21849
|
async function pollForToken(baseUrl, sessionId, interval, sleep, opts = {}) {
|
|
21763
21850
|
let consecutiveNetworkFailures = 0;
|
|
21764
21851
|
const now = opts.now ?? Date.now;
|
|
@@ -21772,10 +21859,10 @@ async function pollForToken(baseUrl, sessionId, interval, sleep, opts = {}) {
|
|
|
21772
21859
|
if (opts.jwt)
|
|
21773
21860
|
headers.authorization = `Bearer ${opts.jwt}`;
|
|
21774
21861
|
const doFetch = opts.fetchImpl ?? fetch;
|
|
21775
|
-
res = await doFetch(new URL("/auth/token", baseUrl), {
|
|
21862
|
+
res = await doFetch(new URL(opts.endpoint ?? "/auth/token", baseUrl), {
|
|
21776
21863
|
method: "POST",
|
|
21777
21864
|
headers,
|
|
21778
|
-
body: JSON.stringify({ session_id: sessionId })
|
|
21865
|
+
body: JSON.stringify({ session_id: sessionId, ...opts.pollSecret ? { poll_secret: opts.pollSecret } : {} })
|
|
21779
21866
|
});
|
|
21780
21867
|
consecutiveNetworkFailures = 0;
|
|
21781
21868
|
} catch (err) {
|
|
@@ -21786,16 +21873,17 @@ async function pollForToken(baseUrl, sessionId, interval, sleep, opts = {}) {
|
|
|
21786
21873
|
await sleep(Math.max(interval, 5) * 1000);
|
|
21787
21874
|
continue;
|
|
21788
21875
|
}
|
|
21789
|
-
if (res.
|
|
21790
|
-
const body = await res
|
|
21791
|
-
|
|
21792
|
-
});
|
|
21793
|
-
if (body?.code === "consent_required") {
|
|
21876
|
+
if (!res.ok) {
|
|
21877
|
+
const body = await readRefusal(res);
|
|
21878
|
+
if (res.status === 403 && body?.code === "consent_required") {
|
|
21794
21879
|
throw new Error("consent_required: server did not record consent before polling");
|
|
21795
21880
|
}
|
|
21881
|
+
if (res.status === 403 && body?.code === "denied")
|
|
21882
|
+
throw new LoginDeniedError;
|
|
21883
|
+
if (res.status === 410 && body?.code === "expired")
|
|
21884
|
+
throw new LoginTimeoutError;
|
|
21885
|
+
throw new Error(refusalMessage(res.status, body) ?? `token poll failed: HTTP ${res.status}`);
|
|
21796
21886
|
}
|
|
21797
|
-
if (!res.ok)
|
|
21798
|
-
throw new Error(`token poll failed: HTTP ${res.status}`);
|
|
21799
21887
|
const parsed = AuthTokenResponseSchema.parse(await res.json());
|
|
21800
21888
|
if ("token" in parsed)
|
|
21801
21889
|
return parsed.token;
|
|
@@ -21894,40 +21982,18 @@ function clipboardCommand(platform) {
|
|
|
21894
21982
|
|
|
21895
21983
|
// src/consent.ts
|
|
21896
21984
|
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.
|
|
21985
|
+
free-pi is free. In exchange, we show you ads and may train on your data.
|
|
21986
|
+
If you want to use free-pi, you accept that. If you don't, don't use it.
|
|
21987
|
+
Terms: https://freepi.ai/terms Privacy: https://freepi.ai/privacy
|
|
21923
21988
|
`.trim();
|
|
21924
21989
|
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.";
|
|
21990
|
+
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
21991
|
var CONSENT_PROMPT = "Accept? [y/N] ";
|
|
21927
21992
|
function parseConsentAnswer(answer) {
|
|
21928
21993
|
return /^\s*y(es)?\s*$/i.test(answer);
|
|
21929
21994
|
}
|
|
21930
|
-
var REPROMPT_CONSENT_TEXT = CONSENT_TEXT
|
|
21995
|
+
var REPROMPT_CONSENT_TEXT = `${CONSENT_TEXT}
|
|
21996
|
+
If you decline, free-pi exits and asks again next time.`;
|
|
21931
21997
|
|
|
21932
21998
|
// src/refill-card.ts
|
|
21933
21999
|
var LOGIN_BONUS_UNLOCK_LINE = "Sign in with GitHub to unlock a one-time comeback bonus.";
|
|
@@ -22151,6 +22217,76 @@ async function runDeviceLogin(deps, attribution = {}) {
|
|
|
22151
22217
|
throw err;
|
|
22152
22218
|
}
|
|
22153
22219
|
}
|
|
22220
|
+
var CONNECT_BROWSER_FAILED_LINE = "Couldn't open a browser here, so using the GitHub device code instead.";
|
|
22221
|
+
var CONNECT_FALLBACK_LINE = "Couldn't start the browser sign-in, so using the GitHub device code instead.";
|
|
22222
|
+
var LOGIN_DENIED_LINE = "Sign-in was cancelled in the browser. Run /login to try again.";
|
|
22223
|
+
var DEVICE_LOGIN_HINT = "Prefer a GitHub device code? Run: npx free-pi-cli@latest --device-login";
|
|
22224
|
+
function browserLikelyAvailable(env = process.env, platform = process.platform) {
|
|
22225
|
+
if (env.SSH_CONNECTION || env.SSH_CLIENT || env.SSH_TTY)
|
|
22226
|
+
return false;
|
|
22227
|
+
if (platform === "linux" && !env.DISPLAY && !env.WAYLAND_DISPLAY)
|
|
22228
|
+
return false;
|
|
22229
|
+
return true;
|
|
22230
|
+
}
|
|
22231
|
+
function formatConnectPrompt(connect) {
|
|
22232
|
+
return [
|
|
22233
|
+
"",
|
|
22234
|
+
LOGIN_REWARD_LINE,
|
|
22235
|
+
"",
|
|
22236
|
+
" Opening your browser to connect this terminal. If it doesn't open, go to:",
|
|
22237
|
+
` ${connect.connect_url}`,
|
|
22238
|
+
"",
|
|
22239
|
+
` Confirmation code: ${connect.confirm_code}`,
|
|
22240
|
+
" (the page must show the same code)",
|
|
22241
|
+
"",
|
|
22242
|
+
DEVICE_LOGIN_HINT,
|
|
22243
|
+
""
|
|
22244
|
+
].join(`
|
|
22245
|
+
`);
|
|
22246
|
+
}
|
|
22247
|
+
async function runConnectLogin(deps, attribution = {}) {
|
|
22248
|
+
const connect = await startConnect(deps.baseUrl, CONSENT_VERSION, attribution, {
|
|
22249
|
+
os: process.platform,
|
|
22250
|
+
cli_version: CLI_VERSION
|
|
22251
|
+
});
|
|
22252
|
+
deps.log(formatConnectPrompt(connect));
|
|
22253
|
+
let browserOpened = false;
|
|
22254
|
+
try {
|
|
22255
|
+
browserOpened = await deps.openBrowser(connect.connect_url) !== false;
|
|
22256
|
+
} catch {
|
|
22257
|
+
browserOpened = false;
|
|
22258
|
+
}
|
|
22259
|
+
if (!browserOpened) {
|
|
22260
|
+
deps.log(CONNECT_BROWSER_FAILED_LINE);
|
|
22261
|
+
return;
|
|
22262
|
+
}
|
|
22263
|
+
deps.log(LOGIN_WAITING_LINE.replace("GitHub", "browser sign-in"));
|
|
22264
|
+
const deadline = (deps.now ?? Date.now)() + connect.expires_in * 1000;
|
|
22265
|
+
return pollForToken(deps.baseUrl, connect.session_id, connect.interval, deps.sleep, {
|
|
22266
|
+
deadline,
|
|
22267
|
+
now: deps.now,
|
|
22268
|
+
jwt: deps.jwt,
|
|
22269
|
+
fetchImpl: deps.fetchImpl,
|
|
22270
|
+
endpoint: "/auth/connect/poll",
|
|
22271
|
+
pollSecret: connect.poll_secret
|
|
22272
|
+
});
|
|
22273
|
+
}
|
|
22274
|
+
async function runLoginAuto(deps, attribution = {}, opts = {}) {
|
|
22275
|
+
const env = opts.env ?? process.env;
|
|
22276
|
+
const forced = opts.forceDevice || env.FREEPI_DEVICE_LOGIN === "1" || env.FREEPI_DEVICE_LOGIN === "true";
|
|
22277
|
+
if (!forced && browserLikelyAvailable(env, opts.platform)) {
|
|
22278
|
+
try {
|
|
22279
|
+
const token = await runConnectLogin(deps, attribution);
|
|
22280
|
+
if (token)
|
|
22281
|
+
return token;
|
|
22282
|
+
} catch (err) {
|
|
22283
|
+
if (err instanceof LoginTimeoutError || err instanceof LoginDeniedError)
|
|
22284
|
+
throw err;
|
|
22285
|
+
deps.log(CONNECT_FALLBACK_LINE);
|
|
22286
|
+
}
|
|
22287
|
+
}
|
|
22288
|
+
return runDeviceLogin(deps, attribution);
|
|
22289
|
+
}
|
|
22154
22290
|
|
|
22155
22291
|
// src/allowance-counter.ts
|
|
22156
22292
|
var cached2;
|
|
@@ -22301,7 +22437,7 @@ async function runLogin(opts, ctx) {
|
|
|
22301
22437
|
try {
|
|
22302
22438
|
const attribution = opts.agentDir ? await loadAttribution(getAttributionPath(opts.agentDir)) : {};
|
|
22303
22439
|
const variant = resolveHandoffVariant(opts);
|
|
22304
|
-
const token = await
|
|
22440
|
+
const token = await runLoginAuto({
|
|
22305
22441
|
baseUrl: opts.baseUrl,
|
|
22306
22442
|
openBrowser: opts.openBrowserImpl ?? openBrowser,
|
|
22307
22443
|
sleep: (ms) => new Promise((r) => setTimeout(r, ms)),
|
|
@@ -22314,12 +22450,16 @@ async function runLogin(opts, ctx) {
|
|
|
22314
22450
|
if (opts.credentialsPath)
|
|
22315
22451
|
await saveJwt(opts.credentialsPath, token);
|
|
22316
22452
|
opts.onLogin?.(token);
|
|
22317
|
-
ctx.ui.notify(isHandoffTreatment(variant) ? SIGNED_IN_CONTINUE_LINE : "Signed in.
|
|
22453
|
+
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
22454
|
} catch (err) {
|
|
22319
22455
|
if (err instanceof LoginTimeoutError) {
|
|
22320
22456
|
ctx.ui.notify(LOGIN_TIMEOUT_LINE, "warning");
|
|
22321
22457
|
return;
|
|
22322
22458
|
}
|
|
22459
|
+
if (err instanceof LoginDeniedError) {
|
|
22460
|
+
ctx.ui.notify(LOGIN_DENIED_LINE, "warning");
|
|
22461
|
+
return;
|
|
22462
|
+
}
|
|
22323
22463
|
const reason = err instanceof Error && err.message ? ` (${err.message})` : "";
|
|
22324
22464
|
ctx.ui.notify(`Couldn't finish GitHub sign-in${reason}. Run /login to try again.`, "error");
|
|
22325
22465
|
}
|
|
@@ -24307,7 +24447,7 @@ var RESUME_COMMAND_RE = /(To resume this session:(?:\u001b\[[0-9;]*m|\s)*)pi(?=\
|
|
|
24307
24447
|
function rewriteResumeLine(chunk) {
|
|
24308
24448
|
if (!chunk.includes(RESUME_LABEL))
|
|
24309
24449
|
return chunk;
|
|
24310
|
-
return chunk.replace(RESUME_COMMAND_RE, "$1npx free-pi-cli");
|
|
24450
|
+
return chunk.replace(RESUME_COMMAND_RE, "$1npx free-pi-cli@latest");
|
|
24311
24451
|
}
|
|
24312
24452
|
function patchResumeLine(stream) {
|
|
24313
24453
|
const original = stream.write.bind(stream);
|
|
@@ -24367,12 +24507,17 @@ function projectTrustForCwd(cwd, launch, agentDir, hasTrustRequiringResources =
|
|
|
24367
24507
|
function createSessionAuth(initial) {
|
|
24368
24508
|
let jwt2 = initial.jwt;
|
|
24369
24509
|
let guest = initial.guest;
|
|
24510
|
+
let onSignedIn;
|
|
24370
24511
|
return {
|
|
24371
24512
|
getJwt: () => jwt2,
|
|
24372
24513
|
isGuest: () => guest,
|
|
24373
24514
|
becomeSignedIn(token) {
|
|
24374
24515
|
jwt2 = token;
|
|
24375
24516
|
guest = false;
|
|
24517
|
+
onSignedIn?.();
|
|
24518
|
+
},
|
|
24519
|
+
setOnSignedIn(fn) {
|
|
24520
|
+
onSignedIn = fn;
|
|
24376
24521
|
}
|
|
24377
24522
|
};
|
|
24378
24523
|
}
|
|
@@ -24450,6 +24595,7 @@ function buildRuntimeOptions(opts, sessionId) {
|
|
|
24450
24595
|
};
|
|
24451
24596
|
let advertiserUrl;
|
|
24452
24597
|
const adsExtension = createAdsExtension({
|
|
24598
|
+
registerMeterRefresh: (refresh) => auth.setOnSignedIn(() => void refresh().catch(() => {})),
|
|
24453
24599
|
baseUrl: opts.baseUrl,
|
|
24454
24600
|
getToken: () => auth.getJwt(),
|
|
24455
24601
|
sessionId,
|
|
@@ -25008,18 +25154,7 @@ import { existsSync as existsSync6, readdirSync as readdirSync3, realpathSync as
|
|
|
25008
25154
|
import { homedir as homedir6 } from "node:os";
|
|
25009
25155
|
import { join as join12 } from "node:path";
|
|
25010
25156
|
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();
|
|
25157
|
+
var INTRO_TEXT = "Tell free-pi what to build, fix, or explain. Resume later with: npx free-pi-cli@latest --continue";
|
|
25023
25158
|
function introSkipped(env = process.env) {
|
|
25024
25159
|
const v = env[INTRO_ENV_SKIP];
|
|
25025
25160
|
return v === "1" || v === "true";
|
|
@@ -25052,7 +25187,7 @@ Try one of these on this project — just type it:
|
|
|
25052
25187
|
`.trim();
|
|
25053
25188
|
var FIRST_TASK_EMPTY_TEXT = `
|
|
25054
25189
|
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.
|
|
25190
|
+
already have: quit, "cd" into a project, and run "npx free-pi-cli@latest" there.
|
|
25056
25191
|
Or just type: Create a small runnable hello-world app here and run it
|
|
25057
25192
|
`.trim();
|
|
25058
25193
|
function showFirstTaskHint(log, cwd = process.cwd(), home = homedir6()) {
|
|
@@ -25106,20 +25241,23 @@ async function loginFlow(deps, argv = []) {
|
|
|
25106
25241
|
throw new Error(resolved.error);
|
|
25107
25242
|
}
|
|
25108
25243
|
try {
|
|
25109
|
-
const token = await
|
|
25244
|
+
const token = await runLoginAuto({
|
|
25110
25245
|
baseUrl: deps.baseUrl,
|
|
25111
25246
|
openBrowser: deps.openBrowser,
|
|
25112
25247
|
sleep: deps.sleep,
|
|
25113
25248
|
log: deps.log,
|
|
25114
25249
|
jwt: await loadJwt(deps.credentialsPath),
|
|
25115
25250
|
credentialsPath: deps.credentialsPath
|
|
25116
|
-
}, resolved.attribution ?? {});
|
|
25251
|
+
}, resolved.attribution ?? {}, { forceDevice: argv.includes("--device-login"), env: deps.env, platform: deps.platform });
|
|
25117
25252
|
await saveJwt(deps.credentialsPath, token);
|
|
25118
25253
|
return token;
|
|
25119
25254
|
} catch (err) {
|
|
25120
25255
|
if (err instanceof LoginTimeoutError) {
|
|
25121
25256
|
deps.log(LOGIN_TIMEOUT_LINE);
|
|
25122
25257
|
}
|
|
25258
|
+
if (err instanceof LoginDeniedError) {
|
|
25259
|
+
deps.log(LOGIN_DENIED_LINE);
|
|
25260
|
+
}
|
|
25123
25261
|
throw err;
|
|
25124
25262
|
}
|
|
25125
25263
|
}
|
|
@@ -25153,6 +25291,21 @@ function reportResumeExposure(deps, jwt2, event, experiment = RESUME_ON_LAUNCH_E
|
|
|
25153
25291
|
async function defaultFindLatestSession(cwd, agentDir) {
|
|
25154
25292
|
return findLatestSession(cwd, getSessionDir(cwd, agentDir));
|
|
25155
25293
|
}
|
|
25294
|
+
async function handleVersionAction(deps, versionCheck) {
|
|
25295
|
+
const outcome = await deps.maybeSelfUpdate(versionCheck.action, versionCheck.latest);
|
|
25296
|
+
if (outcome === "updated-exit")
|
|
25297
|
+
return { code: 0 };
|
|
25298
|
+
if (outcome === "blocked") {
|
|
25299
|
+
const relaunched = await deps.relaunchLatest?.(versionCheck.latest);
|
|
25300
|
+
if (relaunched !== undefined)
|
|
25301
|
+
return { code: relaunched };
|
|
25302
|
+
deps.log(`free-pi-cli needs an update. From a trusted folder (for example: cd ~), run:
|
|
25303
|
+
npx --yes free-pi-cli@latest
|
|
25304
|
+
or: npm i -g free-pi-cli@latest`);
|
|
25305
|
+
return { code: 1 };
|
|
25306
|
+
}
|
|
25307
|
+
return outcome === "notice" ? { updateLatest: versionCheck.latest } : {};
|
|
25308
|
+
}
|
|
25156
25309
|
async function run(deps, argv = process.argv) {
|
|
25157
25310
|
const resumeFlags = parseResumeFlags(argv);
|
|
25158
25311
|
if (resumeFlags.sessionError) {
|
|
@@ -25172,6 +25325,17 @@ async function run(deps, argv = process.argv) {
|
|
|
25172
25325
|
${FINISH_SIGNIN_LINE}
|
|
25173
25326
|
`);
|
|
25174
25327
|
let jwt2 = await loadJwt(deps.credentialsPath);
|
|
25328
|
+
const hadJwt = Boolean(jwt2);
|
|
25329
|
+
let versionCheck = await deps.checkClientVersion(deps.baseUrl, CLI_VERSION, jwt2);
|
|
25330
|
+
let updateLatest;
|
|
25331
|
+
let versionHandled = false;
|
|
25332
|
+
if (versionCheck.action === "block") {
|
|
25333
|
+
const handled = await handleVersionAction(deps, versionCheck);
|
|
25334
|
+
if (handled.code !== undefined)
|
|
25335
|
+
return handled.code;
|
|
25336
|
+
updateLatest = handled.updateLatest;
|
|
25337
|
+
versionHandled = true;
|
|
25338
|
+
}
|
|
25175
25339
|
let guestHandoffVariant;
|
|
25176
25340
|
let showFirstTask = false;
|
|
25177
25341
|
let consentCurrent = false;
|
|
@@ -25202,7 +25366,7 @@ ${FINISH_SIGNIN_LINE}
|
|
|
25202
25366
|
if (!jwt2) {
|
|
25203
25367
|
const accepted = await deps.promptConsent();
|
|
25204
25368
|
if (!accepted) {
|
|
25205
|
-
deps.log("Declined. Exiting
|
|
25369
|
+
deps.log("Declined. Exiting.");
|
|
25206
25370
|
return 1;
|
|
25207
25371
|
}
|
|
25208
25372
|
showIntro(deps.log);
|
|
@@ -25233,24 +25397,13 @@ Guest trial is not available right now.
|
|
|
25233
25397
|
}
|
|
25234
25398
|
consentCurrent = true;
|
|
25235
25399
|
}
|
|
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
|
-
}
|
|
25400
|
+
if (!hadJwt)
|
|
25401
|
+
versionCheck = await deps.checkClientVersion(deps.baseUrl, CLI_VERSION, jwt2);
|
|
25402
|
+
if (!versionHandled && versionCheck.action !== "ok") {
|
|
25403
|
+
const handled = await handleVersionAction(deps, versionCheck);
|
|
25404
|
+
if (handled.code !== undefined)
|
|
25405
|
+
return handled.code;
|
|
25406
|
+
updateLatest = handled.updateLatest;
|
|
25254
25407
|
}
|
|
25255
25408
|
const whatsNew = maybeWhatsNew(CLI_VERSION, deps.agentDir);
|
|
25256
25409
|
if (whatsNew)
|
|
@@ -25335,8 +25488,8 @@ ${exitCard}`);
|
|
|
25335
25488
|
reportRefillCardShown({ baseUrl: deps.baseUrl, jwt: jwt2 });
|
|
25336
25489
|
}
|
|
25337
25490
|
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`);
|
|
25491
|
+
free-pi session ended. Start it again anytime: npx free-pi-cli@latest --session ${finalSessionId}` : `
|
|
25492
|
+
free-pi session ended. Start it again anytime: npx free-pi-cli@latest`);
|
|
25340
25493
|
deps.log(`(or install once for a shorter command: npm i -g free-pi-cli → free-pi-cli)
|
|
25341
25494
|
`);
|
|
25342
25495
|
return 0;
|