free-pi-cli 0.2.19 → 0.4.0
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-tc7xq892.js → main-nyekh9dy.js} +729 -44
- package/package.json +2 -2
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.0",
|
|
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",
|
|
@@ -63,7 +63,7 @@ var package_default = {
|
|
|
63
63
|
prepublishOnly: "bun run build"
|
|
64
64
|
},
|
|
65
65
|
dependencies: {
|
|
66
|
-
"@earendil-works/pi-coding-agent": "
|
|
66
|
+
"@earendil-works/pi-coding-agent": "1.2.0"
|
|
67
67
|
},
|
|
68
68
|
devDependencies: {
|
|
69
69
|
"@freepi/pi-ads": "workspace:*",
|
|
@@ -80,10 +80,13 @@ var HELP_TEXT = `
|
|
|
80
80
|
free-pi — free AI coding in your terminal; ads pay for the inference.
|
|
81
81
|
|
|
82
82
|
Usage:
|
|
83
|
-
free-pi-cli
|
|
84
|
-
free-pi-cli
|
|
85
|
-
free-pi-cli
|
|
86
|
-
free-pi-cli
|
|
83
|
+
free-pi-cli start the coding agent (first run: consent + GitHub sign-in)
|
|
84
|
+
free-pi-cli --session <id> resume a previous session
|
|
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)
|
|
88
|
+
free-pi-cli --version print the CLI version
|
|
89
|
+
free-pi-cli --help show this help
|
|
87
90
|
|
|
88
91
|
Environment:
|
|
89
92
|
FREEPI_BASE_URL override the backend base URL (default https://api.freepi.ai)
|
|
@@ -96,6 +99,11 @@ function parseCliArgs(argv) {
|
|
|
96
99
|
const [first, ...rest] = argv;
|
|
97
100
|
if (first === undefined)
|
|
98
101
|
return { kind: "run" };
|
|
102
|
+
if (first === "mcp")
|
|
103
|
+
return { kind: "mcp", args: rest };
|
|
104
|
+
if (first === "--session") {
|
|
105
|
+
return rest.length > 1 ? { kind: "unknown", arg: rest[1] } : { kind: "run" };
|
|
106
|
+
}
|
|
99
107
|
let kind;
|
|
100
108
|
if (first === "--help" || first === "-h" || first === "help")
|
|
101
109
|
kind = "help";
|
|
@@ -159,21 +167,58 @@ async function runLogout(deps) {
|
|
|
159
167
|
return 0;
|
|
160
168
|
}
|
|
161
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
|
+
|
|
162
207
|
// src/paths.ts
|
|
163
208
|
import { homedir } from "node:os";
|
|
164
|
-
import { join } from "node:path";
|
|
209
|
+
import { join as join2 } from "node:path";
|
|
165
210
|
var FREE_PI_DIR_NAME = ".free-pi";
|
|
166
211
|
function getFreePiAgentDir() {
|
|
167
|
-
return
|
|
212
|
+
return join2(homedir(), FREE_PI_DIR_NAME, "agent");
|
|
168
213
|
}
|
|
169
214
|
function getCredentialsPath(agentDir = getFreePiAgentDir()) {
|
|
170
|
-
return
|
|
215
|
+
return join2(agentDir, "credentials.json");
|
|
171
216
|
}
|
|
172
217
|
function getLastSeenVersionPath(agentDir = getFreePiAgentDir()) {
|
|
173
|
-
return
|
|
218
|
+
return join2(agentDir, "last-seen-version");
|
|
174
219
|
}
|
|
175
220
|
function getConfigPath() {
|
|
176
|
-
return
|
|
221
|
+
return join2(homedir(), FREE_PI_DIR_NAME, "config.json");
|
|
177
222
|
}
|
|
178
223
|
|
|
179
224
|
// src/pi-launch.ts
|
|
@@ -181,6 +226,9 @@ import {
|
|
|
181
226
|
createAgentSessionFromServices,
|
|
182
227
|
createAgentSessionRuntime,
|
|
183
228
|
createAgentSessionServices,
|
|
229
|
+
createCodemodeExtension,
|
|
230
|
+
createMcpExtension,
|
|
231
|
+
createToolSearchExtension,
|
|
184
232
|
InteractiveMode,
|
|
185
233
|
SessionManager,
|
|
186
234
|
SettingsManager
|
|
@@ -4851,6 +4899,13 @@ var ClientVersionResponseSchema = object({
|
|
|
4851
4899
|
var SessionResetResponseSchema = object({
|
|
4852
4900
|
released: boolean2()
|
|
4853
4901
|
});
|
|
4902
|
+
var BugReportRequestSchema = object({
|
|
4903
|
+
description: string2().trim().min(1).max(4000),
|
|
4904
|
+
session_id: string2().max(128).optional(),
|
|
4905
|
+
cli_version: string2().max(32).optional(),
|
|
4906
|
+
model: string2().max(128).optional()
|
|
4907
|
+
});
|
|
4908
|
+
var BugReportResponseSchema = object({ id: string2() });
|
|
4854
4909
|
// ../shared/src/errors.ts
|
|
4855
4910
|
function assertExhaustiveErrorCode(code) {
|
|
4856
4911
|
throw new Error(`unhandled error code: ${String(code)}`);
|
|
@@ -4865,6 +4920,8 @@ var ConfigSchema = object({
|
|
|
4865
4920
|
accountsPerDayCeiling: number2().int().default(500),
|
|
4866
4921
|
authRatePerIpPerHour: number2().int().default(30),
|
|
4867
4922
|
webBuyUrlRatePerProviderPerHour: number2().int().default(20),
|
|
4923
|
+
handoffTokenRatePerProviderPerHour: number2().int().default(60),
|
|
4924
|
+
handoffHourlyCompletionCap: number2().int().default(120),
|
|
4868
4925
|
tokenRatePerIpPerHour: number2().int().default(3600),
|
|
4869
4926
|
consentRatePerIpPerHour: number2().int().default(60),
|
|
4870
4927
|
adInlineTurnFrequency: number2().int().default(5),
|
|
@@ -4931,6 +4988,8 @@ function loadConfig(env = process.env) {
|
|
|
4931
4988
|
accountsPerDayCeiling: envNumber(env.ACCOUNTS_PER_DAY_CEILING),
|
|
4932
4989
|
authRatePerIpPerHour: envNumber(env.AUTH_RATE_PER_IP_PER_HOUR),
|
|
4933
4990
|
webBuyUrlRatePerProviderPerHour: envNumber(env.WEB_BUY_URL_RATE_PER_PROVIDER_PER_HOUR),
|
|
4991
|
+
handoffTokenRatePerProviderPerHour: envNumber(env.HANDOFF_TOKEN_RATE_PER_PROVIDER_PER_HOUR),
|
|
4992
|
+
handoffHourlyCompletionCap: envNumber(env.HANDOFF_HOURLY_COMPLETION_CAP),
|
|
4934
4993
|
tokenRatePerIpPerHour: envNumber(env.TOKEN_RATE_PER_IP_PER_HOUR),
|
|
4935
4994
|
consentRatePerIpPerHour: envNumber(env.CONSENT_RATE_PER_IP_PER_HOUR),
|
|
4936
4995
|
adInlineTurnFrequency: envNumber(env.AD_INLINE_TURN_FREQUENCY),
|
|
@@ -5186,6 +5245,8 @@ function mapErrorCode(code) {
|
|
|
5186
5245
|
return "too many requests this hour on the free tier — wait for the Retry-After window";
|
|
5187
5246
|
case "account_review":
|
|
5188
5247
|
return "this account is under review — contact support";
|
|
5248
|
+
case "ads_required":
|
|
5249
|
+
return "ads aren't displaying for this account, so free completions are paused — run free-pi through `npx free-pi-cli`, then retry";
|
|
5189
5250
|
default:
|
|
5190
5251
|
return assertExhaustiveErrorCode(code);
|
|
5191
5252
|
}
|
|
@@ -5273,7 +5334,7 @@ function createAdsExtension(opts) {
|
|
|
5273
5334
|
}
|
|
5274
5335
|
|
|
5275
5336
|
// src/pi-launch.ts
|
|
5276
|
-
import { join as
|
|
5337
|
+
import { join as join3, resolve as resolve2 } from "node:path";
|
|
5277
5338
|
|
|
5278
5339
|
// src/provider.ts
|
|
5279
5340
|
var PROVIDER_NAME = "free-pi";
|
|
@@ -5311,15 +5372,38 @@ var Metrics = {
|
|
|
5311
5372
|
update: 0
|
|
5312
5373
|
};
|
|
5313
5374
|
// ../../node_modules/.bun/typebox@1.3.16/node_modules/typebox/build/guard/guard.mjs
|
|
5375
|
+
function IsArray(value) {
|
|
5376
|
+
return Array.isArray(value);
|
|
5377
|
+
}
|
|
5378
|
+
function IsBigInt(value) {
|
|
5379
|
+
return IsEqual(typeof value, "bigint");
|
|
5380
|
+
}
|
|
5381
|
+
function IsBoolean(value) {
|
|
5382
|
+
return IsEqual(typeof value, "boolean");
|
|
5383
|
+
}
|
|
5314
5384
|
function IsNull(value) {
|
|
5315
5385
|
return IsEqual(value, null);
|
|
5316
5386
|
}
|
|
5387
|
+
function IsNumber(value) {
|
|
5388
|
+
return Number.isFinite(value);
|
|
5389
|
+
}
|
|
5317
5390
|
function IsObject(value) {
|
|
5318
5391
|
return IsEqual(typeof value, "object") && !IsNull(value);
|
|
5319
5392
|
}
|
|
5393
|
+
function IsString(value) {
|
|
5394
|
+
return IsEqual(typeof value, "string");
|
|
5395
|
+
}
|
|
5320
5396
|
function IsEqual(left, right) {
|
|
5321
5397
|
return left === right;
|
|
5322
5398
|
}
|
|
5399
|
+
function IsClassInstance(value) {
|
|
5400
|
+
if (!IsObject(value))
|
|
5401
|
+
return false;
|
|
5402
|
+
const proto = globalThis.Object.getPrototypeOf(value);
|
|
5403
|
+
if (IsNull(proto))
|
|
5404
|
+
return false;
|
|
5405
|
+
return IsEqual(typeof proto.constructor, "function") && !(IsEqual(proto.constructor, globalThis.Object) || IsEqual(proto.constructor.name, "Object"));
|
|
5406
|
+
}
|
|
5323
5407
|
function IsUnsafePropertyKey(key) {
|
|
5324
5408
|
return IsEqual(key, "__proto__") || IsEqual(key, "constructor") || IsEqual(key, "prototype");
|
|
5325
5409
|
}
|
|
@@ -5329,6 +5413,81 @@ function HasPropertyKey(value, key) {
|
|
|
5329
5413
|
function Keys(value) {
|
|
5330
5414
|
return Object.getOwnPropertyNames(value);
|
|
5331
5415
|
}
|
|
5416
|
+
function Symbols(value) {
|
|
5417
|
+
return Object.getOwnPropertySymbols(value);
|
|
5418
|
+
}
|
|
5419
|
+
// ../../node_modules/.bun/typebox@1.3.16/node_modules/typebox/build/guard/globals.mjs
|
|
5420
|
+
function IsTypeArray(value) {
|
|
5421
|
+
return globalThis.ArrayBuffer.isView(value);
|
|
5422
|
+
}
|
|
5423
|
+
function IsRegExp(value) {
|
|
5424
|
+
return value instanceof globalThis.RegExp;
|
|
5425
|
+
}
|
|
5426
|
+
function IsSet(value) {
|
|
5427
|
+
return value instanceof globalThis.Set;
|
|
5428
|
+
}
|
|
5429
|
+
function IsMap(value) {
|
|
5430
|
+
return value instanceof globalThis.Map;
|
|
5431
|
+
}
|
|
5432
|
+
// ../../node_modules/.bun/typebox@1.3.16/node_modules/typebox/build/system/memory/clone.mjs
|
|
5433
|
+
function FromClassInstance(value) {
|
|
5434
|
+
return value;
|
|
5435
|
+
}
|
|
5436
|
+
function IsSchemaObject(value) {
|
|
5437
|
+
return HasPropertyKey(value, "~kind") || HasPropertyKey(value, "~unsafe");
|
|
5438
|
+
}
|
|
5439
|
+
function FromSchemaObject(value) {
|
|
5440
|
+
const result = {};
|
|
5441
|
+
for (const key of Keys(value)) {
|
|
5442
|
+
if (IsUnsafePropertyKey(key))
|
|
5443
|
+
continue;
|
|
5444
|
+
const descriptor = Object.getOwnPropertyDescriptor(value, key);
|
|
5445
|
+
descriptor.value = FromValue(descriptor.value);
|
|
5446
|
+
if (IsEqual(descriptor.enumerable, true)) {
|
|
5447
|
+
result[key] = descriptor.value;
|
|
5448
|
+
} else {
|
|
5449
|
+
Object.defineProperty(result, key, descriptor);
|
|
5450
|
+
}
|
|
5451
|
+
}
|
|
5452
|
+
return result;
|
|
5453
|
+
}
|
|
5454
|
+
function FromPlainObject(value) {
|
|
5455
|
+
const result = {};
|
|
5456
|
+
for (const key of Keys(value)) {
|
|
5457
|
+
if (IsUnsafePropertyKey(key))
|
|
5458
|
+
continue;
|
|
5459
|
+
result[key] = FromValue(value[key]);
|
|
5460
|
+
}
|
|
5461
|
+
for (const key of Symbols(value)) {
|
|
5462
|
+
result[key] = FromValue(value[key]);
|
|
5463
|
+
}
|
|
5464
|
+
return result;
|
|
5465
|
+
}
|
|
5466
|
+
function FromObject(value) {
|
|
5467
|
+
return IsClassInstance(value) ? FromClassInstance(value) : IsSchemaObject(value) ? FromSchemaObject(value) : FromPlainObject(value);
|
|
5468
|
+
}
|
|
5469
|
+
function FromArray(value) {
|
|
5470
|
+
return value.map((element) => FromValue(element));
|
|
5471
|
+
}
|
|
5472
|
+
function FromTypedArray(value) {
|
|
5473
|
+
return value.slice();
|
|
5474
|
+
}
|
|
5475
|
+
function FromRegExp(value) {
|
|
5476
|
+
return new RegExp(value.source, value.flags);
|
|
5477
|
+
}
|
|
5478
|
+
function FromMap(value) {
|
|
5479
|
+
return new Map(FromValue([...value.entries()]));
|
|
5480
|
+
}
|
|
5481
|
+
function FromSet(value) {
|
|
5482
|
+
return new Set(FromValue([...value.values()]));
|
|
5483
|
+
}
|
|
5484
|
+
function FromValue(value) {
|
|
5485
|
+
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;
|
|
5486
|
+
}
|
|
5487
|
+
function Clone(value) {
|
|
5488
|
+
Metrics.clone += 1;
|
|
5489
|
+
return FromValue(value);
|
|
5490
|
+
}
|
|
5332
5491
|
// ../../node_modules/.bun/typebox@1.3.16/node_modules/typebox/build/system/settings/settings.mjs
|
|
5333
5492
|
var settings = {
|
|
5334
5493
|
immutableTypes: false,
|
|
@@ -5365,12 +5524,52 @@ function Create(hidden, enumerable, options = {}) {
|
|
|
5365
5524
|
const withHidden = settings.enumerableKind ? Merge(withOptions, hidden) : MergeHidden(withOptions, hidden);
|
|
5366
5525
|
return settings.immutableTypes ? Object.freeze(withHidden) : withHidden;
|
|
5367
5526
|
}
|
|
5527
|
+
// ../../node_modules/.bun/typebox@1.3.16/node_modules/typebox/build/system/memory/update.mjs
|
|
5528
|
+
function Update(current, hidden, enumerable) {
|
|
5529
|
+
Metrics.update += 1;
|
|
5530
|
+
const settings = Get();
|
|
5531
|
+
const result = Clone(current);
|
|
5532
|
+
for (const key of Object.keys(hidden)) {
|
|
5533
|
+
Object.defineProperty(result, key, {
|
|
5534
|
+
configurable: true,
|
|
5535
|
+
writable: true,
|
|
5536
|
+
enumerable: settings.enumerableKind,
|
|
5537
|
+
value: hidden[key]
|
|
5538
|
+
});
|
|
5539
|
+
}
|
|
5540
|
+
for (const key of Object.keys(enumerable)) {
|
|
5541
|
+
Object.defineProperty(result, key, {
|
|
5542
|
+
configurable: true,
|
|
5543
|
+
enumerable: true,
|
|
5544
|
+
writable: true,
|
|
5545
|
+
value: enumerable[key]
|
|
5546
|
+
});
|
|
5547
|
+
}
|
|
5548
|
+
return result;
|
|
5549
|
+
}
|
|
5368
5550
|
// ../../node_modules/.bun/typebox@1.3.16/node_modules/typebox/build/type/types/schema.mjs
|
|
5369
5551
|
function IsSchema(value) {
|
|
5370
5552
|
return IsObject(value);
|
|
5371
5553
|
}
|
|
5372
5554
|
|
|
5555
|
+
// ../../node_modules/.bun/typebox@1.3.16/node_modules/typebox/build/type/engine/optional/instantiate_add.mjs
|
|
5556
|
+
function AddOptionalOperation(type) {
|
|
5557
|
+
return Update(type, { "~optional": true }, {});
|
|
5558
|
+
}
|
|
5559
|
+
function AddOptionalAction(type, options) {
|
|
5560
|
+
const result = Update(AddOptionalOperation(type), {}, options);
|
|
5561
|
+
return result;
|
|
5562
|
+
}
|
|
5563
|
+
|
|
5564
|
+
// ../../node_modules/.bun/typebox@1.3.16/node_modules/typebox/build/type/action/_add_optional.mjs
|
|
5565
|
+
function AddOptional(type, options = {}) {
|
|
5566
|
+
return AddOptionalAction(type, options);
|
|
5567
|
+
}
|
|
5568
|
+
|
|
5373
5569
|
// ../../node_modules/.bun/typebox@1.3.16/node_modules/typebox/build/type/types/_optional.mjs
|
|
5570
|
+
function Optional(type) {
|
|
5571
|
+
return AddOptional(type);
|
|
5572
|
+
}
|
|
5374
5573
|
function IsOptional(value) {
|
|
5375
5574
|
return IsSchema(value) && HasPropertyKey(value, "~optional");
|
|
5376
5575
|
}
|
|
@@ -5413,10 +5612,41 @@ var F64Out = new Uint8Array(F64.buffer);
|
|
|
5413
5612
|
var encoder = new TextEncoder;
|
|
5414
5613
|
// ../../node_modules/.bun/typebox@1.3.16/node_modules/typebox/build/type/types/integer.mjs
|
|
5415
5614
|
var IntegerPattern = "-?(?:0|[1-9][0-9]*)";
|
|
5615
|
+
// ../../node_modules/.bun/typebox@1.3.16/node_modules/typebox/build/type/types/literal.mjs
|
|
5616
|
+
class InvalidLiteralValue extends Error {
|
|
5617
|
+
constructor(value) {
|
|
5618
|
+
super(`Invalid Literal value`);
|
|
5619
|
+
Object.defineProperty(this, "cause", {
|
|
5620
|
+
value: { value },
|
|
5621
|
+
writable: false,
|
|
5622
|
+
configurable: false,
|
|
5623
|
+
enumerable: false
|
|
5624
|
+
});
|
|
5625
|
+
}
|
|
5626
|
+
}
|
|
5627
|
+
function LiteralTypeName(value) {
|
|
5628
|
+
return IsBigInt(value) ? "bigint" : IsBoolean(value) ? "boolean" : IsNumber(value) ? "number" : IsString(value) ? "string" : (() => {
|
|
5629
|
+
throw new InvalidLiteralValue(value);
|
|
5630
|
+
})();
|
|
5631
|
+
}
|
|
5632
|
+
function Literal(value, options) {
|
|
5633
|
+
return Create({ "~kind": "Literal" }, { type: LiteralTypeName(value), const: value }, options);
|
|
5634
|
+
}
|
|
5416
5635
|
// ../../node_modules/.bun/typebox@1.3.16/node_modules/typebox/build/type/types/number.mjs
|
|
5417
5636
|
var NumberPattern = "-?(?:0|[1-9][0-9]*)(?:\\.[0-9]+)?";
|
|
5637
|
+
function Number2(options) {
|
|
5638
|
+
return Create({ "~kind": "Number" }, { type: "number" }, options);
|
|
5639
|
+
}
|
|
5418
5640
|
// ../../node_modules/.bun/typebox@1.3.16/node_modules/typebox/build/type/types/string.mjs
|
|
5419
5641
|
var StringPattern = ".*";
|
|
5642
|
+
function String2(options) {
|
|
5643
|
+
return Create({ "~kind": "String" }, { type: "string" }, options);
|
|
5644
|
+
}
|
|
5645
|
+
|
|
5646
|
+
// ../../node_modules/.bun/typebox@1.3.16/node_modules/typebox/build/type/types/union.mjs
|
|
5647
|
+
function Union(anyOf, options = {}) {
|
|
5648
|
+
return Create({ "~kind": "Union" }, { anyOf }, options);
|
|
5649
|
+
}
|
|
5420
5650
|
|
|
5421
5651
|
// ../../node_modules/.bun/typebox@1.3.16/node_modules/typebox/build/type/types/record.mjs
|
|
5422
5652
|
var IntegerKey = `^${IntegerPattern}$`;
|
|
@@ -5573,9 +5803,10 @@ exciting models. Ads shown in the terminal pay for it. You never handle a
|
|
|
5573
5803
|
model API key. Config and the sign-in token live under \`~/.free-pi/agent\`,
|
|
5574
5804
|
separate from any \`~/.pi/agent\` install.
|
|
5575
5805
|
|
|
5576
|
-
##
|
|
5577
|
-
|
|
5578
|
-
|
|
5806
|
+
## Ads and your data
|
|
5807
|
+
Ads render in the terminal, in a framed card. Some ads come from Gravity, a
|
|
5808
|
+
third-party ad provider that receives excerpts of your recent conversation
|
|
5809
|
+
to pick a relevant ad.
|
|
5579
5810
|
Coding sessions may be used to train models. This is on the consent screen
|
|
5580
5811
|
at first run, before the GitHub sign-in.
|
|
5581
5812
|
|
|
@@ -5599,6 +5830,33 @@ first. Purchased credits cover use beyond it and do not expire.
|
|
|
5599
5830
|
- \`/close-other-session\` — free a stuck session on another machine
|
|
5600
5831
|
- \`/update\` — get the latest free-pi
|
|
5601
5832
|
- \`/whats-new\` — what changed in recent releases
|
|
5833
|
+
- \`/todos\` — show the agent's todo list
|
|
5834
|
+
- \`/mcp\` — manage your MCP servers (connect, sign in, turn on or off)
|
|
5835
|
+
|
|
5836
|
+
## Todo list
|
|
5837
|
+
The agent keeps its own todo list with the \`todo\` tool, to track multi-step
|
|
5838
|
+
work. While any item is open, the list shows live above the input box, and
|
|
5839
|
+
each todo change prints a progress line in the conversation. \`/todos\` shows
|
|
5840
|
+
the full list. The list belongs to the session and comes back when you resume
|
|
5841
|
+
it.
|
|
5842
|
+
|
|
5843
|
+
## MCP servers
|
|
5844
|
+
MCP (Model Context Protocol) servers give the agent extra tools, such as a
|
|
5845
|
+
database, a browser, or an internal API. free-pi includes none; you add your
|
|
5846
|
+
own. The agent reaches their tools by writing a short script for its
|
|
5847
|
+
\`codemode\` tool.
|
|
5848
|
+
Servers live in \`~/.free-pi/agent/mcp.json\`. A project can have its own
|
|
5849
|
+
\`.pi/mcp.json\`; it is read only after you trust the project when asked.
|
|
5850
|
+
Add a server from a shell, or ask the agent to run the command for you with
|
|
5851
|
+
its bash tool:
|
|
5852
|
+
\`free-pi-cli mcp add files -- npx -y @modelcontextprotocol/server-filesystem .\`
|
|
5853
|
+
\`free-pi-cli mcp add docs --url https://example.com/mcp\`
|
|
5854
|
+
Other commands: \`free-pi-cli mcp list\` (add \`--json\` for machine output),
|
|
5855
|
+
\`free-pi-cli mcp remove <server>\`, \`free-pi-cli mcp login <server>\` to sign
|
|
5856
|
+
in to a server that uses OAuth, and \`free-pi-cli mcp logout <server>\`. Add
|
|
5857
|
+
\`-l\` to add or remove in the current project instead of globally.
|
|
5858
|
+
Inside a session, \`/mcp\` shows each server and its tools, and lets you sign
|
|
5859
|
+
in, reconnect, or turn a server on or off.
|
|
5602
5860
|
|
|
5603
5861
|
## Support
|
|
5604
5862
|
Today's sponsor keeps free-pi free. When the user asks how to help the
|
|
@@ -5630,6 +5888,257 @@ function createDocsToolExtension() {
|
|
|
5630
5888
|
};
|
|
5631
5889
|
}
|
|
5632
5890
|
|
|
5891
|
+
// src/todo-view.ts
|
|
5892
|
+
var PLAIN_STYLE = { bold: (s) => s, dim: (s) => s, accent: (s) => s, error: (s) => s };
|
|
5893
|
+
var GAP = " ";
|
|
5894
|
+
function truncate2(text, max) {
|
|
5895
|
+
if (max <= 0)
|
|
5896
|
+
return "";
|
|
5897
|
+
if ([...text].length <= max)
|
|
5898
|
+
return text;
|
|
5899
|
+
return [...text].slice(0, Math.max(0, max - 1)).join("") + "…";
|
|
5900
|
+
}
|
|
5901
|
+
function counts(todos) {
|
|
5902
|
+
return { done: todos.filter((t) => t.done).length, total: todos.length };
|
|
5903
|
+
}
|
|
5904
|
+
function widgetLines(todos, width, st = PLAIN_STYLE) {
|
|
5905
|
+
if (todos.length === 0)
|
|
5906
|
+
return [];
|
|
5907
|
+
const { done, total } = counts(todos);
|
|
5908
|
+
const header = `Todos ${done}/${total}`;
|
|
5909
|
+
const firstOpen = todos.findIndex((t) => !t.done);
|
|
5910
|
+
const marks = todos.map((t, i) => t.done ? `✓ ${t.text}` : i === firstOpen ? `▸ ${t.text}` : `○ ${t.text}`);
|
|
5911
|
+
const compactLength = [...[header, ...marks].join(GAP)].length;
|
|
5912
|
+
if (compactLength <= width) {
|
|
5913
|
+
return [
|
|
5914
|
+
[
|
|
5915
|
+
st.bold(header),
|
|
5916
|
+
...todos.map((t, i) => t.done ? st.dim(marks[i]) : i === firstOpen ? st.accent(st.bold(marks[i])) : st.dim(marks[i]))
|
|
5917
|
+
].join(GAP)
|
|
5918
|
+
];
|
|
5919
|
+
}
|
|
5920
|
+
const lines = [st.bold(truncate2(header, width))];
|
|
5921
|
+
const open = todos.filter((t) => !t.done);
|
|
5922
|
+
if (open.length === 0) {
|
|
5923
|
+
lines.push(st.dim(truncate2("✓ All done", width)));
|
|
5924
|
+
return lines;
|
|
5925
|
+
}
|
|
5926
|
+
open.slice(0, 3).forEach((t, i) => {
|
|
5927
|
+
const line = truncate2(`${i === 0 ? "▸" : "○"} ${t.text}`, width);
|
|
5928
|
+
lines.push(i === 0 ? st.accent(st.bold(line)) : st.dim(line));
|
|
5929
|
+
});
|
|
5930
|
+
const more = open.length - 3;
|
|
5931
|
+
if (more > 0)
|
|
5932
|
+
lines.push(st.dim(truncate2(`+${more} more · /todos for all`, width)));
|
|
5933
|
+
return lines;
|
|
5934
|
+
}
|
|
5935
|
+
function resultLines(d, st = PLAIN_STYLE) {
|
|
5936
|
+
if (d.error)
|
|
5937
|
+
return [st.error(`Error: ${d.error}`)];
|
|
5938
|
+
if (d.action === "clear")
|
|
5939
|
+
return [`${st.accent("●")} ${st.bold("todo")} cleared`];
|
|
5940
|
+
const { done, total } = counts(d.todos);
|
|
5941
|
+
const head = `${st.accent("●")} ${st.bold("todo")} ${done}/${total}`;
|
|
5942
|
+
let first = head;
|
|
5943
|
+
if (d.item && d.action === "add")
|
|
5944
|
+
first = `${head}${GAP}+ ${d.item.text}`;
|
|
5945
|
+
if (d.item && d.action === "toggle")
|
|
5946
|
+
first = `${head}${GAP}${d.item.done ? "✓" : "○"} ${d.item.text}`;
|
|
5947
|
+
const lines = [first];
|
|
5948
|
+
const next = d.todos.find((t) => !t.done);
|
|
5949
|
+
if ((d.action === "add" || d.action === "toggle") && next && next.id !== d.item?.id)
|
|
5950
|
+
lines.push(st.dim(`${GAP}next: ${next.text}`));
|
|
5951
|
+
return lines;
|
|
5952
|
+
}
|
|
5953
|
+
|
|
5954
|
+
// src/todo-tool.ts
|
|
5955
|
+
var TODO_TOOL_NAME = "todo";
|
|
5956
|
+
var TODO_WIDGET_ID = "freepi-todo";
|
|
5957
|
+
var TODO_ENTRY_TYPE = "free-pi-todo";
|
|
5958
|
+
var ALL_DONE_LINGER_MS = 3000;
|
|
5959
|
+
var REASSERT_ORDER_MS = 15000;
|
|
5960
|
+
var realTimers = {
|
|
5961
|
+
setTimeout: (fn, ms) => {
|
|
5962
|
+
const h = setTimeout(fn, ms);
|
|
5963
|
+
h.unref?.();
|
|
5964
|
+
return h;
|
|
5965
|
+
},
|
|
5966
|
+
clearTimeout: (h) => clearTimeout(h),
|
|
5967
|
+
setInterval: (fn, ms) => {
|
|
5968
|
+
const h = setInterval(fn, ms);
|
|
5969
|
+
h.unref?.();
|
|
5970
|
+
return h;
|
|
5971
|
+
},
|
|
5972
|
+
clearInterval: (h) => clearInterval(h)
|
|
5973
|
+
};
|
|
5974
|
+
var PARAMS4 = _Object_({
|
|
5975
|
+
action: Union([Literal("list"), Literal("add"), Literal("toggle"), Literal("clear")]),
|
|
5976
|
+
text: Optional(String2({ description: "Todo text (for add)" })),
|
|
5977
|
+
id: Optional(Number2({ description: "Todo ID (for toggle)" }))
|
|
5978
|
+
});
|
|
5979
|
+
function asSnapshot(value) {
|
|
5980
|
+
const v = value;
|
|
5981
|
+
if (!v || !Array.isArray(v.todos) || typeof v.nextId !== "number")
|
|
5982
|
+
return;
|
|
5983
|
+
return { todos: v.todos, nextId: v.nextId };
|
|
5984
|
+
}
|
|
5985
|
+
function formatTodos(todos) {
|
|
5986
|
+
return todos.length ? todos.map((t) => `[${t.done ? "x" : " "}] #${t.id}: ${t.text}`).join(`
|
|
5987
|
+
`) : "No todos";
|
|
5988
|
+
}
|
|
5989
|
+
function styleFrom(theme) {
|
|
5990
|
+
return {
|
|
5991
|
+
bold: (s) => theme.bold(s),
|
|
5992
|
+
dim: (s) => theme.fg("dim", s),
|
|
5993
|
+
accent: (s) => theme.fg("accent", s),
|
|
5994
|
+
error: (s) => theme.fg("error", s)
|
|
5995
|
+
};
|
|
5996
|
+
}
|
|
5997
|
+
function createTodoToolExtension(options = {}) {
|
|
5998
|
+
const timers = options.timers ?? realTimers;
|
|
5999
|
+
return {
|
|
6000
|
+
name: "free-pi-todo",
|
|
6001
|
+
factory(pi) {
|
|
6002
|
+
let todos = [];
|
|
6003
|
+
let nextId = 1;
|
|
6004
|
+
let widgetCtx;
|
|
6005
|
+
let lingerTimer;
|
|
6006
|
+
let orderTimer;
|
|
6007
|
+
const stopTimers = () => {
|
|
6008
|
+
if (lingerTimer !== undefined)
|
|
6009
|
+
timers.clearTimeout(lingerTimer);
|
|
6010
|
+
if (orderTimer !== undefined)
|
|
6011
|
+
timers.clearInterval(orderTimer);
|
|
6012
|
+
lingerTimer = orderTimer = undefined;
|
|
6013
|
+
};
|
|
6014
|
+
const hideWidget = () => {
|
|
6015
|
+
stopTimers();
|
|
6016
|
+
try {
|
|
6017
|
+
widgetCtx?.ui.setWidget(TODO_WIDGET_ID, undefined);
|
|
6018
|
+
} catch {}
|
|
6019
|
+
};
|
|
6020
|
+
const showWidget = () => {
|
|
6021
|
+
try {
|
|
6022
|
+
widgetCtx?.ui.setWidget(TODO_WIDGET_ID, (_tui, theme) => {
|
|
6023
|
+
const style = styleFrom(theme);
|
|
6024
|
+
return {
|
|
6025
|
+
render: (width) => widgetLines(todos, Math.max(1, width - 2), style).map((l) => ` ${l}`),
|
|
6026
|
+
invalidate() {}
|
|
6027
|
+
};
|
|
6028
|
+
});
|
|
6029
|
+
} catch {}
|
|
6030
|
+
};
|
|
6031
|
+
const refreshWidget = (ctx, replay = false) => {
|
|
6032
|
+
if (ctx?.mode !== "tui")
|
|
6033
|
+
return;
|
|
6034
|
+
widgetCtx = ctx;
|
|
6035
|
+
const hasOpen = todos.some((t) => !t.done);
|
|
6036
|
+
if (todos.length === 0 || !hasOpen && replay) {
|
|
6037
|
+
hideWidget();
|
|
6038
|
+
return;
|
|
6039
|
+
}
|
|
6040
|
+
if (lingerTimer !== undefined) {
|
|
6041
|
+
timers.clearTimeout(lingerTimer);
|
|
6042
|
+
lingerTimer = undefined;
|
|
6043
|
+
}
|
|
6044
|
+
showWidget();
|
|
6045
|
+
if (orderTimer === undefined)
|
|
6046
|
+
orderTimer = timers.setInterval(showWidget, REASSERT_ORDER_MS);
|
|
6047
|
+
if (!hasOpen) {
|
|
6048
|
+
lingerTimer = timers.setTimeout(() => {
|
|
6049
|
+
lingerTimer = undefined;
|
|
6050
|
+
hideWidget();
|
|
6051
|
+
}, ALL_DONE_LINGER_MS);
|
|
6052
|
+
}
|
|
6053
|
+
};
|
|
6054
|
+
const reconstructState = (ctx) => {
|
|
6055
|
+
todos = [];
|
|
6056
|
+
nextId = 1;
|
|
6057
|
+
for (const entry of ctx.sessionManager.getBranch()) {
|
|
6058
|
+
let snap;
|
|
6059
|
+
if (entry.type === "custom" && entry.customType === TODO_ENTRY_TYPE)
|
|
6060
|
+
snap = asSnapshot(entry.data);
|
|
6061
|
+
else if (entry.type === "message" && entry.message.role === "toolResult" && entry.message.toolName === TODO_TOOL_NAME)
|
|
6062
|
+
snap = asSnapshot(entry.message.details);
|
|
6063
|
+
if (snap) {
|
|
6064
|
+
todos = snap.todos.map((t) => ({ ...t }));
|
|
6065
|
+
nextId = snap.nextId;
|
|
6066
|
+
}
|
|
6067
|
+
}
|
|
6068
|
+
refreshWidget(ctx, true);
|
|
6069
|
+
};
|
|
6070
|
+
pi.on("session_start", async (_event, ctx) => reconstructState(ctx));
|
|
6071
|
+
pi.on("session_tree", async (_event, ctx) => reconstructState(ctx));
|
|
6072
|
+
pi.on("session_shutdown", async () => {
|
|
6073
|
+
stopTimers();
|
|
6074
|
+
widgetCtx = undefined;
|
|
6075
|
+
});
|
|
6076
|
+
const result = (ctx, text, action, error, item) => {
|
|
6077
|
+
const snapshot = { todos: todos.map((t) => ({ ...t })), nextId };
|
|
6078
|
+
if (action !== "list" && !error)
|
|
6079
|
+
pi.appendEntry(TODO_ENTRY_TYPE, snapshot);
|
|
6080
|
+
if (action !== "list" && !error)
|
|
6081
|
+
refreshWidget(ctx);
|
|
6082
|
+
return {
|
|
6083
|
+
content: [{ type: "text", text }],
|
|
6084
|
+
details: { action, ...snapshot, ...item ? { item: { ...item } } : {}, ...error ? { error } : {} }
|
|
6085
|
+
};
|
|
6086
|
+
};
|
|
6087
|
+
pi.registerTool({
|
|
6088
|
+
name: TODO_TOOL_NAME,
|
|
6089
|
+
label: "Todo",
|
|
6090
|
+
description: "Manage a todo list. Actions: list, add (text), toggle (id), clear",
|
|
6091
|
+
promptSnippet: "Track your own multi-step work in a todo list: add items, toggle them done, list them",
|
|
6092
|
+
parameters: PARAMS4,
|
|
6093
|
+
async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
|
|
6094
|
+
switch (params.action) {
|
|
6095
|
+
case "list":
|
|
6096
|
+
return result(ctx, formatTodos(todos), "list");
|
|
6097
|
+
case "add": {
|
|
6098
|
+
if (!params.text)
|
|
6099
|
+
return result(ctx, "Error: text required for add", "add", "text required");
|
|
6100
|
+
const todo = { id: nextId++, text: params.text, done: false };
|
|
6101
|
+
todos.push(todo);
|
|
6102
|
+
return result(ctx, `Added todo #${todo.id}: ${todo.text}`, "add", undefined, todo);
|
|
6103
|
+
}
|
|
6104
|
+
case "toggle": {
|
|
6105
|
+
if (params.id === undefined)
|
|
6106
|
+
return result(ctx, "Error: id required for toggle", "toggle", "id required");
|
|
6107
|
+
const todo = todos.find((t) => t.id === params.id);
|
|
6108
|
+
if (!todo)
|
|
6109
|
+
return result(ctx, `Todo #${params.id} not found`, "toggle", `#${params.id} not found`);
|
|
6110
|
+
todo.done = !todo.done;
|
|
6111
|
+
return result(ctx, `Todo #${todo.id} ${todo.done ? "completed" : "uncompleted"}`, "toggle", undefined, todo);
|
|
6112
|
+
}
|
|
6113
|
+
case "clear": {
|
|
6114
|
+
const count = todos.length;
|
|
6115
|
+
todos = [];
|
|
6116
|
+
nextId = 1;
|
|
6117
|
+
return result(ctx, `Cleared ${count} todos`, "clear");
|
|
6118
|
+
}
|
|
6119
|
+
default:
|
|
6120
|
+
return result(ctx, `Unknown action: ${String(params.action)}`, "list", "unknown action");
|
|
6121
|
+
}
|
|
6122
|
+
},
|
|
6123
|
+
renderCall() {
|
|
6124
|
+
return { render: () => [], invalidate() {} };
|
|
6125
|
+
},
|
|
6126
|
+
renderResult(res, _opts, theme) {
|
|
6127
|
+
const details = res.details;
|
|
6128
|
+
const lines = details?.action ? resultLines(details, styleFrom(theme)) : [res.content[0]?.type === "text" ? res.content[0].text : ""];
|
|
6129
|
+
return { render: () => lines, invalidate() {} };
|
|
6130
|
+
}
|
|
6131
|
+
});
|
|
6132
|
+
pi.registerCommand("todos", {
|
|
6133
|
+
description: "Show all todos on the current branch",
|
|
6134
|
+
handler: async (_args, ctx) => {
|
|
6135
|
+
ctx.ui.notify(formatTodos(todos), "info");
|
|
6136
|
+
}
|
|
6137
|
+
});
|
|
6138
|
+
}
|
|
6139
|
+
};
|
|
6140
|
+
}
|
|
6141
|
+
|
|
5633
6142
|
// src/error-notice.ts
|
|
5634
6143
|
var NOTICE_DEDUPE_MS = 15000;
|
|
5635
6144
|
function parseErrorEnvelope(errorMessage) {
|
|
@@ -5685,12 +6194,16 @@ async function reportToolGuardTrigger(opts, toolName) {
|
|
|
5685
6194
|
});
|
|
5686
6195
|
} catch {}
|
|
5687
6196
|
}
|
|
6197
|
+
var MCP_RESOURCE_TOOLS = ["list_mcp_resources", "list_mcp_resource_templates", "read_mcp_resource"];
|
|
6198
|
+
function isAllowed(opts, name) {
|
|
6199
|
+
return opts.allowedTools.includes(name) || name.startsWith("mcp__") || MCP_RESOURCE_TOOLS.includes(name);
|
|
6200
|
+
}
|
|
5688
6201
|
function createToolGuardExtension(opts) {
|
|
5689
6202
|
return {
|
|
5690
6203
|
name: "free-pi-tool-guard",
|
|
5691
6204
|
factory(pi) {
|
|
5692
6205
|
pi.on("tool_call", (event) => {
|
|
5693
|
-
if (opts
|
|
6206
|
+
if (isAllowed(opts, event.toolName))
|
|
5694
6207
|
return;
|
|
5695
6208
|
reportToolGuardTrigger(opts, event.toolName);
|
|
5696
6209
|
return { block: true, reason: "tool not permitted in the free-pi distro" };
|
|
@@ -5699,8 +6212,90 @@ function createToolGuardExtension(opts) {
|
|
|
5699
6212
|
};
|
|
5700
6213
|
}
|
|
5701
6214
|
|
|
6215
|
+
// src/bug-report.ts
|
|
6216
|
+
var BUG_USAGE = "Usage: /bug <what went wrong>";
|
|
6217
|
+
var BUG_DESCRIPTION = "Report a bug to free-pi";
|
|
6218
|
+
function buildBugRequest(description, ctx) {
|
|
6219
|
+
return { description, session_id: ctx.sessionId, cli_version: CLI_VERSION, model: ctx.model };
|
|
6220
|
+
}
|
|
6221
|
+
function mapBugFailure(status) {
|
|
6222
|
+
if (status === 401)
|
|
6223
|
+
return "Couldn't send the bug report: you're signed out. Restart free-pi-cli to sign in again.";
|
|
6224
|
+
if (status === 429)
|
|
6225
|
+
return "Couldn't send the bug report: you've reached today's limit of 20 reports. Try again tomorrow.";
|
|
6226
|
+
if (status === 413)
|
|
6227
|
+
return "Couldn't send the bug report: it is too large.";
|
|
6228
|
+
if (status === 400)
|
|
6229
|
+
return "Couldn't send the bug report: the description is too long (4000 characters max).";
|
|
6230
|
+
return `Couldn't send the bug report (the server returned ${status}). Try again in a moment.`;
|
|
6231
|
+
}
|
|
6232
|
+
async function sendBugReport(opts, body) {
|
|
6233
|
+
try {
|
|
6234
|
+
const res = await (opts.fetchImpl ?? fetch)(`${opts.baseUrl}/bug-reports`, {
|
|
6235
|
+
method: "POST",
|
|
6236
|
+
headers: { Authorization: `Bearer ${await opts.getToken()}`, "Content-Type": "application/json" },
|
|
6237
|
+
body: JSON.stringify(body),
|
|
6238
|
+
signal: AbortSignal.timeout(1e4)
|
|
6239
|
+
});
|
|
6240
|
+
if (!res.ok)
|
|
6241
|
+
return { ok: false, message: mapBugFailure(res.status) };
|
|
6242
|
+
const parsed = BugReportResponseSchema.safeParse(await res.json());
|
|
6243
|
+
if (!parsed.success)
|
|
6244
|
+
return { ok: false, message: mapBugFailure(res.status) };
|
|
6245
|
+
return { ok: true, id: parsed.data.id };
|
|
6246
|
+
} catch {
|
|
6247
|
+
return { ok: false, message: "Couldn't reach free-pi to send the bug report — check your connection." };
|
|
6248
|
+
}
|
|
6249
|
+
}
|
|
6250
|
+
function makeBugHandler(mode, opts) {
|
|
6251
|
+
return async (hint) => {
|
|
6252
|
+
if (!hint)
|
|
6253
|
+
return mode.showStatus(BUG_USAGE);
|
|
6254
|
+
const r = await sendBugReport(opts, buildBugRequest(hint, { sessionId: mode.session.sessionId, model: mode.session.model?.id }));
|
|
6255
|
+
if (r.ok)
|
|
6256
|
+
return mode.showStatus(`Thanks — bug report received (#${r.id}).`);
|
|
6257
|
+
if (!mode.editor.getText())
|
|
6258
|
+
mode.editor.setText(`/bug ${hint}`);
|
|
6259
|
+
mode.showError(r.message);
|
|
6260
|
+
};
|
|
6261
|
+
}
|
|
6262
|
+
function installBugHandler(mode, opts) {
|
|
6263
|
+
mode.handleBugCommand = makeBugHandler(mode, opts);
|
|
6264
|
+
}
|
|
6265
|
+
function registerBugAutocomplete(pi) {
|
|
6266
|
+
pi.on("session_start", (_event, ctx) => {
|
|
6267
|
+
ctx.ui.addAutocompleteProvider((cur) => ({
|
|
6268
|
+
triggerCharacters: cur.triggerCharacters,
|
|
6269
|
+
applyCompletion: (...a) => cur.applyCompletion(...a),
|
|
6270
|
+
shouldTriggerFileCompletion: cur.shouldTriggerFileCompletion?.bind(cur),
|
|
6271
|
+
getSuggestions: async (...a) => {
|
|
6272
|
+
const r = await cur.getSuggestions(...a);
|
|
6273
|
+
return r && {
|
|
6274
|
+
...r,
|
|
6275
|
+
items: r.items.map((i) => i.value === "bug" && i.label === "bug" ? { ...i, description: BUG_DESCRIPTION } : i)
|
|
6276
|
+
};
|
|
6277
|
+
}
|
|
6278
|
+
}));
|
|
6279
|
+
});
|
|
6280
|
+
}
|
|
6281
|
+
|
|
5702
6282
|
// src/changelog.ts
|
|
5703
6283
|
var CHANGELOG_HIGHLIGHTS = [
|
|
6284
|
+
{
|
|
6285
|
+
version: "0.4.0",
|
|
6286
|
+
highlights: [
|
|
6287
|
+
"The agent now keeps a todo list for multi-step work: it shows live above the input box, and /todos shows the full list.",
|
|
6288
|
+
"Add your own MCP servers: `npx free-pi-cli mcp add`, then /mcp in a session to manage them."
|
|
6289
|
+
]
|
|
6290
|
+
},
|
|
6291
|
+
{
|
|
6292
|
+
version: "0.3.0",
|
|
6293
|
+
highlights: [
|
|
6294
|
+
"Upgraded the underlying pi agent engine to 1.2.0.",
|
|
6295
|
+
"/bug now sends reports to the free-pi team.",
|
|
6296
|
+
"`npx free-pi-cli --session <id>` resumes a session again."
|
|
6297
|
+
]
|
|
6298
|
+
},
|
|
5704
6299
|
{
|
|
5705
6300
|
version: "0.2.19",
|
|
5706
6301
|
highlights: [
|
|
@@ -5911,6 +6506,7 @@ function createFreePiCommandsExtension(opts) {
|
|
|
5911
6506
|
return {
|
|
5912
6507
|
name: "free-pi-commands",
|
|
5913
6508
|
factory(pi) {
|
|
6509
|
+
registerBugAutocomplete(pi);
|
|
5914
6510
|
pi.registerCommand("close-other-session", {
|
|
5915
6511
|
description: "Clear your other running free-pi session (use if a previous one is stuck).",
|
|
5916
6512
|
handler: (_args, ctx) => closeOtherSession(opts, ctx)
|
|
@@ -5974,12 +6570,12 @@ function updateBannerLines(theme, latest, width) {
|
|
|
5974
6570
|
return [
|
|
5975
6571
|
"",
|
|
5976
6572
|
border,
|
|
5977
|
-
theme.bold(theme.fg("warning",
|
|
5978
|
-
theme.fg("dim",
|
|
6573
|
+
theme.bold(theme.fg("warning", truncate3(headline, inner))),
|
|
6574
|
+
theme.fg("dim", truncate3(detail, inner)),
|
|
5979
6575
|
border
|
|
5980
6576
|
];
|
|
5981
6577
|
}
|
|
5982
|
-
function
|
|
6578
|
+
function truncate3(text, maxWidth) {
|
|
5983
6579
|
if (maxWidth <= 0)
|
|
5984
6580
|
return "";
|
|
5985
6581
|
if (text.length <= maxWidth)
|
|
@@ -5990,7 +6586,7 @@ function truncate2(text, maxWidth) {
|
|
|
5990
6586
|
}
|
|
5991
6587
|
function headerLines(theme, modelName, expanded, width = Number.POSITIVE_INFINITY, updateLatest) {
|
|
5992
6588
|
const inner = width - 1;
|
|
5993
|
-
const fit = (plain, styled) => plain.length <= inner ? styled() : theme.fg("dim",
|
|
6589
|
+
const fit = (plain, styled) => plain.length <= inner ? styled() : theme.fg("dim", truncate3(plain, inner));
|
|
5994
6590
|
const titleRest = ` · ${modelName} · ctrl+o for help`;
|
|
5995
6591
|
const title = fit(`free-pi${titleRest}`, () => `${theme.bold(theme.fg("accent", "free-pi"))}${theme.fg("dim", titleRest)}`);
|
|
5996
6592
|
const commandLines = COMMANDS.map(({ name, description }) => {
|
|
@@ -6054,7 +6650,7 @@ function resolveFreePiScope(modelRuntime, providerName, modelIds, env = process.
|
|
|
6054
6650
|
|
|
6055
6651
|
// src/resume-line.ts
|
|
6056
6652
|
var RESUME_LABEL = "To resume this session:";
|
|
6057
|
-
var RESUME_COMMAND_RE = /(To resume this session:(?:\u001b\[[0-9;]*m|\s)*)pi(?=\s)
|
|
6653
|
+
var RESUME_COMMAND_RE = /(To resume this session:(?:\u001b\[[0-9;]*m|\s)*)pi(?=\s)( --session-dir (?:'(?:[^']|'\\'')*'|\S+))?/g;
|
|
6058
6654
|
function rewriteResumeLine(chunk) {
|
|
6059
6655
|
if (!chunk.includes(RESUME_LABEL))
|
|
6060
6656
|
return chunk;
|
|
@@ -6072,6 +6668,48 @@ function patchResumeLine(stream) {
|
|
|
6072
6668
|
};
|
|
6073
6669
|
}
|
|
6074
6670
|
|
|
6671
|
+
// src/project-trust.ts
|
|
6672
|
+
import { createInterface } from "node:readline/promises";
|
|
6673
|
+
import { resolve } from "node:path";
|
|
6674
|
+
import { hasTrustRequiringProjectResources, ProjectTrustStore } from "@earendil-works/pi-coding-agent";
|
|
6675
|
+
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] ";
|
|
6676
|
+
async function decideProjectTrust(cwd, agentDir, deps) {
|
|
6677
|
+
if (!(deps.hasTrustRequiringResources ?? hasTrustRequiringProjectResources)(cwd))
|
|
6678
|
+
return true;
|
|
6679
|
+
const store = new ProjectTrustStore(agentDir);
|
|
6680
|
+
const saved = store.get(cwd);
|
|
6681
|
+
if (typeof saved === "boolean")
|
|
6682
|
+
return saved;
|
|
6683
|
+
if (!deps.stdinIsTTY) {
|
|
6684
|
+
deps.log("This folder has project settings (.pi/ or .agents/skills); they are ignored because it is not trusted.");
|
|
6685
|
+
return false;
|
|
6686
|
+
}
|
|
6687
|
+
const decision = /^y(es)?$/i.test((await deps.askLine(PROJECT_TRUST_PROMPT)).trim());
|
|
6688
|
+
store.set(cwd, decision);
|
|
6689
|
+
return decision;
|
|
6690
|
+
}
|
|
6691
|
+
function decideProjectTrustInteractive(cwd, agentDir) {
|
|
6692
|
+
return decideProjectTrust(cwd, agentDir, {
|
|
6693
|
+
stdinIsTTY: process.stdin.isTTY === true,
|
|
6694
|
+
log: (message) => console.log(message),
|
|
6695
|
+
askLine: async (prompt) => {
|
|
6696
|
+
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
6697
|
+
try {
|
|
6698
|
+
return await rl.question(prompt);
|
|
6699
|
+
} finally {
|
|
6700
|
+
rl.close();
|
|
6701
|
+
}
|
|
6702
|
+
}
|
|
6703
|
+
});
|
|
6704
|
+
}
|
|
6705
|
+
function projectTrustForCwd(cwd, launch, agentDir, hasTrustRequiringResources = hasTrustRequiringProjectResources) {
|
|
6706
|
+
if (resolve(cwd) === resolve(launch.cwd))
|
|
6707
|
+
return launch.trusted;
|
|
6708
|
+
if (!hasTrustRequiringResources(cwd))
|
|
6709
|
+
return true;
|
|
6710
|
+
return new ProjectTrustStore(agentDir).get(cwd) === true;
|
|
6711
|
+
}
|
|
6712
|
+
|
|
6075
6713
|
// src/pi-launch.ts
|
|
6076
6714
|
function catalogModelsFor(opts) {
|
|
6077
6715
|
if (opts.models && opts.models.length > 0)
|
|
@@ -6079,11 +6717,42 @@ function catalogModelsFor(opts) {
|
|
|
6079
6717
|
const id = opts.model || MODEL_ID;
|
|
6080
6718
|
return [{ id, name: id }];
|
|
6081
6719
|
}
|
|
6082
|
-
var ALLOWED_TOOL_NAMES = [
|
|
6720
|
+
var ALLOWED_TOOL_NAMES = [
|
|
6721
|
+
...SAFE_TOOLS,
|
|
6722
|
+
USAGE_TOOL_NAME,
|
|
6723
|
+
BUY_TOOL_NAME,
|
|
6724
|
+
DOCS_TOOL_NAME,
|
|
6725
|
+
TODO_TOOL_NAME,
|
|
6726
|
+
"codemode",
|
|
6727
|
+
"tool_search"
|
|
6728
|
+
];
|
|
6729
|
+
var SDK_TOOLS = [
|
|
6730
|
+
...ALLOWED_TOOL_NAMES,
|
|
6731
|
+
"mcp__*",
|
|
6732
|
+
"list_mcp_resources",
|
|
6733
|
+
"list_mcp_resource_templates",
|
|
6734
|
+
"read_mcp_resource"
|
|
6735
|
+
];
|
|
6083
6736
|
function resolveModelName(opts) {
|
|
6084
6737
|
return opts.models?.[0]?.name || opts.model || MODEL_ID;
|
|
6085
6738
|
}
|
|
6739
|
+
function buildSettingsManager(opts, models, projectTrusted) {
|
|
6740
|
+
return SettingsManager.inMemory({
|
|
6741
|
+
defaultProvider: PROVIDER_NAME,
|
|
6742
|
+
defaultModel: models[0].id,
|
|
6743
|
+
defaultTools: [...SAFE_TOOLS, TODO_TOOL_NAME],
|
|
6744
|
+
packages: [],
|
|
6745
|
+
quietStartup: true,
|
|
6746
|
+
tuiMode: "regular",
|
|
6747
|
+
theme: "light/dark",
|
|
6748
|
+
enableInstallTelemetry: false
|
|
6749
|
+
}, { projectTrusted });
|
|
6750
|
+
}
|
|
6751
|
+
function settingsForRuntime(opts, rtCwd, launch) {
|
|
6752
|
+
return buildSettingsManager(opts, catalogModelsFor(opts), projectTrustForCwd(rtCwd, launch, opts.agentDir));
|
|
6753
|
+
}
|
|
6086
6754
|
function buildRuntimeOptions(opts, sessionId) {
|
|
6755
|
+
process.env.PI_CODING_AGENT_DIR = opts.agentDir;
|
|
6087
6756
|
const models = catalogModelsFor(opts);
|
|
6088
6757
|
const providerExtension = {
|
|
6089
6758
|
name: "free-pi-provider",
|
|
@@ -6124,6 +6793,10 @@ function buildRuntimeOptions(opts, sessionId) {
|
|
|
6124
6793
|
modelName: resolveModelName(opts),
|
|
6125
6794
|
updateLatest: opts.updateLatest
|
|
6126
6795
|
});
|
|
6796
|
+
const todoToolExtension = createTodoToolExtension();
|
|
6797
|
+
const mcpExtension = { name: "pi-mcp", factory: createMcpExtension() };
|
|
6798
|
+
const codemodeExtension = { name: "pi-codemode", factory: createCodemodeExtension({ models: false }) };
|
|
6799
|
+
const toolSearchExtension = { name: "pi-tool-search", factory: createToolSearchExtension() };
|
|
6127
6800
|
const extensionFactories = [
|
|
6128
6801
|
providerExtension,
|
|
6129
6802
|
adsExtension,
|
|
@@ -6133,26 +6806,24 @@ function buildRuntimeOptions(opts, sessionId) {
|
|
|
6133
6806
|
errorNoticeExtension,
|
|
6134
6807
|
toolGuardExtension,
|
|
6135
6808
|
commandsExtension,
|
|
6136
|
-
headerExtension
|
|
6809
|
+
headerExtension,
|
|
6810
|
+
todoToolExtension,
|
|
6811
|
+
mcpExtension,
|
|
6812
|
+
codemodeExtension,
|
|
6813
|
+
toolSearchExtension
|
|
6137
6814
|
];
|
|
6138
|
-
const settingsManager =
|
|
6139
|
-
defaultProvider: PROVIDER_NAME,
|
|
6140
|
-
defaultModel: models[0].id,
|
|
6141
|
-
defaultTools: [...SAFE_TOOLS],
|
|
6142
|
-
packages: [],
|
|
6143
|
-
quietStartup: true
|
|
6144
|
-
});
|
|
6815
|
+
const settingsManager = buildSettingsManager(opts, models, opts.projectTrusted === true);
|
|
6145
6816
|
return {
|
|
6146
6817
|
settingsManager,
|
|
6147
6818
|
resourceLoaderOptions: { extensionFactories },
|
|
6148
6819
|
extensionNames: extensionFactories.map((e) => e.name),
|
|
6149
|
-
tools:
|
|
6820
|
+
tools: SDK_TOOLS
|
|
6150
6821
|
};
|
|
6151
6822
|
}
|
|
6152
6823
|
function getSessionDir(cwd, agentDir) {
|
|
6153
|
-
const resolvedCwd =
|
|
6824
|
+
const resolvedCwd = resolve2(cwd);
|
|
6154
6825
|
const safePath = `--${resolvedCwd.replace(/^[/\\]/, "").replace(/[/\\:]/g, "-")}--`;
|
|
6155
|
-
return
|
|
6826
|
+
return join3(resolve2(agentDir), "sessions", safePath);
|
|
6156
6827
|
}
|
|
6157
6828
|
async function createSessionManager(cwd, sessionDir, sessionId, api = SessionManager) {
|
|
6158
6829
|
if (sessionId) {
|
|
@@ -6166,6 +6837,7 @@ async function createSessionManager(cwd, sessionDir, sessionId, api = SessionMan
|
|
|
6166
6837
|
return api.create(cwd, sessionDir);
|
|
6167
6838
|
}
|
|
6168
6839
|
process.env.PI_SKIP_VERSION_CHECK = "1";
|
|
6840
|
+
process.env.PI_TELEMETRY = "0";
|
|
6169
6841
|
patchResumeLine(process.stdout);
|
|
6170
6842
|
async function launchPi(opts) {
|
|
6171
6843
|
const cwd = process.cwd();
|
|
@@ -6173,7 +6845,9 @@ async function launchPi(opts) {
|
|
|
6173
6845
|
const sm = await createSessionManager(cwd, sessionDir, opts.session);
|
|
6174
6846
|
const sessionId = sm.getSessionId();
|
|
6175
6847
|
const scopeIds = catalogModelsFor(opts).map((m) => m.id);
|
|
6176
|
-
const
|
|
6848
|
+
const projectTrusted = await decideProjectTrustInteractive(cwd, opts.agentDir);
|
|
6849
|
+
const { resourceLoaderOptions, tools } = buildRuntimeOptions({ ...opts, projectTrusted }, sessionId);
|
|
6850
|
+
const launchTrust = { cwd, trusted: projectTrusted };
|
|
6177
6851
|
const createRuntime = async ({
|
|
6178
6852
|
cwd: rtCwd,
|
|
6179
6853
|
sessionManager,
|
|
@@ -6182,7 +6856,7 @@ async function launchPi(opts) {
|
|
|
6182
6856
|
const services = await createAgentSessionServices({
|
|
6183
6857
|
cwd: rtCwd,
|
|
6184
6858
|
agentDir: opts.agentDir,
|
|
6185
|
-
settingsManager,
|
|
6859
|
+
settingsManager: settingsForRuntime(opts, rtCwd, launchTrust),
|
|
6186
6860
|
resourceLoaderOptions
|
|
6187
6861
|
});
|
|
6188
6862
|
const scopedModels = resolveFreePiScope(services.modelRuntime, PROVIDER_NAME, scopeIds);
|
|
@@ -6207,20 +6881,22 @@ async function launchPi(opts) {
|
|
|
6207
6881
|
initialImages: [],
|
|
6208
6882
|
initialMessages: []
|
|
6209
6883
|
});
|
|
6884
|
+
installBugHandler(mode, { baseUrl: opts.baseUrl, getToken: () => opts.jwt });
|
|
6210
6885
|
await mode.run();
|
|
6211
6886
|
return sm.getSessionId();
|
|
6212
6887
|
}
|
|
6213
6888
|
|
|
6214
6889
|
// src/prompt.ts
|
|
6215
|
-
import { createInterface } from "node:readline/promises";
|
|
6890
|
+
import { createInterface as createInterface2 } from "node:readline/promises";
|
|
6216
6891
|
|
|
6217
6892
|
// src/consent.ts
|
|
6218
6893
|
var CONSENT_TEXT = `
|
|
6219
6894
|
free-pi — before you start
|
|
6220
6895
|
|
|
6221
6896
|
* Inference is free. Ads pay for it, and so does training.
|
|
6222
|
-
* Ad cards render in the terminal UI.
|
|
6223
|
-
|
|
6897
|
+
* Ad cards render in the terminal UI. Some ads come from a third-party ad
|
|
6898
|
+
provider (Gravity), which receives excerpts of your recent conversation
|
|
6899
|
+
to pick a relevant ad.
|
|
6224
6900
|
* Your coding sessions (prompts, code, files, and model responses) are saved
|
|
6225
6901
|
as traces and used to train models. We may share or sell that data to
|
|
6226
6902
|
third parties. Once you accept, there is no opt-out, except where the law
|
|
@@ -6239,7 +6915,7 @@ and the Privacy Policy (https://freepi.ai/privacy).
|
|
|
6239
6915
|
|
|
6240
6916
|
Decline and nothing is sent to the free-pi server.
|
|
6241
6917
|
`.trim();
|
|
6242
|
-
var CONSENT_VERSION = "
|
|
6918
|
+
var CONSENT_VERSION = "3";
|
|
6243
6919
|
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.";
|
|
6244
6920
|
var CONSENT_PROMPT = "Accept? [y/N] ";
|
|
6245
6921
|
function parseConsentAnswer(answer) {
|
|
@@ -6261,7 +6937,7 @@ async function promptConsentInteractive() {
|
|
|
6261
6937
|
stdinIsTTY: process.stdin.isTTY === true,
|
|
6262
6938
|
log: (message) => console.log(message),
|
|
6263
6939
|
askLine: async (prompt) => {
|
|
6264
|
-
const rl =
|
|
6940
|
+
const rl = createInterface2({ input: process.stdin, output: process.stdout });
|
|
6265
6941
|
try {
|
|
6266
6942
|
return await rl.question(prompt);
|
|
6267
6943
|
} finally {
|
|
@@ -6396,12 +7072,12 @@ ${INTRO_TEXT}
|
|
|
6396
7072
|
}
|
|
6397
7073
|
|
|
6398
7074
|
// src/whats-new.ts
|
|
6399
|
-
import { existsSync, mkdirSync, readFileSync as readFileSync2, writeFileSync } from "node:fs";
|
|
6400
|
-
import { dirname as
|
|
7075
|
+
import { existsSync as existsSync2, mkdirSync, readFileSync as readFileSync2, writeFileSync } from "node:fs";
|
|
7076
|
+
import { dirname as dirname3 } from "node:path";
|
|
6401
7077
|
function maybeWhatsNew(cliVersion, agentDir) {
|
|
6402
7078
|
try {
|
|
6403
7079
|
const path = getLastSeenVersionPath(agentDir);
|
|
6404
|
-
const lastSeen =
|
|
7080
|
+
const lastSeen = existsSync2(path) ? readFileSync2(path, "utf-8").trim() : undefined;
|
|
6405
7081
|
writeLastSeen(path, cliVersion);
|
|
6406
7082
|
if (lastSeen === undefined)
|
|
6407
7083
|
return;
|
|
@@ -6417,7 +7093,7 @@ function maybeWhatsNew(cliVersion, agentDir) {
|
|
|
6417
7093
|
}
|
|
6418
7094
|
}
|
|
6419
7095
|
function writeLastSeen(path, version) {
|
|
6420
|
-
mkdirSync(
|
|
7096
|
+
mkdirSync(dirname3(path), { recursive: true });
|
|
6421
7097
|
writeFileSync(path, version, "utf-8");
|
|
6422
7098
|
}
|
|
6423
7099
|
|
|
@@ -6606,6 +7282,15 @@ ${HELP_TEXT}`);
|
|
|
6606
7282
|
});
|
|
6607
7283
|
process.exit(logoutCode);
|
|
6608
7284
|
}
|
|
7285
|
+
if (command.kind === "mcp") {
|
|
7286
|
+
const mcpCode = await runFreePiMcp(command.args, {
|
|
7287
|
+
cwd: process.cwd(),
|
|
7288
|
+
agentDir: getFreePiAgentDir(),
|
|
7289
|
+
log: (line) => console.log(line),
|
|
7290
|
+
error: (line) => console.error(line)
|
|
7291
|
+
});
|
|
7292
|
+
process.exit(mcpCode);
|
|
7293
|
+
}
|
|
6609
7294
|
const agentDir = getFreePiAgentDir();
|
|
6610
7295
|
const code = await run({
|
|
6611
7296
|
baseUrl: resolveBaseUrl(),
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "free-pi-cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
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",
|
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
"prepublishOnly": "bun run build"
|
|
20
20
|
},
|
|
21
21
|
"dependencies": {
|
|
22
|
-
"@earendil-works/pi-coding-agent": "
|
|
22
|
+
"@earendil-works/pi-coding-agent": "1.2.0"
|
|
23
23
|
},
|
|
24
24
|
"devDependencies": {
|
|
25
25
|
"@freepi/pi-ads": "workspace:*",
|