free-pi-cli 0.2.1 → 0.2.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/README.md +19 -0
- package/dist/index.js +113 -14
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -13,6 +13,25 @@ may be used to train models), then signs you in with GitHub. Config and the
|
|
|
13
13
|
signed-in token live under `~/.free-pi/agent`, kept separate from any
|
|
14
14
|
existing `~/.pi/agent` install.
|
|
15
15
|
|
|
16
|
+
## Running it again
|
|
17
|
+
|
|
18
|
+
Closed the terminal? Just run the same command again — you stay signed in:
|
|
19
|
+
|
|
20
|
+
```
|
|
21
|
+
npx free-pi-cli
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
Prefer a shorter command? Install it once, globally, then call it by name:
|
|
25
|
+
|
|
26
|
+
```
|
|
27
|
+
npm install -g free-pi-cli
|
|
28
|
+
free-pi-cli
|
|
29
|
+
```
|
|
30
|
+
|
|
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` instead (each
|
|
33
|
+
run starts a fresh session).
|
|
34
|
+
|
|
16
35
|
## Development
|
|
17
36
|
|
|
18
37
|
- `FREEPI_BASE_URL` overrides the backend base URL (default
|
package/dist/index.js
CHANGED
|
@@ -14467,6 +14467,8 @@ function assertExhaustiveErrorCode(code) {
|
|
|
14467
14467
|
// ../shared/src/config.ts
|
|
14468
14468
|
var ConfigSchema = exports_external.object({
|
|
14469
14469
|
perUserDailyCapUsd: exports_external.number().default(5),
|
|
14470
|
+
perUserRollingCapUsd: exports_external.number().default(0),
|
|
14471
|
+
rollingCapWindowHours: exports_external.number().int().default(5),
|
|
14470
14472
|
globalDailyCapUsd: exports_external.number().default(50),
|
|
14471
14473
|
contextCharBudget: exports_external.number().int().default(800000),
|
|
14472
14474
|
accountsPerDayCeiling: exports_external.number().int().default(500),
|
|
@@ -14477,12 +14479,13 @@ var ConfigSchema = exports_external.object({
|
|
|
14477
14479
|
modelId: exports_external.string().default("deepseek/deepseek-v4-flash"),
|
|
14478
14480
|
providerDataCollection: exports_external.enum(["allow", "deny"]).optional(),
|
|
14479
14481
|
promptCacheEnabled: exports_external.boolean().default(false),
|
|
14482
|
+
emailCaptureEnabled: exports_external.boolean().default(false),
|
|
14480
14483
|
providerOrder: exports_external.array(exports_external.string()).optional(),
|
|
14481
14484
|
tierNewMaxDays: exports_external.number().int().default(30),
|
|
14482
14485
|
tierYoungMaxDays: exports_external.number().int().default(365),
|
|
14483
14486
|
tierCapNewUsd: exports_external.number().default(0.5),
|
|
14484
14487
|
tierCapYoungUsd: exports_external.number().default(2),
|
|
14485
|
-
sessionLeaseTtlMinutes: exports_external.number().int().default(
|
|
14488
|
+
sessionLeaseTtlMinutes: exports_external.number().int().default(30),
|
|
14486
14489
|
minCliVersion: exports_external.string().default("0.0.0"),
|
|
14487
14490
|
latestCliVersion: exports_external.string().default("0.2.0"),
|
|
14488
14491
|
abuseWindowMinutes: exports_external.number().int().default(30),
|
|
@@ -14520,6 +14523,8 @@ function envList(value) {
|
|
|
14520
14523
|
function loadConfig(env = process.env) {
|
|
14521
14524
|
return ConfigSchema.parse({
|
|
14522
14525
|
perUserDailyCapUsd: envNumber(env.PER_USER_DAILY_CAP_USD),
|
|
14526
|
+
perUserRollingCapUsd: envNumber(env.PER_USER_ROLLING_CAP_USD),
|
|
14527
|
+
rollingCapWindowHours: envNumber(env.ROLLING_CAP_WINDOW_HOURS),
|
|
14523
14528
|
globalDailyCapUsd: envNumber(env.GLOBAL_DAILY_CAP_USD),
|
|
14524
14529
|
contextCharBudget: envNumber(env.CONTEXT_CHAR_BUDGET),
|
|
14525
14530
|
accountsPerDayCeiling: envNumber(env.ACCOUNTS_PER_DAY_CEILING),
|
|
@@ -14530,6 +14535,7 @@ function loadConfig(env = process.env) {
|
|
|
14530
14535
|
modelId: env.MODEL_ID || undefined,
|
|
14531
14536
|
providerDataCollection: env.PROVIDER_DATA_COLLECTION || undefined,
|
|
14532
14537
|
promptCacheEnabled: envBoolean(env.PROMPT_CACHE_ENABLED),
|
|
14538
|
+
emailCaptureEnabled: envBoolean(env.EMAIL_CAPTURE_ENABLED),
|
|
14533
14539
|
providerOrder: envList(env.PROVIDER_ORDER),
|
|
14534
14540
|
tierNewMaxDays: envNumber(env.TIER_NEW_MAX_DAYS),
|
|
14535
14541
|
tierYoungMaxDays: envNumber(env.TIER_YOUNG_MAX_DAYS),
|
|
@@ -14582,7 +14588,8 @@ async function authedFetch(deps, path, init) {
|
|
|
14582
14588
|
headers: {
|
|
14583
14589
|
...init?.headers ?? {},
|
|
14584
14590
|
Authorization: `Bearer ${token}`,
|
|
14585
|
-
"Content-Type": "application/json"
|
|
14591
|
+
"Content-Type": "application/json",
|
|
14592
|
+
...deps.sessionId && (path.startsWith("/ads/next") || path === "/ads/impression") ? { "x-session-id": deps.sessionId } : {}
|
|
14586
14593
|
}
|
|
14587
14594
|
});
|
|
14588
14595
|
}
|
|
@@ -14659,7 +14666,7 @@ function renderAdCard(creative, clickUrl, theme, columns) {
|
|
|
14659
14666
|
theme.fg("borderAccent", top),
|
|
14660
14667
|
row(headline, { bold: true }),
|
|
14661
14668
|
row(body),
|
|
14662
|
-
row(`▸ ${cta}
|
|
14669
|
+
row(`▸ ${cta}`, { linkUrl: url2 }),
|
|
14663
14670
|
theme.fg("borderAccent", bottom)
|
|
14664
14671
|
];
|
|
14665
14672
|
}
|
|
@@ -14667,7 +14674,7 @@ function renderPlainAdLine(creative, clickUrl, theme, columns) {
|
|
|
14667
14674
|
const headline = sanitizeText(creative.headline);
|
|
14668
14675
|
const cta = sanitizeText(creative.cta);
|
|
14669
14676
|
const url2 = sanitizeText(clickUrl);
|
|
14670
|
-
const plain = truncate(`AD ▚ ${headline} — ${cta}
|
|
14677
|
+
const plain = truncate(`AD ▚ ${headline} — ${cta}`, Math.max(10, columns - 2));
|
|
14671
14678
|
return theme.fg("dim", osc8(plain, url2));
|
|
14672
14679
|
}
|
|
14673
14680
|
function renderErrorLine(message, theme) {
|
|
@@ -14739,6 +14746,8 @@ function mapErrorCode(code) {
|
|
|
14739
14746
|
switch (code) {
|
|
14740
14747
|
case "daily_cap":
|
|
14741
14748
|
return "out of free tokens today, resets at 00:00 UTC";
|
|
14749
|
+
case "rolling_cap":
|
|
14750
|
+
return "over the rolling spend cap on the free tier — wait for the Retry-After window";
|
|
14742
14751
|
case "concurrent":
|
|
14743
14752
|
return "only one request at a time on the free tier — wait for the current one to finish";
|
|
14744
14753
|
case "concurrent_session":
|
|
@@ -14785,7 +14794,12 @@ async function renderMeter(deps, ctx) {
|
|
|
14785
14794
|
// ../pi-ads/src/index.ts
|
|
14786
14795
|
var BANNER_REFRESH_MS = 10 * 60 * 1000;
|
|
14787
14796
|
function createAdsExtension(opts) {
|
|
14788
|
-
const deps = {
|
|
14797
|
+
const deps = {
|
|
14798
|
+
baseUrl: opts.baseUrl,
|
|
14799
|
+
getToken: opts.getToken,
|
|
14800
|
+
sessionId: opts.sessionId,
|
|
14801
|
+
fetchImpl: opts.fetchImpl
|
|
14802
|
+
};
|
|
14789
14803
|
const config3 = loadConfig();
|
|
14790
14804
|
return {
|
|
14791
14805
|
name: "free-pi-ads",
|
|
@@ -14815,15 +14829,18 @@ function createAdsExtension(opts) {
|
|
|
14815
14829
|
}
|
|
14816
14830
|
};
|
|
14817
14831
|
}
|
|
14832
|
+
|
|
14833
|
+
// src/pi-launch.ts
|
|
14834
|
+
import { join as join2, resolve } from "node:path";
|
|
14818
14835
|
// package.json
|
|
14819
14836
|
var package_default = {
|
|
14820
14837
|
name: "free-pi-cli",
|
|
14821
|
-
version: "0.2.
|
|
14838
|
+
version: "0.2.2",
|
|
14822
14839
|
description: "Free, ad-supported distribution of the pi coding agent. Inference paid for by ads.",
|
|
14823
14840
|
license: "MIT",
|
|
14824
14841
|
type: "module",
|
|
14825
14842
|
bin: {
|
|
14826
|
-
"free-pi-cli": "
|
|
14843
|
+
"free-pi-cli": "dist/index.js"
|
|
14827
14844
|
},
|
|
14828
14845
|
files: [
|
|
14829
14846
|
"dist",
|
|
@@ -19198,7 +19215,8 @@ function buildRuntimeOptions(opts, sessionId) {
|
|
|
19198
19215
|
};
|
|
19199
19216
|
const adsExtension = createAdsExtension({
|
|
19200
19217
|
baseUrl: opts.baseUrl,
|
|
19201
|
-
getToken: () => opts.jwt
|
|
19218
|
+
getToken: () => opts.jwt,
|
|
19219
|
+
sessionId
|
|
19202
19220
|
});
|
|
19203
19221
|
const usageToolExtension = createUsageToolExtension({
|
|
19204
19222
|
baseUrl: opts.baseUrl,
|
|
@@ -19223,10 +19241,28 @@ function buildRuntimeOptions(opts, sessionId) {
|
|
|
19223
19241
|
tools: ALLOWED_TOOL_NAMES
|
|
19224
19242
|
};
|
|
19225
19243
|
}
|
|
19244
|
+
function getSessionDir(cwd, agentDir) {
|
|
19245
|
+
const resolvedCwd = resolve(cwd);
|
|
19246
|
+
const safePath = `--${resolvedCwd.replace(/^[/\\]/, "").replace(/[/\\:]/g, "-")}--`;
|
|
19247
|
+
return join2(resolve(agentDir), "sessions", safePath);
|
|
19248
|
+
}
|
|
19249
|
+
async function createSessionManager(cwd, sessionDir, sessionId, api3 = SessionManager) {
|
|
19250
|
+
if (sessionId) {
|
|
19251
|
+
const infos = await api3.list(cwd, sessionDir);
|
|
19252
|
+
const info = infos.find((session) => session.id === sessionId);
|
|
19253
|
+
if (!info) {
|
|
19254
|
+
throw new Error(`No session found matching '${sessionId}' in this directory`);
|
|
19255
|
+
}
|
|
19256
|
+
return api3.open(info.path, sessionDir);
|
|
19257
|
+
}
|
|
19258
|
+
return api3.create(cwd, sessionDir);
|
|
19259
|
+
}
|
|
19226
19260
|
async function launchPi(opts) {
|
|
19227
|
-
const sessionId = crypto.randomUUID();
|
|
19228
|
-
const { settingsManager, resourceLoaderOptions, tools } = buildRuntimeOptions(opts, sessionId);
|
|
19229
19261
|
const cwd = process.cwd();
|
|
19262
|
+
const sessionDir = getSessionDir(cwd, opts.agentDir);
|
|
19263
|
+
const sm = await createSessionManager(cwd, sessionDir, opts.session);
|
|
19264
|
+
const sessionId = sm.getSessionId();
|
|
19265
|
+
const { settingsManager, resourceLoaderOptions, tools } = buildRuntimeOptions(opts, sessionId);
|
|
19230
19266
|
const createRuntime = async ({
|
|
19231
19267
|
cwd: rtCwd,
|
|
19232
19268
|
sessionManager,
|
|
@@ -19252,13 +19288,14 @@ async function launchPi(opts) {
|
|
|
19252
19288
|
const runtime = await createAgentSessionRuntime(createRuntime, {
|
|
19253
19289
|
cwd,
|
|
19254
19290
|
agentDir: opts.agentDir,
|
|
19255
|
-
sessionManager:
|
|
19291
|
+
sessionManager: sm
|
|
19256
19292
|
});
|
|
19257
19293
|
const mode = new InteractiveMode(runtime, {
|
|
19258
19294
|
initialImages: [],
|
|
19259
19295
|
initialMessages: []
|
|
19260
19296
|
});
|
|
19261
19297
|
await mode.run();
|
|
19298
|
+
return sm.getSessionId();
|
|
19262
19299
|
}
|
|
19263
19300
|
|
|
19264
19301
|
// src/prompt.ts
|
|
@@ -19272,6 +19309,8 @@ free-pi — before you start
|
|
|
19272
19309
|
* A persistent ad banner and occasional inline ad cards render in the
|
|
19273
19310
|
terminal UI. Ad content never enters the model's context.
|
|
19274
19311
|
* Your coding sessions may be used to train models.
|
|
19312
|
+
* If GitHub email capture is enabled, we collect your GitHub email and may
|
|
19313
|
+
use it to contact you about the service or product.
|
|
19275
19314
|
* free-pi-cli auto-updates itself when a new version is available. Disable
|
|
19276
19315
|
with FREEPI_NO_AUTO_UPDATE or {"autoUpdate":false} in ~/.free-pi/config.json.
|
|
19277
19316
|
|
|
@@ -19377,6 +19416,9 @@ async function checkToken(baseUrl, jwt2) {
|
|
|
19377
19416
|
throw new UnauthorizedError;
|
|
19378
19417
|
}
|
|
19379
19418
|
|
|
19419
|
+
// src/run.ts
|
|
19420
|
+
import { SessionManager as SessionManager2 } from "@earendil-works/pi-coding-agent";
|
|
19421
|
+
|
|
19380
19422
|
// src/credentials.ts
|
|
19381
19423
|
import { mkdir, readFile, rm, writeFile, chmod } from "node:fs/promises";
|
|
19382
19424
|
import { dirname } from "node:path";
|
|
@@ -19400,6 +19442,32 @@ async function clearJwt(credentialsPath) {
|
|
|
19400
19442
|
await rm(credentialsPath, { force: true });
|
|
19401
19443
|
}
|
|
19402
19444
|
|
|
19445
|
+
// src/onboarding.ts
|
|
19446
|
+
var INTRO_ENV_SKIP = "FREEPI_NO_INTRO";
|
|
19447
|
+
var INTRO_TEXT = `
|
|
19448
|
+
Welcome to free-pi — a free coding agent in your terminal.
|
|
19449
|
+
|
|
19450
|
+
• It's the pi coding agent, free to use. Ads in the terminal pay for the
|
|
19451
|
+
inference — ad content never reaches the model.
|
|
19452
|
+
• Tell it what to build, fix, or explain, like any coding assistant.
|
|
19453
|
+
• One session per account at a time. If you close the terminal, relaunch
|
|
19454
|
+
with "npx free-pi-cli" — or resume where you left off with
|
|
19455
|
+
"npx free-pi-cli --session <id>" (the id prints when you exit).
|
|
19456
|
+
|
|
19457
|
+
Set ${INTRO_ENV_SKIP}=1 to skip this next time.
|
|
19458
|
+
`.trim();
|
|
19459
|
+
function introSkipped(env = process.env) {
|
|
19460
|
+
const v = env[INTRO_ENV_SKIP];
|
|
19461
|
+
return v === "1" || v === "true";
|
|
19462
|
+
}
|
|
19463
|
+
function showIntro(log, env = process.env) {
|
|
19464
|
+
if (introSkipped(env))
|
|
19465
|
+
return;
|
|
19466
|
+
log(`
|
|
19467
|
+
${INTRO_TEXT}
|
|
19468
|
+
`);
|
|
19469
|
+
}
|
|
19470
|
+
|
|
19403
19471
|
// src/run.ts
|
|
19404
19472
|
async function loginFlow(deps) {
|
|
19405
19473
|
const device = await startDeviceFlow(deps.baseUrl);
|
|
@@ -19412,7 +19480,27 @@ Open ${device.verification_uri} and enter code: ${device.user_code}
|
|
|
19412
19480
|
await saveJwt(deps.credentialsPath, token);
|
|
19413
19481
|
return token;
|
|
19414
19482
|
}
|
|
19415
|
-
|
|
19483
|
+
function parseSessionArg(argv) {
|
|
19484
|
+
const flagIndex = argv.indexOf("--session");
|
|
19485
|
+
if (flagIndex === -1)
|
|
19486
|
+
return {};
|
|
19487
|
+
const value = argv[flagIndex + 1];
|
|
19488
|
+
if (!value || value.startsWith("--")) {
|
|
19489
|
+
return { error: "Missing value for --session" };
|
|
19490
|
+
}
|
|
19491
|
+
try {
|
|
19492
|
+
SessionManager2.inMemory(process.cwd(), { id: value });
|
|
19493
|
+
} catch (err) {
|
|
19494
|
+
return { error: `Invalid --session value: ${err instanceof Error ? err.message : String(err)}` };
|
|
19495
|
+
}
|
|
19496
|
+
return { session: value };
|
|
19497
|
+
}
|
|
19498
|
+
async function run(deps, argv = process.argv) {
|
|
19499
|
+
const sessionArg = parseSessionArg(argv);
|
|
19500
|
+
if (sessionArg.error) {
|
|
19501
|
+
deps.log(sessionArg.error);
|
|
19502
|
+
return 1;
|
|
19503
|
+
}
|
|
19416
19504
|
let jwt2 = await loadJwt(deps.credentialsPath);
|
|
19417
19505
|
if (jwt2) {
|
|
19418
19506
|
try {
|
|
@@ -19430,6 +19518,7 @@ async function run(deps) {
|
|
|
19430
19518
|
deps.log("Declined. Exiting without contacting the free-pi server.");
|
|
19431
19519
|
return 1;
|
|
19432
19520
|
}
|
|
19521
|
+
showIntro(deps.log);
|
|
19433
19522
|
jwt2 = await loginFlow(deps);
|
|
19434
19523
|
}
|
|
19435
19524
|
const versionCheck = await deps.checkClientVersion(deps.baseUrl, CLI_VERSION);
|
|
@@ -19445,7 +19534,17 @@ async function run(deps) {
|
|
|
19445
19534
|
deps.log(`update available: ${versionCheck.latest}, you have ${CLI_VERSION}; run \`npx free-pi-cli@latest\``);
|
|
19446
19535
|
}
|
|
19447
19536
|
}
|
|
19448
|
-
await deps.launchPi({
|
|
19537
|
+
const finalSessionId = await deps.launchPi({
|
|
19538
|
+
baseUrl: deps.baseUrl,
|
|
19539
|
+
jwt: jwt2,
|
|
19540
|
+
agentDir: deps.agentDir,
|
|
19541
|
+
session: sessionArg.session
|
|
19542
|
+
});
|
|
19543
|
+
deps.log(finalSessionId ? `
|
|
19544
|
+
free-pi session ended. Start it again anytime: npx free-pi-cli --session ${finalSessionId}` : `
|
|
19545
|
+
free-pi session ended. Start it again anytime: npx free-pi-cli`);
|
|
19546
|
+
deps.log(`(or install once for a shorter command: npm i -g free-pi-cli → free-pi-cli)
|
|
19547
|
+
`);
|
|
19449
19548
|
return 0;
|
|
19450
19549
|
}
|
|
19451
19550
|
|
|
@@ -19525,7 +19624,7 @@ async function main() {
|
|
|
19525
19624
|
credentialsPath: getCredentialsPath(agentDir),
|
|
19526
19625
|
promptConsent: promptConsentInteractive,
|
|
19527
19626
|
openBrowser,
|
|
19528
|
-
sleep: (ms) => new Promise((
|
|
19627
|
+
sleep: (ms) => new Promise((resolve2) => setTimeout(resolve2, ms)),
|
|
19529
19628
|
launchPi,
|
|
19530
19629
|
log: (message) => console.log(message),
|
|
19531
19630
|
checkClientVersion,
|
package/package.json
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "free-pi-cli",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.2",
|
|
4
4
|
"description": "Free, ad-supported distribution of the pi coding agent. Inference paid for by ads.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"bin": {
|
|
8
|
-
"free-pi-cli": "
|
|
8
|
+
"free-pi-cli": "dist/index.js"
|
|
9
9
|
},
|
|
10
10
|
"files": [
|
|
11
11
|
"dist",
|