free-pi-cli 0.6.15 → 0.6.17

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,3 +1,5 @@
1
+ <p align="center"><img src="https://freepi.ai/brand/free-pi-cli-lockup.png" alt="free-pi CLI" width="360"></p>
2
+
1
3
  # free-pi-cli
2
4
 
3
5
  **A free AI coding agent for your terminal.**
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-jmyqcr54.js");
30
+ await import("./main-9s59pfn5.js");
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  // src/main.ts
4
- import { spawn as spawn3 } from "node:child_process";
4
+ import { spawn as spawn4 } from "node:child_process";
5
5
  import { realpathSync as realpathSync8, writeFileSync as writeFileSync6 } from "node:fs";
6
6
  import { homedir as homedir10 } from "node:os";
7
7
  import { dirname as dirname14, join as join15 } from "node:path";
@@ -515,7 +515,7 @@ function folderMatches(cwd, sessionCwd) {
515
515
  // package.json
516
516
  var package_default = {
517
517
  name: "free-pi-cli",
518
- version: "0.6.15",
518
+ version: "0.6.17",
519
519
  description: "Free AI coding agent for your terminal. Ads and a daily free allowance pay for the models; sign in with GitHub and run npx free-pi-cli.",
520
520
  keywords: [
521
521
  "coding agent",
@@ -6001,6 +6001,11 @@ var AdCreativeSchema = object({
6001
6001
  cta: string2(),
6002
6002
  accent: string2()
6003
6003
  });
6004
+ var AdNextCreativeSchema = AdCreativeSchema.extend({
6005
+ icon_url: string2().optional(),
6006
+ image_url: string2().optional(),
6007
+ brand_name: string2().optional()
6008
+ });
6004
6009
  var AdNextQuerySchema = object({
6005
6010
  slot: AdSlotSchema,
6006
6011
  own_only: _enum(["1"]).optional()
@@ -6008,7 +6013,7 @@ var AdNextQuerySchema = object({
6008
6013
  var AdNextResponseSchema = object({
6009
6014
  ad_id: string2(),
6010
6015
  click_token: string2(),
6011
- creative: AdCreativeSchema,
6016
+ creative: AdNextCreativeSchema,
6012
6017
  click_url: string2(),
6013
6018
  source: _enum(["gravity", "own"]).optional(),
6014
6019
  imp_url: string2().optional()
@@ -7030,11 +7035,14 @@ function formatRewardStatus(r, now, columns) {
7030
7035
  }
7031
7036
  async function renderMeter(deps, ctx, opts = {}) {
7032
7037
  const me = await fetchMe(deps);
7033
- ctx.ui.setStatus(METER_STATUS_KEY, undefined);
7034
7038
  if (!me) {
7035
- ctx.ui.setWidget(METER_WIDGET_ID, undefined);
7039
+ if (await deps.getToken() !== lastToken) {
7040
+ ctx.ui.setStatus(METER_STATUS_KEY, undefined);
7041
+ ctx.ui.setWidget(METER_WIDGET_ID, undefined);
7042
+ }
7036
7043
  return;
7037
7044
  }
7045
+ ctx.ui.setStatus(METER_STATUS_KEY, undefined);
7038
7046
  const theme = ctx.ui.theme;
7039
7047
  const reward = formatRewardStatus(me.earn_reward, opts.now ?? Date.now(), opts.columns ?? (process.stdout.columns || 80));
7040
7048
  if (reward)
@@ -8915,10 +8923,10 @@ function createToolAliasesExtension(opts = {}) {
8915
8923
  var FUSION_ID = "fusion";
8916
8924
  var FUSION_PICKER_ID = "\uD83E\uDDE8 fusion";
8917
8925
  var FUSION_NAME = "\uD83E\uDDE8 Fusion · DeepSeek Pro plans, Flash builds";
8918
- var FUSION_FALLBACK_NAME = "\uD83E\uDDE8 Fusion · Sonnet plans, DeepSeek builds";
8926
+ var FUSION_FALLBACK_NAME = "\uD83E\uDDE8 Fusion · DeepSeek Flash plans and builds";
8919
8927
  var FUSION_PLANNER_ID = "deepseek-v4-pro";
8920
8928
  var FUSION_EXECUTOR_ID = "deepseek-v4-flash";
8921
- var FUSION_FALLBACK_PLANNER_ID = "claude-sonnet-5.5";
8929
+ var FUSION_FALLBACK_PLANNER_ID = "deepseek-v4-flash";
8922
8930
  function resolveFusionIds(catalogIds, server) {
8923
8931
  const has = (id) => catalogIds.includes(id);
8924
8932
  if (server && has(server.planner) && has(server.executor))
@@ -9200,7 +9208,7 @@ var LIMIT_CODES = new Set([
9200
9208
  "context_too_long",
9201
9209
  "reward_required"
9202
9210
  ]);
9203
- var SERVER_MESSAGE_CODES = new Set(["reward_required", "premium_allowance", "model_at_capacity", "context_too_long_for_fallback", "context_too_long"]);
9211
+ var SERVER_MESSAGE_CODES = new Set(["concurrent", "reward_required", "premium_allowance", "model_at_capacity", "context_too_long_for_fallback", "context_too_long"]);
9204
9212
  var FREE_OFFER_CODES = new Set(["daily_cap", "queue_wait", "rolling_cap", "premium_allowance", "model_at_capacity", "model_requires_credits"]);
9205
9213
  var isFusion = (model) => model?.provider === PROVIDER_NAME && (model.id === FUSION_ID || model.id === FUSION_PICKER_ID);
9206
9214
  function lastUserEntryId(ctx) {
@@ -9840,6 +9848,21 @@ function mergeAttribution(a, b) {
9840
9848
 
9841
9849
  // src/changelog.ts
9842
9850
  var CHANGELOG_HIGHLIGHTS = [
9851
+ {
9852
+ version: "0.6.17",
9853
+ highlights: [
9854
+ "free-pi now runs full screen. Click an ad to open it; selecting text still copies it, and the mouse wheel scrolls.",
9855
+ "The questions before start (terms, how you found free-pi, folder trust) now use color."
9856
+ ]
9857
+ },
9858
+ {
9859
+ version: "0.6.16",
9860
+ highlights: [
9861
+ "New premium model: DeepSeek V4.1 Flash Ultrafast. Find it in /model as \uD83D\uDD25\uD83D\uDD25\uD83D\uDD25 ultrafast. It uses premium credits only.",
9862
+ "Subagents now need credits: they use premium credit, and up to 4 run at once.",
9863
+ "Fusion plans with DeepSeek Flash, since the Claude models are retired."
9864
+ ]
9865
+ },
9843
9866
  {
9844
9867
  version: "0.6.15",
9845
9868
  highlights: [
@@ -11995,12 +12018,40 @@ function createHeaderExtension(opts) {
11995
12018
  };
11996
12019
  }
11997
12020
 
12021
+ // src/ultrafast.ts
12022
+ var ULTRAFAST_THINKING_LEVELS = ["off", "minimal", "low", "medium", "high"];
12023
+ var ULTRAFAST_CATALOG_ID = "deepseek-v4.1-flash-1017-ultrafast";
12024
+ var ULTRAFAST_PICKER_ID = "\uD83D\uDD25\uD83D\uDD25\uD83D\uDD25 ultrafast";
12025
+ function createUltrafastExtension(model) {
12026
+ return {
12027
+ name: "free-pi-ultrafast",
12028
+ factory(pi) {
12029
+ if (!model || model.id !== ULTRAFAST_CATALOG_ID)
12030
+ return;
12031
+ pi.registerVirtualModel({
12032
+ provider: PROVIDER_NAME,
12033
+ id: ULTRAFAST_PICKER_ID,
12034
+ name: model.name,
12035
+ input: ["text"],
12036
+ thinkingLevels: ULTRAFAST_THINKING_LEVELS,
12037
+ contextWindow: CONTEXT_WINDOW,
12038
+ maxTokens: MAX_OUTPUT_TOKENS,
12039
+ route: (_request, ctx) => ({
12040
+ model: ctx.modelRegistry.find(PROVIDER_NAME, ULTRAFAST_CATALOG_ID),
12041
+ thinkingLevel: _request.thinkingLevel
12042
+ })
12043
+ });
12044
+ }
12045
+ };
12046
+ }
12047
+
11998
12048
  // src/provider-lock.ts
11999
12049
  function resolveFreePiScope(modelRuntime, providerName, modelIds, env = process.env) {
12000
12050
  if (env.FREEPI_KEEP_ENV_PROVIDERS)
12001
12051
  return [];
12002
12052
  const scoped = [];
12003
- for (const id of new Set([FUSION_PICKER_ID, ...modelIds])) {
12053
+ const pickerModelIds = modelIds.map((id) => id === ULTRAFAST_CATALOG_ID ? ULTRAFAST_PICKER_ID : id);
12054
+ for (const id of new Set([FUSION_PICKER_ID, ...pickerModelIds])) {
12004
12055
  const model = modelRuntime.getModel(providerName, id);
12005
12056
  if (model)
12006
12057
  scoped.push({ model });
@@ -12032,19 +12083,22 @@ var SEND_MESSAGE_TOOL = "send_message";
12032
12083
  var SUBMIT_PLAN_TOOL = "submit_plan";
12033
12084
  var MAX_PARALLEL = 8;
12034
12085
  var PARTICLE_ID = "particle";
12035
- var REFUSAL_FALLBACK = "Subagents run on the free Particle model, or with credits. Switch with /model or run /buy-credits.";
12086
+ var REFUSAL_FALLBACK = "Subagents need credits. Run /buy-credits.";
12036
12087
  function subagentModelId(agent, parentModelId, ids) {
12037
12088
  if (parentModelId === PARTICLE_ID)
12038
12089
  return PARTICLE_ID;
12039
12090
  if (agent === "planner")
12040
12091
  return ids.planner;
12092
+ if (parentModelId === ULTRAFAST_PICKER_ID)
12093
+ return ULTRAFAST_CATALOG_ID;
12041
12094
  return parentModelId === FUSION_ID || parentModelId === FUSION_PICKER_ID ? ids.executor : parentModelId;
12042
12095
  }
12043
- function subagentsAvailable(modelId, hasCredit) {
12044
- return modelId === PARTICLE_ID || hasCredit;
12096
+ function subagentsAvailable(_modelId, hasCredit) {
12097
+ return hasCredit;
12045
12098
  }
12046
- function subagentConcurrency(modelId) {
12047
- return modelId === PARTICLE_ID ? 4 : 1;
12099
+ var SUBAGENT_CONCURRENCY = 4;
12100
+ function subagentConcurrency(_modelId) {
12101
+ return SUBAGENT_CONCURRENCY;
12048
12102
  }
12049
12103
  function substitutePrevious(task, previous) {
12050
12104
  return task.split("{previous}").join(previous);
@@ -13993,10 +14047,83 @@ function patchResumeLine(stream) {
13993
14047
  };
13994
14048
  }
13995
14049
 
14050
+ // src/safe-url-open.ts
14051
+ import { spawn as spawn3 } from "node:child_process";
14052
+ function openLink(url) {
14053
+ if (process.platform !== "win32")
14054
+ return void openBrowser(url);
14055
+ spawn3("rundll32", ["url.dll,FileProtocolHandler", url], { stdio: "ignore", detached: true }).on("error", () => {}).unref();
14056
+ }
14057
+ function safeOpenUrl(url, open = openLink) {
14058
+ const safe = safeBrowserUrl(url);
14059
+ if (safe)
14060
+ open(safe);
14061
+ }
14062
+ function installSafeUrlOpen(mode, open = openLink) {
14063
+ const m = mode;
14064
+ const patch = (r) => {
14065
+ if (r && "openUrl" in r)
14066
+ r.openUrl = (url) => safeOpenUrl(url, open);
14067
+ };
14068
+ let current = m.renderer;
14069
+ patch(current);
14070
+ Object.defineProperty(m, "renderer", {
14071
+ configurable: true,
14072
+ enumerable: true,
14073
+ get: () => current,
14074
+ set: (next) => {
14075
+ current = next;
14076
+ patch(next);
14077
+ }
14078
+ });
14079
+ }
14080
+
13996
14081
  // src/project-trust.ts
13997
14082
  import { createInterface } from "node:readline/promises";
13998
14083
  import { resolve as resolve3 } from "node:path";
13999
14084
  import { hasTrustRequiringProjectResources, ProjectTrustStore } from "@earendil-works/pi-coding-agent";
14085
+
14086
+ // src/prelaunch-style.ts
14087
+ function colorOn() {
14088
+ return !process.env.NO_COLOR && process.stdout.isTTY === true;
14089
+ }
14090
+ var ACCENT = "96";
14091
+ var sgr2 = (code, s, on) => on ? `\x1B[${code}m${s}\x1B[0m` : s;
14092
+ function styleHeader(on = colorOn()) {
14093
+ return sgr2(`1;${ACCENT}`, "›π free-pi", on);
14094
+ }
14095
+ function styleYesNo(prompt, on = colorOn()) {
14096
+ return prompt.replace(/^(.*?)(\[y\/N\])(\s*)$/s, (_m, q, yn, sp) => `${sgr2("1", q, on)}${sgr2(ACCENT, yn, on)}${sp}`);
14097
+ }
14098
+ function styleConsent(text, on = colorOn()) {
14099
+ if (!on)
14100
+ return text;
14101
+ const body = text.split(`
14102
+ `).map((line, i) => {
14103
+ if (i === 0)
14104
+ return sgr2("1", line, on);
14105
+ if (line.startsWith("Terms:"))
14106
+ return line.replace(/https:\/\/\S+/g, (u) => sgr2(`4;${ACCENT}`, u, on));
14107
+ return sgr2("2", line, on);
14108
+ }).join(`
14109
+ `);
14110
+ return `${styleHeader(on)}
14111
+ ${body}`;
14112
+ }
14113
+ function styleSourceQuestion(q, on = colorOn()) {
14114
+ if (!on)
14115
+ return q;
14116
+ return q.replace(/How did you find free-pi\? \(optional\)/, (h) => sgr2("1", h, on)).replace(/\d\)/g, (n) => sgr2(ACCENT, n, on)).replace(/Type a number, or press Enter to skip: $/, (h) => sgr2("2", h, on));
14117
+ }
14118
+ function styleIntro(text, on = colorOn()) {
14119
+ const i = text.indexOf("npx ");
14120
+ if (i < 0)
14121
+ return text;
14122
+ return `${sgr2("2", text.slice(0, i).trimEnd(), on)}
14123
+ ${sgr2(ACCENT, text.slice(i), on)}`;
14124
+ }
14125
+
14126
+ // src/project-trust.ts
14000
14127
  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] ";
14001
14128
  async function decideProjectTrust(cwd, agentDir, deps) {
14002
14129
  if (!(deps.hasTrustRequiringResources ?? hasTrustRequiringProjectResources)(cwd))
@@ -14009,7 +14136,7 @@ async function decideProjectTrust(cwd, agentDir, deps) {
14009
14136
  deps.log("This folder has project settings (.pi/ or .agents/skills); they are ignored because it is not trusted.");
14010
14137
  return false;
14011
14138
  }
14012
- const decision = /^y(es)?$/i.test((await deps.askLine(PROJECT_TRUST_PROMPT)).trim());
14139
+ const decision = /^y(es)?$/i.test((await deps.askLine(styleYesNo(PROJECT_TRUST_PROMPT))).trim());
14013
14140
  store.set(cwd, decision);
14014
14141
  return decision;
14015
14142
  }
@@ -14063,6 +14190,9 @@ function catalogModelsFor(opts) {
14063
14190
  return [{ id, name: id }];
14064
14191
  }
14065
14192
  function pickerModelName(opts, id) {
14193
+ if (id === ULTRAFAST_PICKER_ID) {
14194
+ return opts.models?.find((m) => m.id === ULTRAFAST_CATALOG_ID)?.name ?? ULTRAFAST_PICKER_ID;
14195
+ }
14066
14196
  return [...catalogModelsFor(opts), ...defenseModelsFor(opts)].find((m) => m.id === id)?.name;
14067
14197
  }
14068
14198
  function defenseModelsFor(opts) {
@@ -14107,7 +14237,7 @@ function buildSettingsManager(opts, models, projectTrusted) {
14107
14237
  quietStartup: true,
14108
14238
  defaultThinkingLevel: "off",
14109
14239
  hideThinkingBlock: true,
14110
- tuiMode: "regular",
14240
+ tuiMode: "fullscreen",
14111
14241
  theme: "light/dark",
14112
14242
  enableInstallTelemetry: false,
14113
14243
  ...compactionSettingsFor(opts.costArm) ? { compaction: compactionSettingsFor(opts.costArm) } : {}
@@ -14261,6 +14391,7 @@ function buildRuntimeOptions(opts, sessionId) {
14261
14391
  });
14262
14392
  const extensionFactories = [
14263
14393
  providerExtension,
14394
+ ...models.some((m) => m.id === ULTRAFAST_CATALOG_ID) ? [createUltrafastExtension(models.find((m) => m.id === ULTRAFAST_CATALOG_ID))] : [],
14264
14395
  createFusionExtension(models[0].id, resolveFusionIds(models.map((m) => m.id), opts.fusion)),
14265
14396
  adsExtension,
14266
14397
  usageToolExtension,
@@ -14405,6 +14536,7 @@ async function launchPi(opts) {
14405
14536
  });
14406
14537
  installLogoutHandler(mode, { credentialsPath: opts.credentialsPath, isGuest: () => auth.isGuest() });
14407
14538
  installModelAllowanceHandler(mode);
14539
+ installSafeUrlOpen(mode);
14408
14540
  installFooterRightAlign(mode, () => {
14409
14541
  const id = runtime.session?.model?.id;
14410
14542
  return freeModelBase(pickerModelName(opts, id));
@@ -14425,8 +14557,8 @@ async function promptConsent(deps, text = CONSENT_TEXT) {
14425
14557
  deps.log(NON_INTERACTIVE_CONSENT_TEXT);
14426
14558
  return false;
14427
14559
  }
14428
- deps.log(text);
14429
- const answer = await deps.askLine(CONSENT_PROMPT);
14560
+ deps.log(styleConsent(text));
14561
+ const answer = await deps.askLine(styleYesNo(CONSENT_PROMPT));
14430
14562
  return parseConsentAnswer(answer);
14431
14563
  }
14432
14564
  async function promptConsentInteractive(text) {
@@ -14618,7 +14750,7 @@ function showIntro(log, env = process.env) {
14618
14750
  if (introSkipped(env))
14619
14751
  return;
14620
14752
  log(`
14621
- ${INTRO_TEXT}
14753
+ ${styleIntro(INTRO_TEXT)}
14622
14754
  `);
14623
14755
  }
14624
14756
  var FIRST_TASK_EXPERIMENT = "retain.first_task";
@@ -14793,7 +14925,7 @@ async function promptSourceInteractive() {
14793
14925
  const rl = createInterface3({ input: process.stdin, output: process.stdout });
14794
14926
  try {
14795
14927
  return await Promise.race([
14796
- rl.question(SOURCE_QUESTION),
14928
+ rl.question(styleSourceQuestion(SOURCE_QUESTION)),
14797
14929
  new Promise((resolve) => rl.once("close", () => resolve(undefined))),
14798
14930
  new Promise((resolve) => rl.once("SIGINT", () => resolve("aborted")))
14799
14931
  ]);
@@ -15414,7 +15546,7 @@ ${HELP_TEXT}`);
15414
15546
  homedir: homedir10(),
15415
15547
  execPath: process.execPath,
15416
15548
  autoUpdateEnabled: () => readAutoUpdate(getConfigPath()),
15417
- spawn: (cmd, args, opts) => spawn3(cmd, args, opts),
15549
+ spawn: (cmd, args, opts) => spawn4(cmd, args, opts),
15418
15550
  proc: process,
15419
15551
  log: (message) => console.log(message)
15420
15552
  }),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "free-pi-cli",
3
- "version": "0.6.15",
3
+ "version": "0.6.17",
4
4
  "description": "Free AI coding agent for your terminal. Ads and a daily free allowance pay for the models; sign in with GitHub and run npx free-pi-cli.",
5
5
  "keywords": [
6
6
  "coding agent",