free-pi-cli 0.3.0 → 0.4.1
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-376chsck.js → main-r8jqgh5n.js} +708 -44
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -44,7 +44,7 @@ function openBrowser(url, platform = process.platform, spawnImpl = spawn) {
|
|
|
44
44
|
// package.json
|
|
45
45
|
var package_default = {
|
|
46
46
|
name: "free-pi-cli",
|
|
47
|
-
version: "0.
|
|
47
|
+
version: "0.4.1",
|
|
48
48
|
description: "Free, ad-supported distribution of the pi coding agent. Inference paid for by ads.",
|
|
49
49
|
license: "MIT",
|
|
50
50
|
type: "module",
|
|
@@ -83,6 +83,8 @@ Usage:
|
|
|
83
83
|
free-pi-cli start the coding agent (first run: consent + GitHub sign-in)
|
|
84
84
|
free-pi-cli --session <id> resume a previous session
|
|
85
85
|
free-pi-cli logout remove the stored sign-in token; the next run signs in again
|
|
86
|
+
free-pi-cli mcp <command> add, remove, list, or sign in to your own MCP servers
|
|
87
|
+
(free-pi-cli mcp --help for details)
|
|
86
88
|
free-pi-cli --version print the CLI version
|
|
87
89
|
free-pi-cli --help show this help
|
|
88
90
|
|
|
@@ -97,6 +99,8 @@ function parseCliArgs(argv) {
|
|
|
97
99
|
const [first, ...rest] = argv;
|
|
98
100
|
if (first === undefined)
|
|
99
101
|
return { kind: "run" };
|
|
102
|
+
if (first === "mcp")
|
|
103
|
+
return { kind: "mcp", args: rest };
|
|
100
104
|
if (first === "--session") {
|
|
101
105
|
return rest.length > 1 ? { kind: "unknown", arg: rest[1] } : { kind: "run" };
|
|
102
106
|
}
|
|
@@ -163,21 +167,58 @@ async function runLogout(deps) {
|
|
|
163
167
|
return 0;
|
|
164
168
|
}
|
|
165
169
|
|
|
170
|
+
// src/mcp-command.ts
|
|
171
|
+
import { existsSync } from "node:fs";
|
|
172
|
+
import { dirname as dirname2, join } from "node:path";
|
|
173
|
+
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
174
|
+
function piMcpCliPath() {
|
|
175
|
+
return join(dirname2(fileURLToPath(import.meta.resolve("@earendil-works/pi-coding-agent"))), "extensions", "mcp", "cli.js");
|
|
176
|
+
}
|
|
177
|
+
async function loadPiRunner() {
|
|
178
|
+
const path = piMcpCliPath();
|
|
179
|
+
if (!existsSync(path)) {
|
|
180
|
+
throw new Error(`free-pi-cli mcp is unavailable: the bundled pi package has no ${path}. Reinstall free-pi-cli.`);
|
|
181
|
+
}
|
|
182
|
+
const mod = await import(pathToFileURL(path).href);
|
|
183
|
+
if (typeof mod.runMcpCommand !== "function") {
|
|
184
|
+
throw new Error(`free-pi-cli mcp is unavailable: ${path} does not export runMcpCommand. Reinstall free-pi-cli.`);
|
|
185
|
+
}
|
|
186
|
+
return mod.runMcpCommand;
|
|
187
|
+
}
|
|
188
|
+
function brandMcpLine(line) {
|
|
189
|
+
return line.replace(/\bpi (?=mcp\b)/g, "free-pi-cli ").replace(/~\/\.pi\/agent\b/g, "~/.free-pi/agent").replace(/\b([Ss]tart) pi\b/g, "$1 free-pi-cli");
|
|
190
|
+
}
|
|
191
|
+
async function runFreePiMcp(args, deps) {
|
|
192
|
+
try {
|
|
193
|
+
const run = await (deps.loadRunner ?? loadPiRunner)();
|
|
194
|
+
process.env.PI_CODING_AGENT_DIR = deps.agentDir;
|
|
195
|
+
return await run(args, {
|
|
196
|
+
cwd: deps.cwd,
|
|
197
|
+
agentDir: deps.agentDir,
|
|
198
|
+
log: (line) => deps.log(brandMcpLine(line)),
|
|
199
|
+
error: (line) => deps.error(brandMcpLine(line))
|
|
200
|
+
});
|
|
201
|
+
} catch (err) {
|
|
202
|
+
deps.error(err instanceof Error ? err.message : String(err));
|
|
203
|
+
return 1;
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
|
|
166
207
|
// src/paths.ts
|
|
167
208
|
import { homedir } from "node:os";
|
|
168
|
-
import { join } from "node:path";
|
|
209
|
+
import { join as join2 } from "node:path";
|
|
169
210
|
var FREE_PI_DIR_NAME = ".free-pi";
|
|
170
211
|
function getFreePiAgentDir() {
|
|
171
|
-
return
|
|
212
|
+
return join2(homedir(), FREE_PI_DIR_NAME, "agent");
|
|
172
213
|
}
|
|
173
214
|
function getCredentialsPath(agentDir = getFreePiAgentDir()) {
|
|
174
|
-
return
|
|
215
|
+
return join2(agentDir, "credentials.json");
|
|
175
216
|
}
|
|
176
217
|
function getLastSeenVersionPath(agentDir = getFreePiAgentDir()) {
|
|
177
|
-
return
|
|
218
|
+
return join2(agentDir, "last-seen-version");
|
|
178
219
|
}
|
|
179
220
|
function getConfigPath() {
|
|
180
|
-
return
|
|
221
|
+
return join2(homedir(), FREE_PI_DIR_NAME, "config.json");
|
|
181
222
|
}
|
|
182
223
|
|
|
183
224
|
// src/pi-launch.ts
|
|
@@ -185,6 +226,9 @@ import {
|
|
|
185
226
|
createAgentSessionFromServices,
|
|
186
227
|
createAgentSessionRuntime,
|
|
187
228
|
createAgentSessionServices,
|
|
229
|
+
createCodemodeExtension,
|
|
230
|
+
createMcpExtension,
|
|
231
|
+
createToolSearchExtension,
|
|
188
232
|
InteractiveMode,
|
|
189
233
|
SessionManager,
|
|
190
234
|
SettingsManager
|
|
@@ -4776,6 +4820,7 @@ var AuthConsentRequestSchema = object({
|
|
|
4776
4820
|
consent_version: string2()
|
|
4777
4821
|
});
|
|
4778
4822
|
var AccountTierSchema = _enum(["new", "young", "established"]);
|
|
4823
|
+
var UsageLaneSchema = _enum(["standard", "fallback", "wait"]);
|
|
4779
4824
|
var MeResponseSchema = object({
|
|
4780
4825
|
user_id: string2(),
|
|
4781
4826
|
handle: string2(),
|
|
@@ -4784,7 +4829,12 @@ var MeResponseSchema = object({
|
|
|
4784
4829
|
tier: AccountTierSchema,
|
|
4785
4830
|
credit_usd: number2(),
|
|
4786
4831
|
notice: string2().optional(),
|
|
4787
|
-
buy_url: string2().optional()
|
|
4832
|
+
buy_url: string2().optional(),
|
|
4833
|
+
allowance_credits: number2().optional(),
|
|
4834
|
+
remaining_credits_today: number2().optional(),
|
|
4835
|
+
refill_at: string2().optional(),
|
|
4836
|
+
lane: UsageLaneSchema.optional(),
|
|
4837
|
+
bonus_credits: number2().optional()
|
|
4788
4838
|
});
|
|
4789
4839
|
var MeStatsResponseSchema = object({
|
|
4790
4840
|
user_id: string2(),
|
|
@@ -4795,6 +4845,11 @@ var MeStatsResponseSchema = object({
|
|
|
4795
4845
|
remaining_usd_today: number2(),
|
|
4796
4846
|
credit_usd: number2(),
|
|
4797
4847
|
credit_credits: number2(),
|
|
4848
|
+
allowance_credits: number2(),
|
|
4849
|
+
remaining_credits_today: number2(),
|
|
4850
|
+
refill_at: string2(),
|
|
4851
|
+
lane: UsageLaneSchema,
|
|
4852
|
+
bonus_credits: number2(),
|
|
4798
4853
|
request_count_today: number2(),
|
|
4799
4854
|
prompt_tokens_today: number2(),
|
|
4800
4855
|
completion_tokens_today: number2(),
|
|
@@ -4868,7 +4923,7 @@ function assertExhaustiveErrorCode(code) {
|
|
|
4868
4923
|
}
|
|
4869
4924
|
// ../shared/src/config.ts
|
|
4870
4925
|
var ConfigSchema = object({
|
|
4871
|
-
perUserDailyCapUsd: number2().default(
|
|
4926
|
+
perUserDailyCapUsd: number2().default(1.125),
|
|
4872
4927
|
perUserRollingCapUsd: number2().default(0),
|
|
4873
4928
|
rollingCapWindowHours: number2().int().default(5),
|
|
4874
4929
|
globalDailyCapUsd: number2().default(50),
|
|
@@ -4892,8 +4947,12 @@ var ConfigSchema = object({
|
|
|
4892
4947
|
providerOrder: array(string2()).optional(),
|
|
4893
4948
|
tierNewMaxDays: number2().int().default(30),
|
|
4894
4949
|
tierYoungMaxDays: number2().int().default(365),
|
|
4895
|
-
tierCapNewUsd: number2().default(0.
|
|
4896
|
-
tierCapYoungUsd: number2().default(
|
|
4950
|
+
tierCapNewUsd: number2().default(0.45),
|
|
4951
|
+
tierCapYoungUsd: number2().default(1.125),
|
|
4952
|
+
fallbackLaneUsd: number2().default(0.225),
|
|
4953
|
+
fallbackHourlyCap: number2().int().default(10),
|
|
4954
|
+
fallbackLaneMinHeadroomBps: number2().int().default(1000),
|
|
4955
|
+
queueWaitRetryAfterSeconds: number2().int().default(0),
|
|
4897
4956
|
sessionLeaseTtlMinutes: number2().int().default(30),
|
|
4898
4957
|
minCliVersion: string2().default("0.0.0"),
|
|
4899
4958
|
latestCliVersion: string2().default("0.2.0"),
|
|
@@ -4962,6 +5021,10 @@ function loadConfig(env = process.env) {
|
|
|
4962
5021
|
tierYoungMaxDays: envNumber(env.TIER_YOUNG_MAX_DAYS),
|
|
4963
5022
|
tierCapNewUsd: envNumber(env.TIER_CAP_NEW_USD),
|
|
4964
5023
|
tierCapYoungUsd: envNumber(env.TIER_CAP_YOUNG_USD),
|
|
5024
|
+
fallbackLaneUsd: envNumber(env.FALLBACK_LANE_USD),
|
|
5025
|
+
fallbackHourlyCap: envNumber(env.FALLBACK_HOURLY_CAP),
|
|
5026
|
+
fallbackLaneMinHeadroomBps: envNumber(env.FALLBACK_LANE_MIN_HEADROOM_BPS),
|
|
5027
|
+
queueWaitRetryAfterSeconds: envNumber(env.QUEUE_WAIT_RETRY_AFTER_SECONDS),
|
|
4965
5028
|
sessionLeaseTtlMinutes: envNumber(env.SESSION_LEASE_TTL_MINUTES),
|
|
4966
5029
|
minCliVersion: env.MIN_CLI_VERSION || undefined,
|
|
4967
5030
|
latestCliVersion: env.LATEST_CLI_VERSION || undefined,
|
|
@@ -5007,6 +5070,21 @@ var CREDITS_PER_USD = 40000 / 3;
|
|
|
5007
5070
|
function usdToCredits(usd) {
|
|
5008
5071
|
return Math.round(Number(usd) * CREDITS_PER_USD);
|
|
5009
5072
|
}
|
|
5073
|
+
function formatRefillEt(date, timeZone = "America/New_York") {
|
|
5074
|
+
const formatted = new Intl.DateTimeFormat("en-US", {
|
|
5075
|
+
timeZone,
|
|
5076
|
+
hour: "numeric",
|
|
5077
|
+
minute: "2-digit",
|
|
5078
|
+
hour12: true
|
|
5079
|
+
}).format(date);
|
|
5080
|
+
return `${formatted} ET`;
|
|
5081
|
+
}
|
|
5082
|
+
function formatCreditsLeftLine(remaining, allowance, refillAt) {
|
|
5083
|
+
const left = remaining.toLocaleString("en-US");
|
|
5084
|
+
const total = allowance.toLocaleString("en-US");
|
|
5085
|
+
return `${left} / ${total} credits left today · refills ${formatRefillEt(refillAt)}`;
|
|
5086
|
+
}
|
|
5087
|
+
var SLOW_LANE_METER_LINE = "Slow lane: cheapest model until refill";
|
|
5010
5088
|
// ../pi-ads/src/api.ts
|
|
5011
5089
|
var REQUEST_TIMEOUT_MS = 3000;
|
|
5012
5090
|
async function authedFetch(deps, path, init) {
|
|
@@ -5113,6 +5191,9 @@ function renderPlainAdLine(creative, clickUrl, theme, columns) {
|
|
|
5113
5191
|
function renderErrorLine(message, theme) {
|
|
5114
5192
|
return theme.fg("error", message);
|
|
5115
5193
|
}
|
|
5194
|
+
function renderWarningLine(message, theme) {
|
|
5195
|
+
return theme.fg("warning", message);
|
|
5196
|
+
}
|
|
5116
5197
|
|
|
5117
5198
|
// ../pi-ads/src/banner.ts
|
|
5118
5199
|
var BANNER_WIDGET_ID = "freepi-ad";
|
|
@@ -5180,7 +5261,9 @@ function createInlineRenderer() {
|
|
|
5180
5261
|
function mapErrorCode(code) {
|
|
5181
5262
|
switch (code) {
|
|
5182
5263
|
case "daily_cap":
|
|
5183
|
-
return "out of free
|
|
5264
|
+
return "out of free credits today — refills at UTC midnight";
|
|
5265
|
+
case "queue_wait":
|
|
5266
|
+
return "free-pi is near its daily budget — wait and retry";
|
|
5184
5267
|
case "rolling_cap":
|
|
5185
5268
|
return "over the rolling spend cap on the free tier — wait for the Retry-After window";
|
|
5186
5269
|
case "concurrent":
|
|
@@ -5213,6 +5296,13 @@ function inferErrorFromRemainingBudget(remainingUsdToday) {
|
|
|
5213
5296
|
|
|
5214
5297
|
// ../pi-ads/src/meter.ts
|
|
5215
5298
|
var METER_WIDGET_ID = "freepi-meter";
|
|
5299
|
+
var METER_STATUS_KEY = "freepi-credits";
|
|
5300
|
+
var LOW_CREDIT_FRACTION = 0.1;
|
|
5301
|
+
var BUY_LINK_TEXT = "freepi.ai/buy";
|
|
5302
|
+
function buyMore(buyUrl) {
|
|
5303
|
+
const url = typeof buyUrl === "string" ? sanitizeText(buyUrl) : "";
|
|
5304
|
+
return url !== "" ? ` · Buy more: ${osc8(BUY_LINK_TEXT, url)}` : "";
|
|
5305
|
+
}
|
|
5216
5306
|
var CONFIRM_TURNS = 3;
|
|
5217
5307
|
var lastCreditUsd;
|
|
5218
5308
|
var confirm;
|
|
@@ -5221,6 +5311,7 @@ function creditAddedMessage(addedUsd) {
|
|
|
5221
5311
|
}
|
|
5222
5312
|
async function renderMeter(deps, ctx) {
|
|
5223
5313
|
const me = await fetchMe(deps);
|
|
5314
|
+
ctx.ui.setStatus(METER_STATUS_KEY, undefined);
|
|
5224
5315
|
if (!me) {
|
|
5225
5316
|
ctx.ui.setWidget(METER_WIDGET_ID, undefined);
|
|
5226
5317
|
return;
|
|
@@ -5242,6 +5333,24 @@ async function renderMeter(deps, ctx) {
|
|
|
5242
5333
|
ctx.ui.setWidget(METER_WIDGET_ID, [renderErrorLine(notice, theme)]);
|
|
5243
5334
|
return;
|
|
5244
5335
|
}
|
|
5336
|
+
if (me.lane === "fallback") {
|
|
5337
|
+
ctx.ui.setStatus(METER_STATUS_KEY, theme.fg("muted", "0 credits · slow lane"));
|
|
5338
|
+
ctx.ui.setWidget(METER_WIDGET_ID, [
|
|
5339
|
+
renderWarningLine(`Out of free credits — slow lane until refill${buyMore(me.buy_url)}`, theme)
|
|
5340
|
+
]);
|
|
5341
|
+
return;
|
|
5342
|
+
}
|
|
5343
|
+
if (typeof me.remaining_credits_today === "number" && typeof me.allowance_credits === "number" && typeof me.refill_at === "string") {
|
|
5344
|
+
const refill = new Date(me.refill_at);
|
|
5345
|
+
if (!Number.isNaN(refill.getTime()) && me.lane !== "wait") {
|
|
5346
|
+
const left = me.remaining_credits_today;
|
|
5347
|
+
const bought = typeof me.bonus_credits === "number" && me.bonus_credits > 0 ? ` + ${me.bonus_credits.toLocaleString("en-US")}` : "";
|
|
5348
|
+
ctx.ui.setStatus(METER_STATUS_KEY, theme.fg("muted", `${left.toLocaleString("en-US")}${bought} credits`));
|
|
5349
|
+
const low = left > 0 && left <= me.allowance_credits * LOW_CREDIT_FRACTION;
|
|
5350
|
+
ctx.ui.setWidget(METER_WIDGET_ID, low ? [renderWarningLine(`Low on credits: ${left.toLocaleString("en-US")} left today${buyMore(me.buy_url)}`, theme)] : undefined);
|
|
5351
|
+
return;
|
|
5352
|
+
}
|
|
5353
|
+
}
|
|
5245
5354
|
const errorCode = inferErrorFromRemainingBudget(me.remaining_usd_today);
|
|
5246
5355
|
if (errorCode === undefined) {
|
|
5247
5356
|
ctx.ui.setWidget(METER_WIDGET_ID, undefined);
|
|
@@ -5290,7 +5399,7 @@ function createAdsExtension(opts) {
|
|
|
5290
5399
|
}
|
|
5291
5400
|
|
|
5292
5401
|
// src/pi-launch.ts
|
|
5293
|
-
import { join as
|
|
5402
|
+
import { join as join3, resolve as resolve2 } from "node:path";
|
|
5294
5403
|
|
|
5295
5404
|
// src/provider.ts
|
|
5296
5405
|
var PROVIDER_NAME = "free-pi";
|
|
@@ -5328,15 +5437,38 @@ var Metrics = {
|
|
|
5328
5437
|
update: 0
|
|
5329
5438
|
};
|
|
5330
5439
|
// ../../node_modules/.bun/typebox@1.3.16/node_modules/typebox/build/guard/guard.mjs
|
|
5440
|
+
function IsArray(value) {
|
|
5441
|
+
return Array.isArray(value);
|
|
5442
|
+
}
|
|
5443
|
+
function IsBigInt(value) {
|
|
5444
|
+
return IsEqual(typeof value, "bigint");
|
|
5445
|
+
}
|
|
5446
|
+
function IsBoolean(value) {
|
|
5447
|
+
return IsEqual(typeof value, "boolean");
|
|
5448
|
+
}
|
|
5331
5449
|
function IsNull(value) {
|
|
5332
5450
|
return IsEqual(value, null);
|
|
5333
5451
|
}
|
|
5452
|
+
function IsNumber(value) {
|
|
5453
|
+
return Number.isFinite(value);
|
|
5454
|
+
}
|
|
5334
5455
|
function IsObject(value) {
|
|
5335
5456
|
return IsEqual(typeof value, "object") && !IsNull(value);
|
|
5336
5457
|
}
|
|
5458
|
+
function IsString(value) {
|
|
5459
|
+
return IsEqual(typeof value, "string");
|
|
5460
|
+
}
|
|
5337
5461
|
function IsEqual(left, right) {
|
|
5338
5462
|
return left === right;
|
|
5339
5463
|
}
|
|
5464
|
+
function IsClassInstance(value) {
|
|
5465
|
+
if (!IsObject(value))
|
|
5466
|
+
return false;
|
|
5467
|
+
const proto = globalThis.Object.getPrototypeOf(value);
|
|
5468
|
+
if (IsNull(proto))
|
|
5469
|
+
return false;
|
|
5470
|
+
return IsEqual(typeof proto.constructor, "function") && !(IsEqual(proto.constructor, globalThis.Object) || IsEqual(proto.constructor.name, "Object"));
|
|
5471
|
+
}
|
|
5340
5472
|
function IsUnsafePropertyKey(key) {
|
|
5341
5473
|
return IsEqual(key, "__proto__") || IsEqual(key, "constructor") || IsEqual(key, "prototype");
|
|
5342
5474
|
}
|
|
@@ -5346,6 +5478,81 @@ function HasPropertyKey(value, key) {
|
|
|
5346
5478
|
function Keys(value) {
|
|
5347
5479
|
return Object.getOwnPropertyNames(value);
|
|
5348
5480
|
}
|
|
5481
|
+
function Symbols(value) {
|
|
5482
|
+
return Object.getOwnPropertySymbols(value);
|
|
5483
|
+
}
|
|
5484
|
+
// ../../node_modules/.bun/typebox@1.3.16/node_modules/typebox/build/guard/globals.mjs
|
|
5485
|
+
function IsTypeArray(value) {
|
|
5486
|
+
return globalThis.ArrayBuffer.isView(value);
|
|
5487
|
+
}
|
|
5488
|
+
function IsRegExp(value) {
|
|
5489
|
+
return value instanceof globalThis.RegExp;
|
|
5490
|
+
}
|
|
5491
|
+
function IsSet(value) {
|
|
5492
|
+
return value instanceof globalThis.Set;
|
|
5493
|
+
}
|
|
5494
|
+
function IsMap(value) {
|
|
5495
|
+
return value instanceof globalThis.Map;
|
|
5496
|
+
}
|
|
5497
|
+
// ../../node_modules/.bun/typebox@1.3.16/node_modules/typebox/build/system/memory/clone.mjs
|
|
5498
|
+
function FromClassInstance(value) {
|
|
5499
|
+
return value;
|
|
5500
|
+
}
|
|
5501
|
+
function IsSchemaObject(value) {
|
|
5502
|
+
return HasPropertyKey(value, "~kind") || HasPropertyKey(value, "~unsafe");
|
|
5503
|
+
}
|
|
5504
|
+
function FromSchemaObject(value) {
|
|
5505
|
+
const result = {};
|
|
5506
|
+
for (const key of Keys(value)) {
|
|
5507
|
+
if (IsUnsafePropertyKey(key))
|
|
5508
|
+
continue;
|
|
5509
|
+
const descriptor = Object.getOwnPropertyDescriptor(value, key);
|
|
5510
|
+
descriptor.value = FromValue(descriptor.value);
|
|
5511
|
+
if (IsEqual(descriptor.enumerable, true)) {
|
|
5512
|
+
result[key] = descriptor.value;
|
|
5513
|
+
} else {
|
|
5514
|
+
Object.defineProperty(result, key, descriptor);
|
|
5515
|
+
}
|
|
5516
|
+
}
|
|
5517
|
+
return result;
|
|
5518
|
+
}
|
|
5519
|
+
function FromPlainObject(value) {
|
|
5520
|
+
const result = {};
|
|
5521
|
+
for (const key of Keys(value)) {
|
|
5522
|
+
if (IsUnsafePropertyKey(key))
|
|
5523
|
+
continue;
|
|
5524
|
+
result[key] = FromValue(value[key]);
|
|
5525
|
+
}
|
|
5526
|
+
for (const key of Symbols(value)) {
|
|
5527
|
+
result[key] = FromValue(value[key]);
|
|
5528
|
+
}
|
|
5529
|
+
return result;
|
|
5530
|
+
}
|
|
5531
|
+
function FromObject(value) {
|
|
5532
|
+
return IsClassInstance(value) ? FromClassInstance(value) : IsSchemaObject(value) ? FromSchemaObject(value) : FromPlainObject(value);
|
|
5533
|
+
}
|
|
5534
|
+
function FromArray(value) {
|
|
5535
|
+
return value.map((element) => FromValue(element));
|
|
5536
|
+
}
|
|
5537
|
+
function FromTypedArray(value) {
|
|
5538
|
+
return value.slice();
|
|
5539
|
+
}
|
|
5540
|
+
function FromRegExp(value) {
|
|
5541
|
+
return new RegExp(value.source, value.flags);
|
|
5542
|
+
}
|
|
5543
|
+
function FromMap(value) {
|
|
5544
|
+
return new Map(FromValue([...value.entries()]));
|
|
5545
|
+
}
|
|
5546
|
+
function FromSet(value) {
|
|
5547
|
+
return new Set(FromValue([...value.values()]));
|
|
5548
|
+
}
|
|
5549
|
+
function FromValue(value) {
|
|
5550
|
+
return IsTypeArray(value) ? FromTypedArray(value) : IsRegExp(value) ? FromRegExp(value) : IsMap(value) ? FromMap(value) : IsSet(value) ? FromSet(value) : IsArray(value) ? FromArray(value) : IsObject(value) ? FromObject(value) : value;
|
|
5551
|
+
}
|
|
5552
|
+
function Clone(value) {
|
|
5553
|
+
Metrics.clone += 1;
|
|
5554
|
+
return FromValue(value);
|
|
5555
|
+
}
|
|
5349
5556
|
// ../../node_modules/.bun/typebox@1.3.16/node_modules/typebox/build/system/settings/settings.mjs
|
|
5350
5557
|
var settings = {
|
|
5351
5558
|
immutableTypes: false,
|
|
@@ -5382,12 +5589,52 @@ function Create(hidden, enumerable, options = {}) {
|
|
|
5382
5589
|
const withHidden = settings.enumerableKind ? Merge(withOptions, hidden) : MergeHidden(withOptions, hidden);
|
|
5383
5590
|
return settings.immutableTypes ? Object.freeze(withHidden) : withHidden;
|
|
5384
5591
|
}
|
|
5592
|
+
// ../../node_modules/.bun/typebox@1.3.16/node_modules/typebox/build/system/memory/update.mjs
|
|
5593
|
+
function Update(current, hidden, enumerable) {
|
|
5594
|
+
Metrics.update += 1;
|
|
5595
|
+
const settings = Get();
|
|
5596
|
+
const result = Clone(current);
|
|
5597
|
+
for (const key of Object.keys(hidden)) {
|
|
5598
|
+
Object.defineProperty(result, key, {
|
|
5599
|
+
configurable: true,
|
|
5600
|
+
writable: true,
|
|
5601
|
+
enumerable: settings.enumerableKind,
|
|
5602
|
+
value: hidden[key]
|
|
5603
|
+
});
|
|
5604
|
+
}
|
|
5605
|
+
for (const key of Object.keys(enumerable)) {
|
|
5606
|
+
Object.defineProperty(result, key, {
|
|
5607
|
+
configurable: true,
|
|
5608
|
+
enumerable: true,
|
|
5609
|
+
writable: true,
|
|
5610
|
+
value: enumerable[key]
|
|
5611
|
+
});
|
|
5612
|
+
}
|
|
5613
|
+
return result;
|
|
5614
|
+
}
|
|
5385
5615
|
// ../../node_modules/.bun/typebox@1.3.16/node_modules/typebox/build/type/types/schema.mjs
|
|
5386
5616
|
function IsSchema(value) {
|
|
5387
5617
|
return IsObject(value);
|
|
5388
5618
|
}
|
|
5389
5619
|
|
|
5620
|
+
// ../../node_modules/.bun/typebox@1.3.16/node_modules/typebox/build/type/engine/optional/instantiate_add.mjs
|
|
5621
|
+
function AddOptionalOperation(type) {
|
|
5622
|
+
return Update(type, { "~optional": true }, {});
|
|
5623
|
+
}
|
|
5624
|
+
function AddOptionalAction(type, options) {
|
|
5625
|
+
const result = Update(AddOptionalOperation(type), {}, options);
|
|
5626
|
+
return result;
|
|
5627
|
+
}
|
|
5628
|
+
|
|
5629
|
+
// ../../node_modules/.bun/typebox@1.3.16/node_modules/typebox/build/type/action/_add_optional.mjs
|
|
5630
|
+
function AddOptional(type, options = {}) {
|
|
5631
|
+
return AddOptionalAction(type, options);
|
|
5632
|
+
}
|
|
5633
|
+
|
|
5390
5634
|
// ../../node_modules/.bun/typebox@1.3.16/node_modules/typebox/build/type/types/_optional.mjs
|
|
5635
|
+
function Optional(type) {
|
|
5636
|
+
return AddOptional(type);
|
|
5637
|
+
}
|
|
5391
5638
|
function IsOptional(value) {
|
|
5392
5639
|
return IsSchema(value) && HasPropertyKey(value, "~optional");
|
|
5393
5640
|
}
|
|
@@ -5430,10 +5677,41 @@ var F64Out = new Uint8Array(F64.buffer);
|
|
|
5430
5677
|
var encoder = new TextEncoder;
|
|
5431
5678
|
// ../../node_modules/.bun/typebox@1.3.16/node_modules/typebox/build/type/types/integer.mjs
|
|
5432
5679
|
var IntegerPattern = "-?(?:0|[1-9][0-9]*)";
|
|
5680
|
+
// ../../node_modules/.bun/typebox@1.3.16/node_modules/typebox/build/type/types/literal.mjs
|
|
5681
|
+
class InvalidLiteralValue extends Error {
|
|
5682
|
+
constructor(value) {
|
|
5683
|
+
super(`Invalid Literal value`);
|
|
5684
|
+
Object.defineProperty(this, "cause", {
|
|
5685
|
+
value: { value },
|
|
5686
|
+
writable: false,
|
|
5687
|
+
configurable: false,
|
|
5688
|
+
enumerable: false
|
|
5689
|
+
});
|
|
5690
|
+
}
|
|
5691
|
+
}
|
|
5692
|
+
function LiteralTypeName(value) {
|
|
5693
|
+
return IsBigInt(value) ? "bigint" : IsBoolean(value) ? "boolean" : IsNumber(value) ? "number" : IsString(value) ? "string" : (() => {
|
|
5694
|
+
throw new InvalidLiteralValue(value);
|
|
5695
|
+
})();
|
|
5696
|
+
}
|
|
5697
|
+
function Literal(value, options) {
|
|
5698
|
+
return Create({ "~kind": "Literal" }, { type: LiteralTypeName(value), const: value }, options);
|
|
5699
|
+
}
|
|
5433
5700
|
// ../../node_modules/.bun/typebox@1.3.16/node_modules/typebox/build/type/types/number.mjs
|
|
5434
5701
|
var NumberPattern = "-?(?:0|[1-9][0-9]*)(?:\\.[0-9]+)?";
|
|
5702
|
+
function Number2(options) {
|
|
5703
|
+
return Create({ "~kind": "Number" }, { type: "number" }, options);
|
|
5704
|
+
}
|
|
5435
5705
|
// ../../node_modules/.bun/typebox@1.3.16/node_modules/typebox/build/type/types/string.mjs
|
|
5436
5706
|
var StringPattern = ".*";
|
|
5707
|
+
function String2(options) {
|
|
5708
|
+
return Create({ "~kind": "String" }, { type: "string" }, options);
|
|
5709
|
+
}
|
|
5710
|
+
|
|
5711
|
+
// ../../node_modules/.bun/typebox@1.3.16/node_modules/typebox/build/type/types/union.mjs
|
|
5712
|
+
function Union(anyOf, options = {}) {
|
|
5713
|
+
return Create({ "~kind": "Union" }, { anyOf }, options);
|
|
5714
|
+
}
|
|
5437
5715
|
|
|
5438
5716
|
// ../../node_modules/.bun/typebox@1.3.16/node_modules/typebox/build/type/types/record.mjs
|
|
5439
5717
|
var IntegerKey = `^${IntegerPattern}$`;
|
|
@@ -5475,12 +5753,19 @@ function percentUsed(spentUsd, capUsd) {
|
|
|
5475
5753
|
}
|
|
5476
5754
|
function formatStats(stats) {
|
|
5477
5755
|
const credits = stats.credit_credits;
|
|
5756
|
+
const remaining = stats.remaining_credits_today;
|
|
5757
|
+
const allowance = stats.allowance_credits;
|
|
5758
|
+
const refillAt = stats.refill_at;
|
|
5759
|
+
const lane = stats.lane;
|
|
5760
|
+
const todayLine = typeof remaining === "number" && typeof allowance === "number" && typeof refillAt === "string" ? formatCreditsLeftLine(remaining, allowance, new Date(refillAt)) : `today: ${percentUsed(stats.spent_usd_today, stats.cap_usd_today)}% of your free daily allowance used`;
|
|
5478
5761
|
return [
|
|
5479
5762
|
`tier: ${stats.tier}`,
|
|
5480
|
-
|
|
5763
|
+
todayLine,
|
|
5764
|
+
...lane === "fallback" ? [SLOW_LANE_METER_LINE] : [],
|
|
5481
5765
|
...typeof credits === "number" ? [`credit: ${credits.toLocaleString("en-US")} credits remaining`] : [],
|
|
5482
5766
|
`today tokens: ${stats.prompt_tokens_today} prompt / ${stats.completion_tokens_today} completion`,
|
|
5483
|
-
`lifetime: ${stats.lifetime.prompt_tokens} prompt / ${stats.lifetime.completion_tokens} completion tokens
|
|
5767
|
+
`lifetime: ${stats.lifetime.prompt_tokens} prompt / ${stats.lifetime.completion_tokens} completion tokens`,
|
|
5768
|
+
"buy more credits: /buy-credits"
|
|
5484
5769
|
].join(`
|
|
5485
5770
|
`);
|
|
5486
5771
|
}
|
|
@@ -5617,6 +5902,33 @@ first. Purchased credits cover use beyond it and do not expire.
|
|
|
5617
5902
|
- \`/close-other-session\` — free a stuck session on another machine
|
|
5618
5903
|
- \`/update\` — get the latest free-pi
|
|
5619
5904
|
- \`/whats-new\` — what changed in recent releases
|
|
5905
|
+
- \`/todos\` — show the agent's todo list
|
|
5906
|
+
- \`/mcp\` — manage your MCP servers (connect, sign in, turn on or off)
|
|
5907
|
+
|
|
5908
|
+
## Todo list
|
|
5909
|
+
The agent keeps its own todo list with the \`todo\` tool, to track multi-step
|
|
5910
|
+
work. While any item is open, the list shows live above the input box, and
|
|
5911
|
+
each todo change prints a progress line in the conversation. \`/todos\` shows
|
|
5912
|
+
the full list. The list belongs to the session and comes back when you resume
|
|
5913
|
+
it.
|
|
5914
|
+
|
|
5915
|
+
## MCP servers
|
|
5916
|
+
MCP (Model Context Protocol) servers give the agent extra tools, such as a
|
|
5917
|
+
database, a browser, or an internal API. free-pi includes none; you add your
|
|
5918
|
+
own. The agent reaches their tools by writing a short script for its
|
|
5919
|
+
\`codemode\` tool.
|
|
5920
|
+
Servers live in \`~/.free-pi/agent/mcp.json\`. A project can have its own
|
|
5921
|
+
\`.pi/mcp.json\`; it is read only after you trust the project when asked.
|
|
5922
|
+
Add a server from a shell, or ask the agent to run the command for you with
|
|
5923
|
+
its bash tool:
|
|
5924
|
+
\`free-pi-cli mcp add files -- npx -y @modelcontextprotocol/server-filesystem .\`
|
|
5925
|
+
\`free-pi-cli mcp add docs --url https://example.com/mcp\`
|
|
5926
|
+
Other commands: \`free-pi-cli mcp list\` (add \`--json\` for machine output),
|
|
5927
|
+
\`free-pi-cli mcp remove <server>\`, \`free-pi-cli mcp login <server>\` to sign
|
|
5928
|
+
in to a server that uses OAuth, and \`free-pi-cli mcp logout <server>\`. Add
|
|
5929
|
+
\`-l\` to add or remove in the current project instead of globally.
|
|
5930
|
+
Inside a session, \`/mcp\` shows each server and its tools, and lets you sign
|
|
5931
|
+
in, reconnect, or turn a server on or off.
|
|
5620
5932
|
|
|
5621
5933
|
## Support
|
|
5622
5934
|
Today's sponsor keeps free-pi free. When the user asks how to help the
|
|
@@ -5648,6 +5960,257 @@ function createDocsToolExtension() {
|
|
|
5648
5960
|
};
|
|
5649
5961
|
}
|
|
5650
5962
|
|
|
5963
|
+
// src/todo-view.ts
|
|
5964
|
+
var PLAIN_STYLE = { bold: (s) => s, dim: (s) => s, accent: (s) => s, error: (s) => s };
|
|
5965
|
+
var GAP = " ";
|
|
5966
|
+
function truncate2(text, max) {
|
|
5967
|
+
if (max <= 0)
|
|
5968
|
+
return "";
|
|
5969
|
+
if ([...text].length <= max)
|
|
5970
|
+
return text;
|
|
5971
|
+
return [...text].slice(0, Math.max(0, max - 1)).join("") + "…";
|
|
5972
|
+
}
|
|
5973
|
+
function counts(todos) {
|
|
5974
|
+
return { done: todos.filter((t) => t.done).length, total: todos.length };
|
|
5975
|
+
}
|
|
5976
|
+
function widgetLines(todos, width, st = PLAIN_STYLE) {
|
|
5977
|
+
if (todos.length === 0)
|
|
5978
|
+
return [];
|
|
5979
|
+
const { done, total } = counts(todos);
|
|
5980
|
+
const header = `Todos ${done}/${total}`;
|
|
5981
|
+
const firstOpen = todos.findIndex((t) => !t.done);
|
|
5982
|
+
const marks = todos.map((t, i) => t.done ? `✓ ${t.text}` : i === firstOpen ? `▸ ${t.text}` : `○ ${t.text}`);
|
|
5983
|
+
const compactLength = [...[header, ...marks].join(GAP)].length;
|
|
5984
|
+
if (compactLength <= width) {
|
|
5985
|
+
return [
|
|
5986
|
+
[
|
|
5987
|
+
st.bold(header),
|
|
5988
|
+
...todos.map((t, i) => t.done ? st.dim(marks[i]) : i === firstOpen ? st.accent(st.bold(marks[i])) : st.dim(marks[i]))
|
|
5989
|
+
].join(GAP)
|
|
5990
|
+
];
|
|
5991
|
+
}
|
|
5992
|
+
const lines = [st.bold(truncate2(header, width))];
|
|
5993
|
+
const open = todos.filter((t) => !t.done);
|
|
5994
|
+
if (open.length === 0) {
|
|
5995
|
+
lines.push(st.dim(truncate2("✓ All done", width)));
|
|
5996
|
+
return lines;
|
|
5997
|
+
}
|
|
5998
|
+
open.slice(0, 3).forEach((t, i) => {
|
|
5999
|
+
const line = truncate2(`${i === 0 ? "▸" : "○"} ${t.text}`, width);
|
|
6000
|
+
lines.push(i === 0 ? st.accent(st.bold(line)) : st.dim(line));
|
|
6001
|
+
});
|
|
6002
|
+
const more = open.length - 3;
|
|
6003
|
+
if (more > 0)
|
|
6004
|
+
lines.push(st.dim(truncate2(`+${more} more · /todos for all`, width)));
|
|
6005
|
+
return lines;
|
|
6006
|
+
}
|
|
6007
|
+
function resultLines(d, st = PLAIN_STYLE) {
|
|
6008
|
+
if (d.error)
|
|
6009
|
+
return [st.error(`Error: ${d.error}`)];
|
|
6010
|
+
if (d.action === "clear")
|
|
6011
|
+
return [`${st.accent("●")} ${st.bold("todo")} cleared`];
|
|
6012
|
+
const { done, total } = counts(d.todos);
|
|
6013
|
+
const head = `${st.accent("●")} ${st.bold("todo")} ${done}/${total}`;
|
|
6014
|
+
let first = head;
|
|
6015
|
+
if (d.item && d.action === "add")
|
|
6016
|
+
first = `${head}${GAP}+ ${d.item.text}`;
|
|
6017
|
+
if (d.item && d.action === "toggle")
|
|
6018
|
+
first = `${head}${GAP}${d.item.done ? "✓" : "○"} ${d.item.text}`;
|
|
6019
|
+
const lines = [first];
|
|
6020
|
+
const next = d.todos.find((t) => !t.done);
|
|
6021
|
+
if ((d.action === "add" || d.action === "toggle") && next && next.id !== d.item?.id)
|
|
6022
|
+
lines.push(st.dim(`${GAP}next: ${next.text}`));
|
|
6023
|
+
return lines;
|
|
6024
|
+
}
|
|
6025
|
+
|
|
6026
|
+
// src/todo-tool.ts
|
|
6027
|
+
var TODO_TOOL_NAME = "todo";
|
|
6028
|
+
var TODO_WIDGET_ID = "freepi-todo";
|
|
6029
|
+
var TODO_ENTRY_TYPE = "free-pi-todo";
|
|
6030
|
+
var ALL_DONE_LINGER_MS = 3000;
|
|
6031
|
+
var REASSERT_ORDER_MS = 15000;
|
|
6032
|
+
var realTimers = {
|
|
6033
|
+
setTimeout: (fn, ms) => {
|
|
6034
|
+
const h = setTimeout(fn, ms);
|
|
6035
|
+
h.unref?.();
|
|
6036
|
+
return h;
|
|
6037
|
+
},
|
|
6038
|
+
clearTimeout: (h) => clearTimeout(h),
|
|
6039
|
+
setInterval: (fn, ms) => {
|
|
6040
|
+
const h = setInterval(fn, ms);
|
|
6041
|
+
h.unref?.();
|
|
6042
|
+
return h;
|
|
6043
|
+
},
|
|
6044
|
+
clearInterval: (h) => clearInterval(h)
|
|
6045
|
+
};
|
|
6046
|
+
var PARAMS4 = _Object_({
|
|
6047
|
+
action: Union([Literal("list"), Literal("add"), Literal("toggle"), Literal("clear")]),
|
|
6048
|
+
text: Optional(String2({ description: "Todo text (for add)" })),
|
|
6049
|
+
id: Optional(Number2({ description: "Todo ID (for toggle)" }))
|
|
6050
|
+
});
|
|
6051
|
+
function asSnapshot(value) {
|
|
6052
|
+
const v = value;
|
|
6053
|
+
if (!v || !Array.isArray(v.todos) || typeof v.nextId !== "number")
|
|
6054
|
+
return;
|
|
6055
|
+
return { todos: v.todos, nextId: v.nextId };
|
|
6056
|
+
}
|
|
6057
|
+
function formatTodos(todos) {
|
|
6058
|
+
return todos.length ? todos.map((t) => `[${t.done ? "x" : " "}] #${t.id}: ${t.text}`).join(`
|
|
6059
|
+
`) : "No todos";
|
|
6060
|
+
}
|
|
6061
|
+
function styleFrom(theme) {
|
|
6062
|
+
return {
|
|
6063
|
+
bold: (s) => theme.bold(s),
|
|
6064
|
+
dim: (s) => theme.fg("dim", s),
|
|
6065
|
+
accent: (s) => theme.fg("accent", s),
|
|
6066
|
+
error: (s) => theme.fg("error", s)
|
|
6067
|
+
};
|
|
6068
|
+
}
|
|
6069
|
+
function createTodoToolExtension(options = {}) {
|
|
6070
|
+
const timers = options.timers ?? realTimers;
|
|
6071
|
+
return {
|
|
6072
|
+
name: "free-pi-todo",
|
|
6073
|
+
factory(pi) {
|
|
6074
|
+
let todos = [];
|
|
6075
|
+
let nextId = 1;
|
|
6076
|
+
let widgetCtx;
|
|
6077
|
+
let lingerTimer;
|
|
6078
|
+
let orderTimer;
|
|
6079
|
+
const stopTimers = () => {
|
|
6080
|
+
if (lingerTimer !== undefined)
|
|
6081
|
+
timers.clearTimeout(lingerTimer);
|
|
6082
|
+
if (orderTimer !== undefined)
|
|
6083
|
+
timers.clearInterval(orderTimer);
|
|
6084
|
+
lingerTimer = orderTimer = undefined;
|
|
6085
|
+
};
|
|
6086
|
+
const hideWidget = () => {
|
|
6087
|
+
stopTimers();
|
|
6088
|
+
try {
|
|
6089
|
+
widgetCtx?.ui.setWidget(TODO_WIDGET_ID, undefined);
|
|
6090
|
+
} catch {}
|
|
6091
|
+
};
|
|
6092
|
+
const showWidget = () => {
|
|
6093
|
+
try {
|
|
6094
|
+
widgetCtx?.ui.setWidget(TODO_WIDGET_ID, (_tui, theme) => {
|
|
6095
|
+
const style = styleFrom(theme);
|
|
6096
|
+
return {
|
|
6097
|
+
render: (width) => widgetLines(todos, Math.max(1, width - 2), style).map((l) => ` ${l}`),
|
|
6098
|
+
invalidate() {}
|
|
6099
|
+
};
|
|
6100
|
+
});
|
|
6101
|
+
} catch {}
|
|
6102
|
+
};
|
|
6103
|
+
const refreshWidget = (ctx, replay = false) => {
|
|
6104
|
+
if (ctx?.mode !== "tui")
|
|
6105
|
+
return;
|
|
6106
|
+
widgetCtx = ctx;
|
|
6107
|
+
const hasOpen = todos.some((t) => !t.done);
|
|
6108
|
+
if (todos.length === 0 || !hasOpen && replay) {
|
|
6109
|
+
hideWidget();
|
|
6110
|
+
return;
|
|
6111
|
+
}
|
|
6112
|
+
if (lingerTimer !== undefined) {
|
|
6113
|
+
timers.clearTimeout(lingerTimer);
|
|
6114
|
+
lingerTimer = undefined;
|
|
6115
|
+
}
|
|
6116
|
+
showWidget();
|
|
6117
|
+
if (orderTimer === undefined)
|
|
6118
|
+
orderTimer = timers.setInterval(showWidget, REASSERT_ORDER_MS);
|
|
6119
|
+
if (!hasOpen) {
|
|
6120
|
+
lingerTimer = timers.setTimeout(() => {
|
|
6121
|
+
lingerTimer = undefined;
|
|
6122
|
+
hideWidget();
|
|
6123
|
+
}, ALL_DONE_LINGER_MS);
|
|
6124
|
+
}
|
|
6125
|
+
};
|
|
6126
|
+
const reconstructState = (ctx) => {
|
|
6127
|
+
todos = [];
|
|
6128
|
+
nextId = 1;
|
|
6129
|
+
for (const entry of ctx.sessionManager.getBranch()) {
|
|
6130
|
+
let snap;
|
|
6131
|
+
if (entry.type === "custom" && entry.customType === TODO_ENTRY_TYPE)
|
|
6132
|
+
snap = asSnapshot(entry.data);
|
|
6133
|
+
else if (entry.type === "message" && entry.message.role === "toolResult" && entry.message.toolName === TODO_TOOL_NAME)
|
|
6134
|
+
snap = asSnapshot(entry.message.details);
|
|
6135
|
+
if (snap) {
|
|
6136
|
+
todos = snap.todos.map((t) => ({ ...t }));
|
|
6137
|
+
nextId = snap.nextId;
|
|
6138
|
+
}
|
|
6139
|
+
}
|
|
6140
|
+
refreshWidget(ctx, true);
|
|
6141
|
+
};
|
|
6142
|
+
pi.on("session_start", async (_event, ctx) => reconstructState(ctx));
|
|
6143
|
+
pi.on("session_tree", async (_event, ctx) => reconstructState(ctx));
|
|
6144
|
+
pi.on("session_shutdown", async () => {
|
|
6145
|
+
stopTimers();
|
|
6146
|
+
widgetCtx = undefined;
|
|
6147
|
+
});
|
|
6148
|
+
const result = (ctx, text, action, error, item) => {
|
|
6149
|
+
const snapshot = { todos: todos.map((t) => ({ ...t })), nextId };
|
|
6150
|
+
if (action !== "list" && !error)
|
|
6151
|
+
pi.appendEntry(TODO_ENTRY_TYPE, snapshot);
|
|
6152
|
+
if (action !== "list" && !error)
|
|
6153
|
+
refreshWidget(ctx);
|
|
6154
|
+
return {
|
|
6155
|
+
content: [{ type: "text", text }],
|
|
6156
|
+
details: { action, ...snapshot, ...item ? { item: { ...item } } : {}, ...error ? { error } : {} }
|
|
6157
|
+
};
|
|
6158
|
+
};
|
|
6159
|
+
pi.registerTool({
|
|
6160
|
+
name: TODO_TOOL_NAME,
|
|
6161
|
+
label: "Todo",
|
|
6162
|
+
description: "Manage a todo list. Actions: list, add (text), toggle (id), clear",
|
|
6163
|
+
promptSnippet: "Track your own multi-step work in a todo list: add items, toggle them done, list them",
|
|
6164
|
+
parameters: PARAMS4,
|
|
6165
|
+
async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
|
|
6166
|
+
switch (params.action) {
|
|
6167
|
+
case "list":
|
|
6168
|
+
return result(ctx, formatTodos(todos), "list");
|
|
6169
|
+
case "add": {
|
|
6170
|
+
if (!params.text)
|
|
6171
|
+
return result(ctx, "Error: text required for add", "add", "text required");
|
|
6172
|
+
const todo = { id: nextId++, text: params.text, done: false };
|
|
6173
|
+
todos.push(todo);
|
|
6174
|
+
return result(ctx, `Added todo #${todo.id}: ${todo.text}`, "add", undefined, todo);
|
|
6175
|
+
}
|
|
6176
|
+
case "toggle": {
|
|
6177
|
+
if (params.id === undefined)
|
|
6178
|
+
return result(ctx, "Error: id required for toggle", "toggle", "id required");
|
|
6179
|
+
const todo = todos.find((t) => t.id === params.id);
|
|
6180
|
+
if (!todo)
|
|
6181
|
+
return result(ctx, `Todo #${params.id} not found`, "toggle", `#${params.id} not found`);
|
|
6182
|
+
todo.done = !todo.done;
|
|
6183
|
+
return result(ctx, `Todo #${todo.id} ${todo.done ? "completed" : "uncompleted"}`, "toggle", undefined, todo);
|
|
6184
|
+
}
|
|
6185
|
+
case "clear": {
|
|
6186
|
+
const count = todos.length;
|
|
6187
|
+
todos = [];
|
|
6188
|
+
nextId = 1;
|
|
6189
|
+
return result(ctx, `Cleared ${count} todos`, "clear");
|
|
6190
|
+
}
|
|
6191
|
+
default:
|
|
6192
|
+
return result(ctx, `Unknown action: ${String(params.action)}`, "list", "unknown action");
|
|
6193
|
+
}
|
|
6194
|
+
},
|
|
6195
|
+
renderCall() {
|
|
6196
|
+
return { render: () => [], invalidate() {} };
|
|
6197
|
+
},
|
|
6198
|
+
renderResult(res, _opts, theme) {
|
|
6199
|
+
const details = res.details;
|
|
6200
|
+
const lines = details?.action ? resultLines(details, styleFrom(theme)) : [res.content[0]?.type === "text" ? res.content[0].text : ""];
|
|
6201
|
+
return { render: () => lines, invalidate() {} };
|
|
6202
|
+
}
|
|
6203
|
+
});
|
|
6204
|
+
pi.registerCommand("todos", {
|
|
6205
|
+
description: "Show all todos on the current branch",
|
|
6206
|
+
handler: async (_args, ctx) => {
|
|
6207
|
+
ctx.ui.notify(formatTodos(todos), "info");
|
|
6208
|
+
}
|
|
6209
|
+
});
|
|
6210
|
+
}
|
|
6211
|
+
};
|
|
6212
|
+
}
|
|
6213
|
+
|
|
5651
6214
|
// src/error-notice.ts
|
|
5652
6215
|
var NOTICE_DEDUPE_MS = 15000;
|
|
5653
6216
|
function parseErrorEnvelope(errorMessage) {
|
|
@@ -5703,12 +6266,16 @@ async function reportToolGuardTrigger(opts, toolName) {
|
|
|
5703
6266
|
});
|
|
5704
6267
|
} catch {}
|
|
5705
6268
|
}
|
|
6269
|
+
var MCP_RESOURCE_TOOLS = ["list_mcp_resources", "list_mcp_resource_templates", "read_mcp_resource"];
|
|
6270
|
+
function isAllowed(opts, name) {
|
|
6271
|
+
return opts.allowedTools.includes(name) || name.startsWith("mcp__") || MCP_RESOURCE_TOOLS.includes(name);
|
|
6272
|
+
}
|
|
5706
6273
|
function createToolGuardExtension(opts) {
|
|
5707
6274
|
return {
|
|
5708
6275
|
name: "free-pi-tool-guard",
|
|
5709
6276
|
factory(pi) {
|
|
5710
6277
|
pi.on("tool_call", (event) => {
|
|
5711
|
-
if (opts
|
|
6278
|
+
if (isAllowed(opts, event.toolName))
|
|
5712
6279
|
return;
|
|
5713
6280
|
reportToolGuardTrigger(opts, event.toolName);
|
|
5714
6281
|
return { block: true, reason: "tool not permitted in the free-pi distro" };
|
|
@@ -5786,6 +6353,20 @@ function registerBugAutocomplete(pi) {
|
|
|
5786
6353
|
|
|
5787
6354
|
// src/changelog.ts
|
|
5788
6355
|
var CHANGELOG_HIGHLIGHTS = [
|
|
6356
|
+
{
|
|
6357
|
+
version: "0.4.1",
|
|
6358
|
+
highlights: [
|
|
6359
|
+
"The footer now shows your credits left, including any you bought. A warning appears when you are low or in the slow lane.",
|
|
6360
|
+
"/usage shows credits left today and when they refill, with /buy-credits to get more."
|
|
6361
|
+
]
|
|
6362
|
+
},
|
|
6363
|
+
{
|
|
6364
|
+
version: "0.4.0",
|
|
6365
|
+
highlights: [
|
|
6366
|
+
"The agent now keeps a todo list for multi-step work: it shows live above the input box, and /todos shows the full list.",
|
|
6367
|
+
"Add your own MCP servers: `npx free-pi-cli mcp add`, then /mcp in a session to manage them."
|
|
6368
|
+
]
|
|
6369
|
+
},
|
|
5789
6370
|
{
|
|
5790
6371
|
version: "0.3.0",
|
|
5791
6372
|
highlights: [
|
|
@@ -6018,7 +6599,7 @@ function createFreePiCommandsExtension(opts) {
|
|
|
6018
6599
|
handler: (_args, ctx) => runUpdate(ctx)
|
|
6019
6600
|
});
|
|
6020
6601
|
pi.registerCommand("usage", {
|
|
6021
|
-
description: "Show
|
|
6602
|
+
description: "Show credits left today, refill time, and purchased credit.",
|
|
6022
6603
|
handler: (_args, ctx) => showUsage(opts, ctx)
|
|
6023
6604
|
});
|
|
6024
6605
|
pi.registerCommand("buy-credits", {
|
|
@@ -6043,7 +6624,7 @@ function createFreePiCommandsExtension(opts) {
|
|
|
6043
6624
|
|
|
6044
6625
|
// src/header.ts
|
|
6045
6626
|
var COMMANDS = [
|
|
6046
|
-
{ name: "/usage", description: "
|
|
6627
|
+
{ name: "/usage", description: "credits left today and refill time" },
|
|
6047
6628
|
{ name: "/support", description: "visit today's advertiser" },
|
|
6048
6629
|
{ name: "/tos", description: "terms of service" },
|
|
6049
6630
|
{ name: "/privacy-policy", description: "privacy policy" },
|
|
@@ -6068,12 +6649,12 @@ function updateBannerLines(theme, latest, width) {
|
|
|
6068
6649
|
return [
|
|
6069
6650
|
"",
|
|
6070
6651
|
border,
|
|
6071
|
-
theme.bold(theme.fg("warning",
|
|
6072
|
-
theme.fg("dim",
|
|
6652
|
+
theme.bold(theme.fg("warning", truncate3(headline, inner))),
|
|
6653
|
+
theme.fg("dim", truncate3(detail, inner)),
|
|
6073
6654
|
border
|
|
6074
6655
|
];
|
|
6075
6656
|
}
|
|
6076
|
-
function
|
|
6657
|
+
function truncate3(text, maxWidth) {
|
|
6077
6658
|
if (maxWidth <= 0)
|
|
6078
6659
|
return "";
|
|
6079
6660
|
if (text.length <= maxWidth)
|
|
@@ -6084,7 +6665,7 @@ function truncate2(text, maxWidth) {
|
|
|
6084
6665
|
}
|
|
6085
6666
|
function headerLines(theme, modelName, expanded, width = Number.POSITIVE_INFINITY, updateLatest) {
|
|
6086
6667
|
const inner = width - 1;
|
|
6087
|
-
const fit = (plain, styled) => plain.length <= inner ? styled() : theme.fg("dim",
|
|
6668
|
+
const fit = (plain, styled) => plain.length <= inner ? styled() : theme.fg("dim", truncate3(plain, inner));
|
|
6088
6669
|
const titleRest = ` · ${modelName} · ctrl+o for help`;
|
|
6089
6670
|
const title = fit(`free-pi${titleRest}`, () => `${theme.bold(theme.fg("accent", "free-pi"))}${theme.fg("dim", titleRest)}`);
|
|
6090
6671
|
const commandLines = COMMANDS.map(({ name, description }) => {
|
|
@@ -6166,6 +6747,48 @@ function patchResumeLine(stream) {
|
|
|
6166
6747
|
};
|
|
6167
6748
|
}
|
|
6168
6749
|
|
|
6750
|
+
// src/project-trust.ts
|
|
6751
|
+
import { createInterface } from "node:readline/promises";
|
|
6752
|
+
import { resolve } from "node:path";
|
|
6753
|
+
import { hasTrustRequiringProjectResources, ProjectTrustStore } from "@earendil-works/pi-coding-agent";
|
|
6754
|
+
var PROJECT_TRUST_PROMPT = "This folder has project settings for free-pi (.pi/ or .agents/skills), which can run code such as MCP servers and extensions. Trust this folder? [y/N] ";
|
|
6755
|
+
async function decideProjectTrust(cwd, agentDir, deps) {
|
|
6756
|
+
if (!(deps.hasTrustRequiringResources ?? hasTrustRequiringProjectResources)(cwd))
|
|
6757
|
+
return true;
|
|
6758
|
+
const store = new ProjectTrustStore(agentDir);
|
|
6759
|
+
const saved = store.get(cwd);
|
|
6760
|
+
if (typeof saved === "boolean")
|
|
6761
|
+
return saved;
|
|
6762
|
+
if (!deps.stdinIsTTY) {
|
|
6763
|
+
deps.log("This folder has project settings (.pi/ or .agents/skills); they are ignored because it is not trusted.");
|
|
6764
|
+
return false;
|
|
6765
|
+
}
|
|
6766
|
+
const decision = /^y(es)?$/i.test((await deps.askLine(PROJECT_TRUST_PROMPT)).trim());
|
|
6767
|
+
store.set(cwd, decision);
|
|
6768
|
+
return decision;
|
|
6769
|
+
}
|
|
6770
|
+
function decideProjectTrustInteractive(cwd, agentDir) {
|
|
6771
|
+
return decideProjectTrust(cwd, agentDir, {
|
|
6772
|
+
stdinIsTTY: process.stdin.isTTY === true,
|
|
6773
|
+
log: (message) => console.log(message),
|
|
6774
|
+
askLine: async (prompt) => {
|
|
6775
|
+
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
6776
|
+
try {
|
|
6777
|
+
return await rl.question(prompt);
|
|
6778
|
+
} finally {
|
|
6779
|
+
rl.close();
|
|
6780
|
+
}
|
|
6781
|
+
}
|
|
6782
|
+
});
|
|
6783
|
+
}
|
|
6784
|
+
function projectTrustForCwd(cwd, launch, agentDir, hasTrustRequiringResources = hasTrustRequiringProjectResources) {
|
|
6785
|
+
if (resolve(cwd) === resolve(launch.cwd))
|
|
6786
|
+
return launch.trusted;
|
|
6787
|
+
if (!hasTrustRequiringResources(cwd))
|
|
6788
|
+
return true;
|
|
6789
|
+
return new ProjectTrustStore(agentDir).get(cwd) === true;
|
|
6790
|
+
}
|
|
6791
|
+
|
|
6169
6792
|
// src/pi-launch.ts
|
|
6170
6793
|
function catalogModelsFor(opts) {
|
|
6171
6794
|
if (opts.models && opts.models.length > 0)
|
|
@@ -6173,11 +6796,42 @@ function catalogModelsFor(opts) {
|
|
|
6173
6796
|
const id = opts.model || MODEL_ID;
|
|
6174
6797
|
return [{ id, name: id }];
|
|
6175
6798
|
}
|
|
6176
|
-
var ALLOWED_TOOL_NAMES = [
|
|
6799
|
+
var ALLOWED_TOOL_NAMES = [
|
|
6800
|
+
...SAFE_TOOLS,
|
|
6801
|
+
USAGE_TOOL_NAME,
|
|
6802
|
+
BUY_TOOL_NAME,
|
|
6803
|
+
DOCS_TOOL_NAME,
|
|
6804
|
+
TODO_TOOL_NAME,
|
|
6805
|
+
"codemode",
|
|
6806
|
+
"tool_search"
|
|
6807
|
+
];
|
|
6808
|
+
var SDK_TOOLS = [
|
|
6809
|
+
...ALLOWED_TOOL_NAMES,
|
|
6810
|
+
"mcp__*",
|
|
6811
|
+
"list_mcp_resources",
|
|
6812
|
+
"list_mcp_resource_templates",
|
|
6813
|
+
"read_mcp_resource"
|
|
6814
|
+
];
|
|
6177
6815
|
function resolveModelName(opts) {
|
|
6178
6816
|
return opts.models?.[0]?.name || opts.model || MODEL_ID;
|
|
6179
6817
|
}
|
|
6818
|
+
function buildSettingsManager(opts, models, projectTrusted) {
|
|
6819
|
+
return SettingsManager.inMemory({
|
|
6820
|
+
defaultProvider: PROVIDER_NAME,
|
|
6821
|
+
defaultModel: models[0].id,
|
|
6822
|
+
defaultTools: [...SAFE_TOOLS, TODO_TOOL_NAME],
|
|
6823
|
+
packages: [],
|
|
6824
|
+
quietStartup: true,
|
|
6825
|
+
tuiMode: "regular",
|
|
6826
|
+
theme: "light/dark",
|
|
6827
|
+
enableInstallTelemetry: false
|
|
6828
|
+
}, { projectTrusted });
|
|
6829
|
+
}
|
|
6830
|
+
function settingsForRuntime(opts, rtCwd, launch) {
|
|
6831
|
+
return buildSettingsManager(opts, catalogModelsFor(opts), projectTrustForCwd(rtCwd, launch, opts.agentDir));
|
|
6832
|
+
}
|
|
6180
6833
|
function buildRuntimeOptions(opts, sessionId) {
|
|
6834
|
+
process.env.PI_CODING_AGENT_DIR = opts.agentDir;
|
|
6181
6835
|
const models = catalogModelsFor(opts);
|
|
6182
6836
|
const providerExtension = {
|
|
6183
6837
|
name: "free-pi-provider",
|
|
@@ -6218,6 +6872,10 @@ function buildRuntimeOptions(opts, sessionId) {
|
|
|
6218
6872
|
modelName: resolveModelName(opts),
|
|
6219
6873
|
updateLatest: opts.updateLatest
|
|
6220
6874
|
});
|
|
6875
|
+
const todoToolExtension = createTodoToolExtension();
|
|
6876
|
+
const mcpExtension = { name: "pi-mcp", factory: createMcpExtension() };
|
|
6877
|
+
const codemodeExtension = { name: "pi-codemode", factory: createCodemodeExtension({ models: false }) };
|
|
6878
|
+
const toolSearchExtension = { name: "pi-tool-search", factory: createToolSearchExtension() };
|
|
6221
6879
|
const extensionFactories = [
|
|
6222
6880
|
providerExtension,
|
|
6223
6881
|
adsExtension,
|
|
@@ -6227,29 +6885,24 @@ function buildRuntimeOptions(opts, sessionId) {
|
|
|
6227
6885
|
errorNoticeExtension,
|
|
6228
6886
|
toolGuardExtension,
|
|
6229
6887
|
commandsExtension,
|
|
6230
|
-
headerExtension
|
|
6888
|
+
headerExtension,
|
|
6889
|
+
todoToolExtension,
|
|
6890
|
+
mcpExtension,
|
|
6891
|
+
codemodeExtension,
|
|
6892
|
+
toolSearchExtension
|
|
6231
6893
|
];
|
|
6232
|
-
const settingsManager =
|
|
6233
|
-
defaultProvider: PROVIDER_NAME,
|
|
6234
|
-
defaultModel: models[0].id,
|
|
6235
|
-
defaultTools: [...SAFE_TOOLS],
|
|
6236
|
-
packages: [],
|
|
6237
|
-
quietStartup: true,
|
|
6238
|
-
tuiMode: "regular",
|
|
6239
|
-
theme: "light/dark",
|
|
6240
|
-
enableInstallTelemetry: false
|
|
6241
|
-
});
|
|
6894
|
+
const settingsManager = buildSettingsManager(opts, models, opts.projectTrusted === true);
|
|
6242
6895
|
return {
|
|
6243
6896
|
settingsManager,
|
|
6244
6897
|
resourceLoaderOptions: { extensionFactories },
|
|
6245
6898
|
extensionNames: extensionFactories.map((e) => e.name),
|
|
6246
|
-
tools:
|
|
6899
|
+
tools: SDK_TOOLS
|
|
6247
6900
|
};
|
|
6248
6901
|
}
|
|
6249
6902
|
function getSessionDir(cwd, agentDir) {
|
|
6250
|
-
const resolvedCwd =
|
|
6903
|
+
const resolvedCwd = resolve2(cwd);
|
|
6251
6904
|
const safePath = `--${resolvedCwd.replace(/^[/\\]/, "").replace(/[/\\:]/g, "-")}--`;
|
|
6252
|
-
return
|
|
6905
|
+
return join3(resolve2(agentDir), "sessions", safePath);
|
|
6253
6906
|
}
|
|
6254
6907
|
async function createSessionManager(cwd, sessionDir, sessionId, api = SessionManager) {
|
|
6255
6908
|
if (sessionId) {
|
|
@@ -6271,7 +6924,9 @@ async function launchPi(opts) {
|
|
|
6271
6924
|
const sm = await createSessionManager(cwd, sessionDir, opts.session);
|
|
6272
6925
|
const sessionId = sm.getSessionId();
|
|
6273
6926
|
const scopeIds = catalogModelsFor(opts).map((m) => m.id);
|
|
6274
|
-
const
|
|
6927
|
+
const projectTrusted = await decideProjectTrustInteractive(cwd, opts.agentDir);
|
|
6928
|
+
const { resourceLoaderOptions, tools } = buildRuntimeOptions({ ...opts, projectTrusted }, sessionId);
|
|
6929
|
+
const launchTrust = { cwd, trusted: projectTrusted };
|
|
6275
6930
|
const createRuntime = async ({
|
|
6276
6931
|
cwd: rtCwd,
|
|
6277
6932
|
sessionManager,
|
|
@@ -6280,7 +6935,7 @@ async function launchPi(opts) {
|
|
|
6280
6935
|
const services = await createAgentSessionServices({
|
|
6281
6936
|
cwd: rtCwd,
|
|
6282
6937
|
agentDir: opts.agentDir,
|
|
6283
|
-
settingsManager,
|
|
6938
|
+
settingsManager: settingsForRuntime(opts, rtCwd, launchTrust),
|
|
6284
6939
|
resourceLoaderOptions
|
|
6285
6940
|
});
|
|
6286
6941
|
const scopedModels = resolveFreePiScope(services.modelRuntime, PROVIDER_NAME, scopeIds);
|
|
@@ -6311,7 +6966,7 @@ async function launchPi(opts) {
|
|
|
6311
6966
|
}
|
|
6312
6967
|
|
|
6313
6968
|
// src/prompt.ts
|
|
6314
|
-
import { createInterface } from "node:readline/promises";
|
|
6969
|
+
import { createInterface as createInterface2 } from "node:readline/promises";
|
|
6315
6970
|
|
|
6316
6971
|
// src/consent.ts
|
|
6317
6972
|
var CONSENT_TEXT = `
|
|
@@ -6361,7 +7016,7 @@ async function promptConsentInteractive() {
|
|
|
6361
7016
|
stdinIsTTY: process.stdin.isTTY === true,
|
|
6362
7017
|
log: (message) => console.log(message),
|
|
6363
7018
|
askLine: async (prompt) => {
|
|
6364
|
-
const rl =
|
|
7019
|
+
const rl = createInterface2({ input: process.stdin, output: process.stdout });
|
|
6365
7020
|
try {
|
|
6366
7021
|
return await rl.question(prompt);
|
|
6367
7022
|
} finally {
|
|
@@ -6496,12 +7151,12 @@ ${INTRO_TEXT}
|
|
|
6496
7151
|
}
|
|
6497
7152
|
|
|
6498
7153
|
// src/whats-new.ts
|
|
6499
|
-
import { existsSync, mkdirSync, readFileSync as readFileSync2, writeFileSync } from "node:fs";
|
|
6500
|
-
import { dirname as
|
|
7154
|
+
import { existsSync as existsSync2, mkdirSync, readFileSync as readFileSync2, writeFileSync } from "node:fs";
|
|
7155
|
+
import { dirname as dirname3 } from "node:path";
|
|
6501
7156
|
function maybeWhatsNew(cliVersion, agentDir) {
|
|
6502
7157
|
try {
|
|
6503
7158
|
const path = getLastSeenVersionPath(agentDir);
|
|
6504
|
-
const lastSeen =
|
|
7159
|
+
const lastSeen = existsSync2(path) ? readFileSync2(path, "utf-8").trim() : undefined;
|
|
6505
7160
|
writeLastSeen(path, cliVersion);
|
|
6506
7161
|
if (lastSeen === undefined)
|
|
6507
7162
|
return;
|
|
@@ -6517,7 +7172,7 @@ function maybeWhatsNew(cliVersion, agentDir) {
|
|
|
6517
7172
|
}
|
|
6518
7173
|
}
|
|
6519
7174
|
function writeLastSeen(path, version) {
|
|
6520
|
-
mkdirSync(
|
|
7175
|
+
mkdirSync(dirname3(path), { recursive: true });
|
|
6521
7176
|
writeFileSync(path, version, "utf-8");
|
|
6522
7177
|
}
|
|
6523
7178
|
|
|
@@ -6706,6 +7361,15 @@ ${HELP_TEXT}`);
|
|
|
6706
7361
|
});
|
|
6707
7362
|
process.exit(logoutCode);
|
|
6708
7363
|
}
|
|
7364
|
+
if (command.kind === "mcp") {
|
|
7365
|
+
const mcpCode = await runFreePiMcp(command.args, {
|
|
7366
|
+
cwd: process.cwd(),
|
|
7367
|
+
agentDir: getFreePiAgentDir(),
|
|
7368
|
+
log: (line) => console.log(line),
|
|
7369
|
+
error: (line) => console.error(line)
|
|
7370
|
+
});
|
|
7371
|
+
process.exit(mcpCode);
|
|
7372
|
+
}
|
|
6709
7373
|
const agentDir = getFreePiAgentDir();
|
|
6710
7374
|
const code = await run({
|
|
6711
7375
|
baseUrl: resolveBaseUrl(),
|