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 CHANGED
@@ -27,4 +27,4 @@ if (err) {
27
27
  console.error(err);
28
28
  process.exit(1);
29
29
  }
30
- await import("./main-376chsck.js");
30
+ await import("./main-r8jqgh5n.js");
@@ -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.3.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 join(homedir(), FREE_PI_DIR_NAME, "agent");
212
+ return join2(homedir(), FREE_PI_DIR_NAME, "agent");
172
213
  }
173
214
  function getCredentialsPath(agentDir = getFreePiAgentDir()) {
174
- return join(agentDir, "credentials.json");
215
+ return join2(agentDir, "credentials.json");
175
216
  }
176
217
  function getLastSeenVersionPath(agentDir = getFreePiAgentDir()) {
177
- return join(agentDir, "last-seen-version");
218
+ return join2(agentDir, "last-seen-version");
178
219
  }
179
220
  function getConfigPath() {
180
- return join(homedir(), FREE_PI_DIR_NAME, "config.json");
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(2.5),
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.5),
4896
- tierCapYoungUsd: number2().default(2),
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 tokens today, resets at 00:00 UTC";
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 join2, resolve } from "node:path";
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
- `today: ${percentUsed(stats.spent_usd_today, stats.cap_usd_today)}% of your free daily allowance used`,
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.allowedTools.includes(event.toolName))
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 how much of today's free allowance you have used, and your purchased credit.",
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: "spend and remaining budget today" },
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", truncate2(headline, inner))),
6072
- theme.fg("dim", truncate2(detail, inner)),
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 truncate2(text, maxWidth) {
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", truncate2(plain, inner));
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 = [...SAFE_TOOLS, USAGE_TOOL_NAME, BUY_TOOL_NAME, DOCS_TOOL_NAME];
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 = SettingsManager.inMemory({
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: ALLOWED_TOOL_NAMES
6899
+ tools: SDK_TOOLS
6247
6900
  };
6248
6901
  }
6249
6902
  function getSessionDir(cwd, agentDir) {
6250
- const resolvedCwd = resolve(cwd);
6903
+ const resolvedCwd = resolve2(cwd);
6251
6904
  const safePath = `--${resolvedCwd.replace(/^[/\\]/, "").replace(/[/\\:]/g, "-")}--`;
6252
- return join2(resolve(agentDir), "sessions", safePath);
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 { settingsManager, resourceLoaderOptions, tools } = buildRuntimeOptions(opts, sessionId);
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 = createInterface({ input: process.stdin, output: process.stdout });
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 dirname2 } from "node:path";
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 = existsSync(path) ? readFileSync2(path, "utf-8").trim() : undefined;
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(dirname2(path), { recursive: true });
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(),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "free-pi-cli",
3
- "version": "0.3.0",
3
+ "version": "0.4.1",
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",