free-pi-cli 0.2.10 → 0.2.12
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +1 -1
- package/dist/{main-b5491jxx.js → main-mh0wmxjc.js} +202 -29
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -48,7 +48,7 @@ function openBrowser(url, platform = process.platform, spawnImpl = spawn) {
|
|
|
48
48
|
// package.json
|
|
49
49
|
var package_default = {
|
|
50
50
|
name: "free-pi-cli",
|
|
51
|
-
version: "0.2.
|
|
51
|
+
version: "0.2.12",
|
|
52
52
|
description: "Free, ad-supported distribution of the pi coding agent. Inference paid for by ads.",
|
|
53
53
|
license: "MIT",
|
|
54
54
|
type: "module",
|
|
@@ -14512,7 +14512,10 @@ var MeResponseSchema = exports_external.object({
|
|
|
14512
14512
|
handle: exports_external.string(),
|
|
14513
14513
|
remaining_usd_today: exports_external.number(),
|
|
14514
14514
|
cap_usd_today: exports_external.number(),
|
|
14515
|
-
tier: AccountTierSchema
|
|
14515
|
+
tier: AccountTierSchema,
|
|
14516
|
+
credit_usd: exports_external.number(),
|
|
14517
|
+
notice: exports_external.string().optional(),
|
|
14518
|
+
buy_url: exports_external.string().optional()
|
|
14516
14519
|
});
|
|
14517
14520
|
var MeStatsResponseSchema = exports_external.object({
|
|
14518
14521
|
user_id: exports_external.string(),
|
|
@@ -14521,6 +14524,7 @@ var MeStatsResponseSchema = exports_external.object({
|
|
|
14521
14524
|
cap_usd_today: exports_external.number(),
|
|
14522
14525
|
spent_usd_today: exports_external.number(),
|
|
14523
14526
|
remaining_usd_today: exports_external.number(),
|
|
14527
|
+
credit_usd: exports_external.number(),
|
|
14524
14528
|
request_count_today: exports_external.number(),
|
|
14525
14529
|
prompt_tokens_today: exports_external.number(),
|
|
14526
14530
|
completion_tokens_today: exports_external.number(),
|
|
@@ -14915,6 +14919,12 @@ function inferErrorFromRemainingBudget(remainingUsdToday) {
|
|
|
14915
14919
|
|
|
14916
14920
|
// ../pi-ads/src/meter.ts
|
|
14917
14921
|
var METER_WIDGET_ID = "freepi-meter";
|
|
14922
|
+
var CONFIRM_TURNS = 3;
|
|
14923
|
+
var lastCreditUsd;
|
|
14924
|
+
var confirm;
|
|
14925
|
+
function creditAddedMessage(addedUsd) {
|
|
14926
|
+
return `Payment received — $${addedUsd.toFixed(2)} of usage added to your balance.`;
|
|
14927
|
+
}
|
|
14918
14928
|
async function renderMeter(deps, ctx) {
|
|
14919
14929
|
const me = await fetchMe(deps);
|
|
14920
14930
|
if (!me) {
|
|
@@ -14922,6 +14932,22 @@ async function renderMeter(deps, ctx) {
|
|
|
14922
14932
|
return;
|
|
14923
14933
|
}
|
|
14924
14934
|
const theme = ctx.ui.theme;
|
|
14935
|
+
const credit = typeof me.credit_usd === "number" ? me.credit_usd : undefined;
|
|
14936
|
+
const added = credit !== undefined && lastCreditUsd !== undefined ? credit - lastCreditUsd : 0;
|
|
14937
|
+
lastCreditUsd = credit;
|
|
14938
|
+
if (added > 0)
|
|
14939
|
+
confirm = { addedUsd: added, turnsLeft: CONFIRM_TURNS };
|
|
14940
|
+
if (confirm && confirm.turnsLeft > 0) {
|
|
14941
|
+
confirm.turnsLeft--;
|
|
14942
|
+
ctx.ui.setWidget(METER_WIDGET_ID, [renderErrorLine(creditAddedMessage(confirm.addedUsd), theme)]);
|
|
14943
|
+
return;
|
|
14944
|
+
}
|
|
14945
|
+
confirm = undefined;
|
|
14946
|
+
const notice = typeof me.notice === "string" ? sanitizeText(me.notice).trim() : "";
|
|
14947
|
+
if (notice !== "") {
|
|
14948
|
+
ctx.ui.setWidget(METER_WIDGET_ID, [renderErrorLine(notice, theme)]);
|
|
14949
|
+
return;
|
|
14950
|
+
}
|
|
14925
14951
|
const errorCode = inferErrorFromRemainingBudget(me.remaining_usd_today);
|
|
14926
14952
|
if (errorCode === undefined) {
|
|
14927
14953
|
ctx.ui.setWidget(METER_WIDGET_ID, undefined);
|
|
@@ -19261,15 +19287,38 @@ __export(exports_typebox, {
|
|
|
19261
19287
|
// src/usage-tool.ts
|
|
19262
19288
|
var USAGE_TOOL_NAME = "free_pi_usage";
|
|
19263
19289
|
var PARAMS = exports_typebox.Object({});
|
|
19290
|
+
function percentUsed(spentUsd, capUsd) {
|
|
19291
|
+
if (!(capUsd > 0))
|
|
19292
|
+
return 100;
|
|
19293
|
+
return Math.min(100, Math.max(0, Math.round(spentUsd / capUsd * 100)));
|
|
19294
|
+
}
|
|
19264
19295
|
function formatStats(stats) {
|
|
19296
|
+
const credit = stats.credit_usd;
|
|
19265
19297
|
return [
|
|
19266
|
-
`tier: ${stats.tier}
|
|
19267
|
-
`today:
|
|
19298
|
+
`tier: ${stats.tier}`,
|
|
19299
|
+
`today: ${percentUsed(stats.spent_usd_today, stats.cap_usd_today)}% of your free daily allowance used, ${stats.request_count_today} request(s)`,
|
|
19300
|
+
...typeof credit === "number" ? [`credit: $${credit.toFixed(2)} purchased usage remaining`] : [],
|
|
19268
19301
|
`today tokens: ${stats.prompt_tokens_today} prompt / ${stats.completion_tokens_today} completion`,
|
|
19269
|
-
`lifetime:
|
|
19302
|
+
`lifetime: ${stats.lifetime.request_count} request(s), ${stats.lifetime.prompt_tokens} prompt / ${stats.lifetime.completion_tokens} completion tokens`
|
|
19270
19303
|
].join(`
|
|
19271
19304
|
`);
|
|
19272
19305
|
}
|
|
19306
|
+
async function fetchUsageText(opts) {
|
|
19307
|
+
try {
|
|
19308
|
+
const token = await opts.getToken();
|
|
19309
|
+
const doFetch = opts.fetchImpl ?? fetch;
|
|
19310
|
+
const res = await doFetch(`${opts.baseUrl}/me/stats`, {
|
|
19311
|
+
headers: { Authorization: `Bearer ${token}` },
|
|
19312
|
+
signal: AbortSignal.timeout(5000)
|
|
19313
|
+
});
|
|
19314
|
+
if (!res.ok)
|
|
19315
|
+
return { ok: false, text: `Could not fetch usage (HTTP ${res.status}).` };
|
|
19316
|
+
const stats = await res.json();
|
|
19317
|
+
return { ok: true, text: formatStats(stats), stats };
|
|
19318
|
+
} catch (err) {
|
|
19319
|
+
return { ok: false, text: `Could not fetch usage: ${String(err)}` };
|
|
19320
|
+
}
|
|
19321
|
+
}
|
|
19273
19322
|
function createUsageToolExtension(opts) {
|
|
19274
19323
|
return {
|
|
19275
19324
|
name: "free-pi-usage-tool",
|
|
@@ -19277,40 +19326,121 @@ function createUsageToolExtension(opts) {
|
|
|
19277
19326
|
pi.registerTool({
|
|
19278
19327
|
name: USAGE_TOOL_NAME,
|
|
19279
19328
|
label: "My Usage",
|
|
19280
|
-
description: "Reports the caller's
|
|
19281
|
-
promptSnippet: "Check your own free-pi usage
|
|
19329
|
+
description: "Reports how much of the caller's free daily allowance is used (as a percentage), purchased credit, token counts, and account tier. Read-only, takes no arguments, makes no changes.",
|
|
19330
|
+
promptSnippet: "Check your own free-pi usage and remaining daily allowance",
|
|
19282
19331
|
parameters: PARAMS,
|
|
19283
19332
|
async execute() {
|
|
19284
|
-
|
|
19285
|
-
|
|
19286
|
-
|
|
19287
|
-
|
|
19288
|
-
|
|
19289
|
-
|
|
19290
|
-
|
|
19291
|
-
|
|
19292
|
-
|
|
19293
|
-
|
|
19294
|
-
|
|
19295
|
-
|
|
19296
|
-
|
|
19297
|
-
|
|
19298
|
-
|
|
19299
|
-
|
|
19300
|
-
|
|
19301
|
-
|
|
19302
|
-
|
|
19333
|
+
const r = await fetchUsageText(opts);
|
|
19334
|
+
return {
|
|
19335
|
+
content: [{ type: "text", text: r.text }],
|
|
19336
|
+
details: r.ok ? r.stats : { ok: false }
|
|
19337
|
+
};
|
|
19338
|
+
}
|
|
19339
|
+
});
|
|
19340
|
+
}
|
|
19341
|
+
};
|
|
19342
|
+
}
|
|
19343
|
+
|
|
19344
|
+
// src/buy-tool.ts
|
|
19345
|
+
async function resolveBuyPage(opts) {
|
|
19346
|
+
try {
|
|
19347
|
+
const token = await opts.getToken();
|
|
19348
|
+
const doFetch = opts.fetchImpl ?? fetch;
|
|
19349
|
+
const res = await doFetch(`${opts.baseUrl}/me`, {
|
|
19350
|
+
headers: { Authorization: `Bearer ${token}` },
|
|
19351
|
+
signal: AbortSignal.timeout(5000)
|
|
19352
|
+
});
|
|
19353
|
+
if (!res.ok)
|
|
19354
|
+
return { kind: "error", text: `Could not fetch the buy page (HTTP ${res.status}).` };
|
|
19355
|
+
const me = await res.json();
|
|
19356
|
+
return me.buy_url ? { kind: "url", url: me.buy_url } : { kind: "unavailable" };
|
|
19357
|
+
} catch (err) {
|
|
19358
|
+
return { kind: "error", text: `Could not fetch the buy page: ${String(err)}` };
|
|
19359
|
+
}
|
|
19360
|
+
}
|
|
19361
|
+
var BUY_NOT_AVAILABLE_TEXT = "Buying credits is not available on this server yet.";
|
|
19362
|
+
function buyPageText(url2) {
|
|
19363
|
+
return `Opening the buy page in your browser. If it did not open (for example over SSH), visit:
|
|
19364
|
+
${url2}
|
|
19365
|
+
|
|
19366
|
+
Packs: $5 → $4.00 of usage, $10 → $8.00. Card or USDC.`;
|
|
19367
|
+
}
|
|
19368
|
+
var BUY_TOOL_NAME = "free_pi_buy_credits";
|
|
19369
|
+
var PARAMS2 = exports_typebox.Object({});
|
|
19370
|
+
function createBuyToolExtension(opts) {
|
|
19371
|
+
return {
|
|
19372
|
+
name: "free-pi-buy-tool",
|
|
19373
|
+
factory(pi) {
|
|
19374
|
+
pi.registerTool({
|
|
19375
|
+
name: BUY_TOOL_NAME,
|
|
19376
|
+
label: "Buy Credits",
|
|
19377
|
+
description: "Opens the free-pi buy-credits page in your browser (and prints the URL, for SSH sessions with no browser). Read-only against the server, takes no arguments.",
|
|
19378
|
+
promptSnippet: "Buy more free-pi credits",
|
|
19379
|
+
parameters: PARAMS2,
|
|
19380
|
+
async execute() {
|
|
19381
|
+
const page = await resolveBuyPage(opts);
|
|
19382
|
+
if (page.kind === "error") {
|
|
19383
|
+
return { content: [{ type: "text", text: page.text }], details: { ok: false } };
|
|
19384
|
+
}
|
|
19385
|
+
if (page.kind === "unavailable") {
|
|
19303
19386
|
return {
|
|
19304
|
-
content: [{ type: "text", text:
|
|
19305
|
-
details: {
|
|
19387
|
+
content: [{ type: "text", text: BUY_NOT_AVAILABLE_TEXT }],
|
|
19388
|
+
details: { available: false }
|
|
19306
19389
|
};
|
|
19307
19390
|
}
|
|
19391
|
+
(opts.openBrowserImpl ?? openBrowser)(page.url);
|
|
19392
|
+
return {
|
|
19393
|
+
content: [{ type: "text", text: buyPageText(page.url) }],
|
|
19394
|
+
details: { buy_url: page.url }
|
|
19395
|
+
};
|
|
19308
19396
|
}
|
|
19309
19397
|
});
|
|
19310
19398
|
}
|
|
19311
19399
|
};
|
|
19312
19400
|
}
|
|
19313
19401
|
|
|
19402
|
+
// src/error-notice.ts
|
|
19403
|
+
var NOTICE_DEDUPE_MS = 15000;
|
|
19404
|
+
function parseErrorEnvelope(errorMessage) {
|
|
19405
|
+
if (typeof errorMessage !== "string")
|
|
19406
|
+
return;
|
|
19407
|
+
const m = /^(\d{3}): (\{[\s\S]*\})\s*$/.exec(errorMessage);
|
|
19408
|
+
if (!m)
|
|
19409
|
+
return;
|
|
19410
|
+
try {
|
|
19411
|
+
const body = JSON.parse(m[2]);
|
|
19412
|
+
const message = body.message ?? body.error?.message;
|
|
19413
|
+
const code = body.code ?? body.error?.code;
|
|
19414
|
+
if (typeof message !== "string" || message.trim() === "")
|
|
19415
|
+
return;
|
|
19416
|
+
return { status: Number(m[1]), code: typeof code === "string" ? code : "unknown", message };
|
|
19417
|
+
} catch {
|
|
19418
|
+
return;
|
|
19419
|
+
}
|
|
19420
|
+
}
|
|
19421
|
+
function createErrorNoticeExtension(opts = {}) {
|
|
19422
|
+
const lastAt = new Map;
|
|
19423
|
+
const now = opts.now ?? Date.now;
|
|
19424
|
+
return {
|
|
19425
|
+
name: "free-pi-error-notice",
|
|
19426
|
+
factory(pi) {
|
|
19427
|
+
pi.on("turn_end", (event, ctx) => {
|
|
19428
|
+
const msg = event.message;
|
|
19429
|
+
if (!msg || msg.stopReason !== "error")
|
|
19430
|
+
return;
|
|
19431
|
+
const env = parseErrorEnvelope(msg.errorMessage);
|
|
19432
|
+
if (!env)
|
|
19433
|
+
return;
|
|
19434
|
+
const t = now();
|
|
19435
|
+
if (t - (lastAt.get(env.code) ?? -Infinity) < NOTICE_DEDUPE_MS)
|
|
19436
|
+
return;
|
|
19437
|
+
lastAt.set(env.code, t);
|
|
19438
|
+
ctx.ui.notify(env.message, env.status >= 500 ? "error" : "warning");
|
|
19439
|
+
});
|
|
19440
|
+
}
|
|
19441
|
+
};
|
|
19442
|
+
}
|
|
19443
|
+
|
|
19314
19444
|
// src/tool-guard.ts
|
|
19315
19445
|
async function reportToolGuardTrigger(opts, toolName) {
|
|
19316
19446
|
try {
|
|
@@ -19340,6 +19470,21 @@ function createToolGuardExtension(opts) {
|
|
|
19340
19470
|
|
|
19341
19471
|
// src/changelog.ts
|
|
19342
19472
|
var CHANGELOG_HIGHLIGHTS = [
|
|
19473
|
+
{
|
|
19474
|
+
version: "0.2.12",
|
|
19475
|
+
highlights: [
|
|
19476
|
+
"New: /buy-credits and /usage slash commands — work instantly, even when you are out of usage.",
|
|
19477
|
+
"Free usage is shown as a percentage, never a dollar figure; purchased credit stays in dollars.",
|
|
19478
|
+
"Clearer messages: one readable line when a request is rejected, a heads-up at 80% of your daily allowance, and a confirmation when a purchase lands."
|
|
19479
|
+
]
|
|
19480
|
+
},
|
|
19481
|
+
{
|
|
19482
|
+
version: "0.2.11",
|
|
19483
|
+
highlights: [
|
|
19484
|
+
'Out of free usage? You can now buy more — $5 or $10 packs, card or USDC. Ask pi to "buy credits" or follow the link in the out-of-usage notice.',
|
|
19485
|
+
"The out-of-usage notice now comes from the server, and `free_pi_usage` shows your purchased balance."
|
|
19486
|
+
]
|
|
19487
|
+
},
|
|
19343
19488
|
{
|
|
19344
19489
|
version: "0.2.10",
|
|
19345
19490
|
highlights: [
|
|
@@ -19451,6 +19596,19 @@ async function runUpdate(ctx, deps = {}) {
|
|
|
19451
19596
|
ctx.ui.notify("Update failed. Try manually: npm install -g free-pi-cli@latest", "error");
|
|
19452
19597
|
}
|
|
19453
19598
|
}
|
|
19599
|
+
async function buyCredits(opts, ctx) {
|
|
19600
|
+
const page = await resolveBuyPage(opts);
|
|
19601
|
+
if (page.kind === "error")
|
|
19602
|
+
return ctx.ui.notify(page.text, "error");
|
|
19603
|
+
if (page.kind === "unavailable")
|
|
19604
|
+
return ctx.ui.notify(BUY_NOT_AVAILABLE_TEXT, "info");
|
|
19605
|
+
(opts.openBrowserImpl ?? openBrowser)(page.url);
|
|
19606
|
+
ctx.ui.notify(buyPageText(page.url), "info");
|
|
19607
|
+
}
|
|
19608
|
+
async function showUsage(opts, ctx) {
|
|
19609
|
+
const r = await fetchUsageText(opts);
|
|
19610
|
+
ctx.ui.notify(r.text, r.ok ? "info" : "error");
|
|
19611
|
+
}
|
|
19454
19612
|
function createFreePiCommandsExtension(opts) {
|
|
19455
19613
|
return {
|
|
19456
19614
|
name: "free-pi-commands",
|
|
@@ -19467,6 +19625,14 @@ function createFreePiCommandsExtension(opts) {
|
|
|
19467
19625
|
description: "Update free-pi-cli to the latest version.",
|
|
19468
19626
|
handler: (_args, ctx) => runUpdate(ctx)
|
|
19469
19627
|
});
|
|
19628
|
+
pi.registerCommand("usage", {
|
|
19629
|
+
description: "Show how much of today's free allowance you have used, and your purchased credit.",
|
|
19630
|
+
handler: (_args, ctx) => showUsage(opts, ctx)
|
|
19631
|
+
});
|
|
19632
|
+
pi.registerCommand("buy-credits", {
|
|
19633
|
+
description: "Buy more usage ($5 or $10 packs, card or USDC) — opens the buy page.",
|
|
19634
|
+
handler: (_args, ctx) => buyCredits(opts, ctx)
|
|
19635
|
+
});
|
|
19470
19636
|
}
|
|
19471
19637
|
};
|
|
19472
19638
|
}
|
|
@@ -19491,7 +19657,7 @@ function catalogModelsFor(opts) {
|
|
|
19491
19657
|
const id = opts.model || MODEL_ID;
|
|
19492
19658
|
return [{ id, name: id }];
|
|
19493
19659
|
}
|
|
19494
|
-
var ALLOWED_TOOL_NAMES = [...SAFE_TOOLS, USAGE_TOOL_NAME];
|
|
19660
|
+
var ALLOWED_TOOL_NAMES = [...SAFE_TOOLS, USAGE_TOOL_NAME, BUY_TOOL_NAME];
|
|
19495
19661
|
function buildRuntimeOptions(opts, sessionId) {
|
|
19496
19662
|
const models = catalogModelsFor(opts);
|
|
19497
19663
|
const providerExtension = {
|
|
@@ -19509,6 +19675,11 @@ function buildRuntimeOptions(opts, sessionId) {
|
|
|
19509
19675
|
baseUrl: opts.baseUrl,
|
|
19510
19676
|
getToken: () => opts.jwt
|
|
19511
19677
|
});
|
|
19678
|
+
const buyToolExtension = createBuyToolExtension({
|
|
19679
|
+
baseUrl: opts.baseUrl,
|
|
19680
|
+
getToken: () => opts.jwt
|
|
19681
|
+
});
|
|
19682
|
+
const errorNoticeExtension = createErrorNoticeExtension();
|
|
19512
19683
|
const toolGuardExtension = createToolGuardExtension({
|
|
19513
19684
|
baseUrl: opts.baseUrl,
|
|
19514
19685
|
getToken: () => opts.jwt,
|
|
@@ -19522,6 +19693,8 @@ function buildRuntimeOptions(opts, sessionId) {
|
|
|
19522
19693
|
providerExtension,
|
|
19523
19694
|
adsExtension,
|
|
19524
19695
|
usageToolExtension,
|
|
19696
|
+
buyToolExtension,
|
|
19697
|
+
errorNoticeExtension,
|
|
19525
19698
|
toolGuardExtension,
|
|
19526
19699
|
commandsExtension
|
|
19527
19700
|
];
|