free-pi-cli 0.6.18 → 0.6.20
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-m86v4egg.js → main-qswbbh2q.js} +636 -148
- package/package.json +2 -1
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
|
|
3
3
|
// src/main.ts
|
|
4
4
|
import { spawn as spawn4 } from "node:child_process";
|
|
5
|
-
import { realpathSync as realpathSync8, writeFileSync as
|
|
5
|
+
import { realpathSync as realpathSync8, writeFileSync as writeFileSync7 } from "node:fs";
|
|
6
6
|
import { homedir as homedir10 } from "node:os";
|
|
7
|
-
import { dirname as
|
|
7
|
+
import { dirname as dirname15, join as join16 } from "node:path";
|
|
8
8
|
|
|
9
9
|
// src/browser.ts
|
|
10
10
|
import { spawn } from "node:child_process";
|
|
@@ -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.
|
|
518
|
+
version: "0.6.20",
|
|
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",
|
|
@@ -554,6 +554,7 @@ var package_default = {
|
|
|
554
554
|
prepublishOnly: "bun run build"
|
|
555
555
|
},
|
|
556
556
|
dependencies: {
|
|
557
|
+
"@earendil-works/pi-ai": "1.2.0",
|
|
557
558
|
"@earendil-works/pi-coding-agent": "1.2.0",
|
|
558
559
|
"@earendil-works/pi-tui": "1.2.0",
|
|
559
560
|
"@gravity-ai/api": "1.2.1"
|
|
@@ -569,7 +570,7 @@ var package_default = {
|
|
|
569
570
|
var CLI_VERSION = package_default.version;
|
|
570
571
|
|
|
571
572
|
// src/output-limit.ts
|
|
572
|
-
var MAX_OUTPUT_TOKENS =
|
|
573
|
+
var MAX_OUTPUT_TOKENS = 32768;
|
|
573
574
|
var OUTPUT_LIMIT_MESSAGE = "This tool call was cut off by the output limit before it finished. It either did not run or ran with incomplete content, " + "so a file may be partly written. Check what was written, then redo the work in smaller parts " + "(for example write the first part, then add the rest with edit).";
|
|
574
575
|
function createOutputLimitExtension() {
|
|
575
576
|
let truncated = new Set;
|
|
@@ -6583,14 +6584,15 @@ import { fileURLToPath, pathToFileURL } from "node:url";
|
|
|
6583
6584
|
function piMcpCliPath() {
|
|
6584
6585
|
return join4(dirname5(fileURLToPath(import.meta.resolve("@earendil-works/pi-coding-agent"))), "extensions", "mcp", "cli.js");
|
|
6585
6586
|
}
|
|
6587
|
+
var MCP_UNAVAILABLE = "free-pi-cli mcp isn't available in this install. Reinstall with npx free-pi-cli@latest.";
|
|
6586
6588
|
async function loadPiRunner() {
|
|
6587
6589
|
const path = piMcpCliPath();
|
|
6588
6590
|
if (!existsSync2(path)) {
|
|
6589
|
-
throw new Error(
|
|
6591
|
+
throw new Error(MCP_UNAVAILABLE);
|
|
6590
6592
|
}
|
|
6591
6593
|
const mod = await import(pathToFileURL(path).href);
|
|
6592
6594
|
if (typeof mod.runMcpCommand !== "function") {
|
|
6593
|
-
throw new Error(
|
|
6595
|
+
throw new Error(MCP_UNAVAILABLE);
|
|
6594
6596
|
}
|
|
6595
6597
|
return mod.runMcpCommand;
|
|
6596
6598
|
}
|
|
@@ -6608,7 +6610,9 @@ async function runFreePiMcp(args, deps) {
|
|
|
6608
6610
|
error: (line) => deps.error(brandMcpLine(line))
|
|
6609
6611
|
});
|
|
6610
6612
|
} catch (err) {
|
|
6611
|
-
|
|
6613
|
+
const msg = (err instanceof Error ? err.message : String(err)).split(`
|
|
6614
|
+
at `)[0];
|
|
6615
|
+
deps.error(msg === MCP_UNAVAILABLE ? msg : `free-pi-cli mcp failed: ${msg}`);
|
|
6612
6616
|
return 1;
|
|
6613
6617
|
}
|
|
6614
6618
|
}
|
|
@@ -6708,6 +6712,28 @@ function loadConfig(env = process.env) {
|
|
|
6708
6712
|
|
|
6709
6713
|
// ../pi-ads/src/api.ts
|
|
6710
6714
|
import { arch } from "node:os";
|
|
6715
|
+
|
|
6716
|
+
// ../pi-ads/src/turn.ts
|
|
6717
|
+
var TURN_HEADER = "x-turn-id";
|
|
6718
|
+
var current;
|
|
6719
|
+
function startTurn() {
|
|
6720
|
+
current = crypto.randomUUID();
|
|
6721
|
+
return current;
|
|
6722
|
+
}
|
|
6723
|
+
var currentTurnId = () => current;
|
|
6724
|
+
function createTurnHeaderExtension() {
|
|
6725
|
+
return {
|
|
6726
|
+
name: "free-pi-turn-header",
|
|
6727
|
+
factory(pi) {
|
|
6728
|
+
pi.on("before_provider_headers", (event) => {
|
|
6729
|
+
if (current)
|
|
6730
|
+
event.headers[TURN_HEADER] = current;
|
|
6731
|
+
});
|
|
6732
|
+
}
|
|
6733
|
+
};
|
|
6734
|
+
}
|
|
6735
|
+
|
|
6736
|
+
// ../pi-ads/src/api.ts
|
|
6711
6737
|
var REQUEST_TIMEOUT_MS = 5000;
|
|
6712
6738
|
async function authedFetch(deps, path, init) {
|
|
6713
6739
|
const token = await deps.getToken();
|
|
@@ -6721,7 +6747,7 @@ async function authedFetch(deps, path, init) {
|
|
|
6721
6747
|
Authorization: `Bearer ${token}`,
|
|
6722
6748
|
"Content-Type": "application/json",
|
|
6723
6749
|
...deps.sessionId && (path.startsWith("/ads/next") || path === "/ads/impression") ? { "x-session-id": deps.sessionId } : {},
|
|
6724
|
-
...path.startsWith("/ads/next") ? { ...deps.clientVersion ? { "x-client-version": deps.clientVersion } : {}, "x-client-os": process.platform, "x-client-arch": arch(), ...localeHeaders(), ...gravityHeader(deps) } : {}
|
|
6750
|
+
...path.startsWith("/ads/next") ? { ...currentTurnId() ? { "x-turn-id": currentTurnId() } : {}, ...deps.clientVersion ? { "x-client-version": deps.clientVersion } : {}, "x-client-os": process.platform, "x-client-arch": arch(), ...localeHeaders(), ...gravityHeader(deps) } : {}
|
|
6725
6751
|
}
|
|
6726
6752
|
});
|
|
6727
6753
|
}
|
|
@@ -6760,9 +6786,9 @@ function fireGravityImpression(deps, ad) {
|
|
|
6760
6786
|
} catch {}
|
|
6761
6787
|
return true;
|
|
6762
6788
|
}
|
|
6763
|
-
async function fetchAdNext(deps, slot) {
|
|
6789
|
+
async function fetchAdNext(deps, slot, trigger) {
|
|
6764
6790
|
try {
|
|
6765
|
-
const res = await authedFetch(deps, `/ads/next?slot=${slot}
|
|
6791
|
+
const res = await authedFetch(deps, `/ads/next?slot=${slot}`, trigger ? { headers: { "x-ad-trigger": trigger } } : undefined);
|
|
6766
6792
|
if (res.status === 204 || !res.ok)
|
|
6767
6793
|
return;
|
|
6768
6794
|
return await res.json();
|
|
@@ -6862,8 +6888,8 @@ function createBannerRenderer(opts = {}) {
|
|
|
6862
6888
|
let lastPostedToken;
|
|
6863
6889
|
const screen = opts.screen ?? {};
|
|
6864
6890
|
return {
|
|
6865
|
-
async render(deps, ctx, config) {
|
|
6866
|
-
const ad = await fetchAdNext(deps, "banner");
|
|
6891
|
+
async render(deps, ctx, config, trigger) {
|
|
6892
|
+
const ad = await fetchAdNext(deps, "banner", trigger);
|
|
6867
6893
|
if (opts.isActive && !opts.isActive())
|
|
6868
6894
|
return;
|
|
6869
6895
|
if (!ad) {
|
|
@@ -7129,7 +7155,7 @@ async function renderMeter(deps, ctx, opts = {}) {
|
|
|
7129
7155
|
}
|
|
7130
7156
|
|
|
7131
7157
|
// ../pi-ads/src/index.ts
|
|
7132
|
-
var
|
|
7158
|
+
var REFRESH_ON_TURN_END = false;
|
|
7133
7159
|
var BANNER_MIN_REFRESH_MS = 60 * 1000;
|
|
7134
7160
|
function createAdsExtension(opts) {
|
|
7135
7161
|
const deps = {
|
|
@@ -7149,51 +7175,79 @@ function createAdsExtension(opts) {
|
|
|
7149
7175
|
const screen = {};
|
|
7150
7176
|
const banner = createBannerRenderer({ onBannerAd: opts.onBannerAd, isActive: () => !stopped, screen });
|
|
7151
7177
|
const inline = createInlineRenderer(screen);
|
|
7152
|
-
let
|
|
7178
|
+
let launchTurnUnused = false;
|
|
7179
|
+
let launchToken;
|
|
7180
|
+
let launchAt = 0;
|
|
7153
7181
|
let lastBannerAt = 0;
|
|
7154
7182
|
let bannerInFlight = false;
|
|
7155
|
-
const renderBanner = async (ctx, force = false) => {
|
|
7183
|
+
const renderBanner = async (ctx, trigger, force = false) => {
|
|
7156
7184
|
if (stopped)
|
|
7157
7185
|
return;
|
|
7158
7186
|
if (!force && (bannerInFlight || Date.now() - lastBannerAt < BANNER_MIN_REFRESH_MS))
|
|
7159
7187
|
return;
|
|
7160
7188
|
bannerInFlight = true;
|
|
7161
7189
|
try {
|
|
7162
|
-
await banner.render(deps, ctx, config);
|
|
7190
|
+
await banner.render(deps, ctx, config, trigger);
|
|
7163
7191
|
} finally {
|
|
7164
7192
|
bannerInFlight = false;
|
|
7165
7193
|
lastBannerAt = Date.now();
|
|
7166
7194
|
}
|
|
7167
7195
|
};
|
|
7196
|
+
pi.on("before_provider_headers", (event) => {
|
|
7197
|
+
const id = currentTurnId();
|
|
7198
|
+
if (id)
|
|
7199
|
+
event.headers[TURN_HEADER] = id;
|
|
7200
|
+
});
|
|
7168
7201
|
pi.on("session_start", async (_event, ctx) => {
|
|
7169
7202
|
stopped = false;
|
|
7170
7203
|
inline.reset(ctx);
|
|
7204
|
+
startTurn();
|
|
7205
|
+
launchTurnUnused = true;
|
|
7206
|
+
launchAt = Date.now();
|
|
7207
|
+
launchToken = await Promise.resolve(deps.getToken()).catch(() => {
|
|
7208
|
+
return;
|
|
7209
|
+
});
|
|
7171
7210
|
opts.registerMeterRefresh?.(() => renderMeter(deps, ctx));
|
|
7172
|
-
await Promise.all([renderBanner(ctx, true), renderMeter(deps, ctx)]);
|
|
7173
|
-
|
|
7174
|
-
|
|
7175
|
-
|
|
7176
|
-
|
|
7177
|
-
|
|
7178
|
-
|
|
7211
|
+
await Promise.all([renderBanner(ctx, "launch", true), renderMeter(deps, ctx)]);
|
|
7212
|
+
});
|
|
7213
|
+
const LAUNCH_AD_MAX_AGE_MS = 60 * 60000;
|
|
7214
|
+
const newMessage = async (ctx) => {
|
|
7215
|
+
if (launchTurnUnused) {
|
|
7216
|
+
launchTurnUnused = false;
|
|
7217
|
+
const token = await Promise.resolve(deps.getToken()).catch(() => {
|
|
7218
|
+
return;
|
|
7219
|
+
});
|
|
7220
|
+
if (token === launchToken && Date.now() - launchAt < LAUNCH_AD_MAX_AGE_MS)
|
|
7221
|
+
return;
|
|
7222
|
+
}
|
|
7223
|
+
startTurn();
|
|
7224
|
+
renderBanner(ctx, "user_message", true);
|
|
7225
|
+
};
|
|
7226
|
+
pi.on("before_agent_start", async (event, ctx) => {
|
|
7227
|
+
if (event.messageType !== "user")
|
|
7228
|
+
return;
|
|
7229
|
+
await newMessage(ctx);
|
|
7230
|
+
});
|
|
7231
|
+
pi.on("input", (event, ctx) => {
|
|
7232
|
+
const e = event;
|
|
7233
|
+
if (e.streamingBehavior && e.source !== "extension")
|
|
7234
|
+
return newMessage(ctx).then(() => ({ action: "continue" }));
|
|
7235
|
+
return { action: "continue" };
|
|
7179
7236
|
});
|
|
7180
7237
|
pi.on("turn_end", async (_event, ctx) => {
|
|
7181
|
-
|
|
7182
|
-
|
|
7238
|
+
if (REFRESH_ON_TURN_END)
|
|
7239
|
+
renderBanner(ctx, "turn_end");
|
|
7240
|
+
await renderMeter(deps, ctx);
|
|
7183
7241
|
});
|
|
7184
7242
|
pi.on("session_shutdown", () => {
|
|
7185
7243
|
stopped = true;
|
|
7186
|
-
if (bannerInterval) {
|
|
7187
|
-
clearInterval(bannerInterval);
|
|
7188
|
-
bannerInterval = undefined;
|
|
7189
|
-
}
|
|
7190
7244
|
});
|
|
7191
7245
|
}
|
|
7192
7246
|
};
|
|
7193
7247
|
}
|
|
7194
7248
|
|
|
7195
7249
|
// src/pi-launch.ts
|
|
7196
|
-
import { dirname as
|
|
7250
|
+
import { dirname as dirname12, join as join12, resolve as resolve4 } from "node:path";
|
|
7197
7251
|
import { fileURLToPath as fileURLToPath4 } from "node:url";
|
|
7198
7252
|
|
|
7199
7253
|
// ../../node_modules/.bun/typebox@1.3.27/node_modules/typebox/build/system/memory/metrics.mjs
|
|
@@ -7523,6 +7577,28 @@ var integerKeyPattern = new RegExp("^(?:0|[1-9][0-9]*)$");
|
|
|
7523
7577
|
|
|
7524
7578
|
// ../../node_modules/.bun/typebox@1.3.27/node_modules/typebox/build/type/engine/indexed/from_object.mjs
|
|
7525
7579
|
var NumericKeyPattern = new RegExp(IntegerKey);
|
|
7580
|
+
// src/friendly-error.ts
|
|
7581
|
+
function friendlyThrown(e) {
|
|
7582
|
+
const name = e instanceof Error ? e.name : "";
|
|
7583
|
+
if (name === "TimeoutError")
|
|
7584
|
+
return "free-pi took too long to answer. Try again.";
|
|
7585
|
+
if (name === "AbortError")
|
|
7586
|
+
return "Cancelled.";
|
|
7587
|
+
if (name === "SyntaxError")
|
|
7588
|
+
return "free-pi sent an unexpected reply. Try again.";
|
|
7589
|
+
return "Couldn't reach free-pi — check your connection.";
|
|
7590
|
+
}
|
|
7591
|
+
function friendlyStatus(status, audience = "user", message) {
|
|
7592
|
+
if (status === 401) {
|
|
7593
|
+
return audience === "model" ? "The user's free-pi sign-in has expired; tell them to run /login." : "Your free-pi sign-in has expired. Run /login.";
|
|
7594
|
+
}
|
|
7595
|
+
if (message?.trim())
|
|
7596
|
+
return message.trim();
|
|
7597
|
+
if (status >= 500)
|
|
7598
|
+
return `free-pi is having trouble right now (HTTP ${status}). Try again in a minute.`;
|
|
7599
|
+
return `free-pi could not complete the request (HTTP ${status}).`;
|
|
7600
|
+
}
|
|
7601
|
+
|
|
7526
7602
|
// src/usage-tool.ts
|
|
7527
7603
|
var USAGE_TOOL_NAME = "free_pi_usage";
|
|
7528
7604
|
var PARAMS = _Object_({});
|
|
@@ -7580,11 +7656,11 @@ async function fetchUsageText(opts) {
|
|
|
7580
7656
|
signal: AbortSignal.timeout(5000)
|
|
7581
7657
|
});
|
|
7582
7658
|
if (!res.ok)
|
|
7583
|
-
return { ok: false, text:
|
|
7659
|
+
return { ok: false, text: friendlyStatus(res.status) };
|
|
7584
7660
|
const stats = await res.json();
|
|
7585
7661
|
return { ok: true, text: formatStats(stats), stats };
|
|
7586
7662
|
} catch (err) {
|
|
7587
|
-
return { ok: false, text:
|
|
7663
|
+
return { ok: false, text: friendlyThrown(err) };
|
|
7588
7664
|
}
|
|
7589
7665
|
}
|
|
7590
7666
|
function createUsageToolExtension(opts) {
|
|
@@ -7652,11 +7728,9 @@ async function post(opts, body, signal) {
|
|
|
7652
7728
|
const mapped = ERROR_TEXT[code];
|
|
7653
7729
|
if (mapped)
|
|
7654
7730
|
return { ok: false, text: mapped };
|
|
7655
|
-
return { ok: false, text:
|
|
7731
|
+
return { ok: false, text: friendlyStatus(res.status, "model", j.error?.message ?? j.message) };
|
|
7656
7732
|
} catch (e) {
|
|
7657
|
-
|
|
7658
|
-
return { ok: false, text: "Image generation timed out after 120 seconds." };
|
|
7659
|
-
return { ok: false, text: `Request failed: ${String(e)}` };
|
|
7733
|
+
return { ok: false, text: friendlyThrown(e) };
|
|
7660
7734
|
}
|
|
7661
7735
|
}
|
|
7662
7736
|
function slug(s) {
|
|
@@ -7733,7 +7807,8 @@ function createImageToolExtension(opts) {
|
|
|
7733
7807
|
await fh.close();
|
|
7734
7808
|
}
|
|
7735
7809
|
} catch (e) {
|
|
7736
|
-
|
|
7810
|
+
const why = { EACCES: "permission denied", EPERM: "permission denied", ENOSPC: "the disk is full", ENOENT: "the folder does not exist" }[e.code ?? ""] ?? "it could not be written";
|
|
7811
|
+
return fail(`Couldn't save the image to images: ${why}.`);
|
|
7737
7812
|
}
|
|
7738
7813
|
return done(`Saved image to ${rel} (${ext}, ${buf.length} bytes). You cannot see the image; reference this path in your answer or code, and tell the user you could not check how it looks.`, { ok: true, path: rel, cost_usd: d.cost_usd, model: d.model });
|
|
7739
7814
|
}
|
|
@@ -8342,11 +8417,9 @@ async function post2(opts, path, body, label, signal) {
|
|
|
8342
8417
|
const mapped = ERROR_TEXT3[err.code ?? ""];
|
|
8343
8418
|
if (mapped)
|
|
8344
8419
|
return { ok: false, text: typeof mapped === "function" ? mapped(label) : mapped };
|
|
8345
|
-
return { ok: false, text:
|
|
8420
|
+
return { ok: false, text: friendlyStatus(res.status, "model", err.message) };
|
|
8346
8421
|
} catch (e) {
|
|
8347
|
-
|
|
8348
|
-
return { ok: false, text: "Request timed out after 30 seconds." };
|
|
8349
|
-
return { ok: false, text: `Request failed: ${String(e)}` };
|
|
8422
|
+
return { ok: false, text: friendlyThrown(e) };
|
|
8350
8423
|
}
|
|
8351
8424
|
}
|
|
8352
8425
|
var done3 = (o) => ({ content: [{ type: "text", text: o.text }], details: o.details });
|
|
@@ -8415,11 +8488,11 @@ async function resolveBuyPage(opts) {
|
|
|
8415
8488
|
signal: AbortSignal.timeout(5000)
|
|
8416
8489
|
});
|
|
8417
8490
|
if (!res.ok)
|
|
8418
|
-
return { kind: "error", text:
|
|
8491
|
+
return { kind: "error", text: friendlyStatus(res.status) };
|
|
8419
8492
|
const me = await res.json();
|
|
8420
8493
|
return me.buy_url ? { kind: "url", url: me.buy_url } : { kind: "unavailable" };
|
|
8421
8494
|
} catch (err) {
|
|
8422
|
-
return { kind: "error", text:
|
|
8495
|
+
return { kind: "error", text: friendlyThrown(err) };
|
|
8423
8496
|
}
|
|
8424
8497
|
}
|
|
8425
8498
|
var BUY_NOT_AVAILABLE_TEXT = "Buying credits is not available on this server yet.";
|
|
@@ -8930,6 +9003,142 @@ function createToolAliasesExtension(opts = {}) {
|
|
|
8930
9003
|
};
|
|
8931
9004
|
}
|
|
8932
9005
|
|
|
9006
|
+
// ../../node_modules/.bun/@earendil-works+pi-ai@1.2.0+a410056ef8f430f0/node_modules/@earendil-works/pi-ai/dist/utils/overflow.js
|
|
9007
|
+
var OVERFLOW_PATTERNS = [
|
|
9008
|
+
/prompt (?:is )?too long/i,
|
|
9009
|
+
/prompt exceeds max length/i,
|
|
9010
|
+
/request_too_large/i,
|
|
9011
|
+
/input is too long for requested model/i,
|
|
9012
|
+
/exceeds the context window/i,
|
|
9013
|
+
/exceeds (?:the )?(?:model'?s )?maximum context length(?: of [\d,]+ tokens?|\s*\([\d,]+\))/i,
|
|
9014
|
+
/input token count.*exceeds the maximum/i,
|
|
9015
|
+
/maximum prompt length is \d+/i,
|
|
9016
|
+
/reduce the length of the messages/i,
|
|
9017
|
+
/maximum context length is \d+ tokens/i,
|
|
9018
|
+
/exceeds (?:the )?maximum allowed input length of [\d,]+ tokens?/i,
|
|
9019
|
+
/input \(\d+ tokens\) is longer than the model'?s context length \(\d+ tokens\)/i,
|
|
9020
|
+
/exceeds the limit of \d+/i,
|
|
9021
|
+
/exceeds the available context size/i,
|
|
9022
|
+
/greater than the context length/i,
|
|
9023
|
+
/context window exceeds limit/i,
|
|
9024
|
+
/exceeded model token limit/i,
|
|
9025
|
+
/too large for model with \d+ maximum context length/i,
|
|
9026
|
+
/prompt has [\d,]+ tokens?, but the configured context size is [\d,]+ tokens?/i,
|
|
9027
|
+
/model_context_window_exceeded/i,
|
|
9028
|
+
/prompt too long; exceeded (?:max )?context length/i,
|
|
9029
|
+
/range of input length should be/i,
|
|
9030
|
+
/context[_ ]length[_ ]exceeded/i,
|
|
9031
|
+
/too many tokens/i,
|
|
9032
|
+
/token limit exceeded/i
|
|
9033
|
+
];
|
|
9034
|
+
var CEREBRAS_BODYLESS_OVERFLOW_PATTERN = /^4(?:00|13)\s*(?:status code)?\s*\(no body\)/i;
|
|
9035
|
+
var NON_OVERFLOW_PATTERNS = [
|
|
9036
|
+
/^(Throttling error|Service unavailable):/i,
|
|
9037
|
+
/rate limit/i,
|
|
9038
|
+
/too many requests/i
|
|
9039
|
+
];
|
|
9040
|
+
function isContextOverflow(message, contextWindow) {
|
|
9041
|
+
if (message.stopReason === "error" && message.errorMessage) {
|
|
9042
|
+
const isNonOverflow = NON_OVERFLOW_PATTERNS.some((p) => p.test(message.errorMessage));
|
|
9043
|
+
if (!isNonOverflow) {
|
|
9044
|
+
if (OVERFLOW_PATTERNS.some((p) => p.test(message.errorMessage))) {
|
|
9045
|
+
return true;
|
|
9046
|
+
}
|
|
9047
|
+
if (message.provider === "cerebras" && CEREBRAS_BODYLESS_OVERFLOW_PATTERN.test(message.errorMessage)) {
|
|
9048
|
+
return true;
|
|
9049
|
+
}
|
|
9050
|
+
}
|
|
9051
|
+
}
|
|
9052
|
+
if (contextWindow && message.stopReason === "stop") {
|
|
9053
|
+
const inputTokens = message.usage.input + message.usage.cacheRead;
|
|
9054
|
+
if (inputTokens > contextWindow) {
|
|
9055
|
+
return true;
|
|
9056
|
+
}
|
|
9057
|
+
}
|
|
9058
|
+
if (contextWindow && message.stopReason === "length" && message.usage.output === 0) {
|
|
9059
|
+
const inputTokens = message.usage.input + message.usage.cacheRead;
|
|
9060
|
+
if (inputTokens >= contextWindow * 0.99) {
|
|
9061
|
+
return true;
|
|
9062
|
+
}
|
|
9063
|
+
}
|
|
9064
|
+
return false;
|
|
9065
|
+
}
|
|
9066
|
+
|
|
9067
|
+
// ../../node_modules/.bun/@earendil-works+pi-ai@1.2.0+a410056ef8f430f0/node_modules/@earendil-works/pi-ai/dist/utils/retry.js
|
|
9068
|
+
function buildProviderErrorPattern(patterns) {
|
|
9069
|
+
return new RegExp(patterns.join("|"), "i");
|
|
9070
|
+
}
|
|
9071
|
+
var NON_RETRYABLE_PROVIDER_LIMIT_ERROR_PATTERN = buildProviderErrorPattern([
|
|
9072
|
+
"GoUsageLimitError",
|
|
9073
|
+
"FreeUsageLimitError",
|
|
9074
|
+
"Monthly usage limit reached",
|
|
9075
|
+
"available balance",
|
|
9076
|
+
"insufficient_quota",
|
|
9077
|
+
"out of budget",
|
|
9078
|
+
"quota exceeded",
|
|
9079
|
+
"billing",
|
|
9080
|
+
"subscription_sharing_usage_limit_exceeded"
|
|
9081
|
+
]);
|
|
9082
|
+
var RETRYABLE_PROVIDER_ERROR_PATTERN = buildProviderErrorPattern([
|
|
9083
|
+
"overloaded",
|
|
9084
|
+
"server_busy",
|
|
9085
|
+
"servers are currently busy",
|
|
9086
|
+
"currently experiencing high demand",
|
|
9087
|
+
"model is at capacity",
|
|
9088
|
+
"rate.?limit",
|
|
9089
|
+
"too many requests",
|
|
9090
|
+
"429",
|
|
9091
|
+
"500",
|
|
9092
|
+
"502",
|
|
9093
|
+
"503",
|
|
9094
|
+
"504",
|
|
9095
|
+
"520",
|
|
9096
|
+
"524",
|
|
9097
|
+
"service.?unavailable",
|
|
9098
|
+
"server.?error",
|
|
9099
|
+
"internal.?error",
|
|
9100
|
+
"provider.?returned.?(?:an.?)?error",
|
|
9101
|
+
"exceeded request buffer limit while retrying upstream",
|
|
9102
|
+
"network.?error",
|
|
9103
|
+
"connection.?error",
|
|
9104
|
+
"connection.?refused",
|
|
9105
|
+
"connection.?lost",
|
|
9106
|
+
"other side closed",
|
|
9107
|
+
"fetch failed",
|
|
9108
|
+
"getaddrinfo",
|
|
9109
|
+
"ENOTFOUND",
|
|
9110
|
+
"EAI_AGAIN",
|
|
9111
|
+
"upstream.?connect",
|
|
9112
|
+
"reset before headers",
|
|
9113
|
+
"socket hang up",
|
|
9114
|
+
"socket connection was closed",
|
|
9115
|
+
"timed? out",
|
|
9116
|
+
"timeout",
|
|
9117
|
+
"terminated",
|
|
9118
|
+
"websocket.?closed",
|
|
9119
|
+
"websocket.?error",
|
|
9120
|
+
"ended without",
|
|
9121
|
+
"stream ended before message_stop",
|
|
9122
|
+
"stream ended before a terminal response event",
|
|
9123
|
+
"http2 request did not get a response",
|
|
9124
|
+
"pending stream has been canceled",
|
|
9125
|
+
"retry delay",
|
|
9126
|
+
"you can retry your request",
|
|
9127
|
+
"try your request again",
|
|
9128
|
+
"please retry your request",
|
|
9129
|
+
"ResourceExhausted",
|
|
9130
|
+
"subscription_sharing_usage_unavailable",
|
|
9131
|
+
"subscription_sharing_user_unavailable"
|
|
9132
|
+
]);
|
|
9133
|
+
function isRetryableAssistantError(message) {
|
|
9134
|
+
if (message.stopReason !== "error" || !message.errorMessage)
|
|
9135
|
+
return false;
|
|
9136
|
+
const errorMessage = message.errorMessage;
|
|
9137
|
+
if (NON_RETRYABLE_PROVIDER_LIMIT_ERROR_PATTERN.test(errorMessage))
|
|
9138
|
+
return false;
|
|
9139
|
+
return RETRYABLE_PROVIDER_ERROR_PATTERN.test(errorMessage);
|
|
9140
|
+
}
|
|
9141
|
+
|
|
8933
9142
|
// src/fusion.ts
|
|
8934
9143
|
var FUSION_ID = "fusion";
|
|
8935
9144
|
var FUSION_PICKER_ID = "\uD83E\uDDE8 fusion";
|
|
@@ -9125,7 +9334,11 @@ function createFusionExtension(defaultModelId, ids = resolveFusionIds([])) {
|
|
|
9125
9334
|
|
|
9126
9335
|
// src/error-notice.ts
|
|
9127
9336
|
var UPSTREAM_ERROR_FRAME_MESSAGE = "upstream returned an error frame";
|
|
9337
|
+
var PREMIUM_MODEL_NOTICE = "This model needs premium credits. Get some with /buy-credits, or switch to another model with /model.";
|
|
9128
9338
|
var PROVIDER_FAILING_NOTICE = "This model's provider is failing right now. Try again in a moment, or switch models with /model.";
|
|
9339
|
+
var SIGNIN_EXPIRED_NOTICE = "Your free-pi sign-in has expired. Run /login, or restart free-pi-cli.";
|
|
9340
|
+
var CONNECTION_NOTICE = "Can't reach free-pi right now (connection error). Check your internet connection.";
|
|
9341
|
+
var CONTENT_FILTER_NOTICE = "The model's reply was blocked by a content filter. Try rephrasing your request.";
|
|
9129
9342
|
var FREE_OFFER_TITLE = "Switch to the free model and resend this prompt?";
|
|
9130
9343
|
var FREE_OFFER_ACCEPT = "Switch and resend";
|
|
9131
9344
|
var FREE_OFFER_DECLINE = "Stay on this model";
|
|
@@ -9185,6 +9398,8 @@ function parseErrorEnvelope(errorMessage) {
|
|
|
9185
9398
|
}
|
|
9186
9399
|
}
|
|
9187
9400
|
function formatLimitEnvelope(env, opts) {
|
|
9401
|
+
if (env.code === "model_requires_credits" && !/premium credit/i.test(env.message))
|
|
9402
|
+
return PREMIUM_MODEL_NOTICE;
|
|
9188
9403
|
if (env.code === "concurrent_session" || SERVER_MESSAGE_CODES.has(env.code) || env.prompt === "github_signin" || env.copy_variant === "treatment" || typeof env.remaining_credits_today !== "number") {
|
|
9189
9404
|
return env.message;
|
|
9190
9405
|
}
|
|
@@ -9230,6 +9445,36 @@ function lastUserEntryId(ctx) {
|
|
|
9230
9445
|
}
|
|
9231
9446
|
return "";
|
|
9232
9447
|
}
|
|
9448
|
+
var RAW_ERROR_FIELD = "freePiRawError";
|
|
9449
|
+
function rawErrorMessage(msg) {
|
|
9450
|
+
if (!msg)
|
|
9451
|
+
return;
|
|
9452
|
+
const m = msg;
|
|
9453
|
+
return typeof m[RAW_ERROR_FIELD] === "string" ? m[RAW_ERROR_FIELD] : m.errorMessage;
|
|
9454
|
+
}
|
|
9455
|
+
function envelopeOf(raw) {
|
|
9456
|
+
return raw === UPSTREAM_ERROR_FRAME_MESSAGE ? { status: 502, code: "upstream_error", message: UPSTREAM_ERROR_FRAME_MESSAGE } : parseErrorEnvelope(raw);
|
|
9457
|
+
}
|
|
9458
|
+
var friendlyText = (env, timeZone) => env.code === "upstream_error" ? PROVIDER_FAILING_NOTICE : env.code === "unauthorized" ? SIGNIN_EXPIRED_NOTICE : formatLimitEnvelope(env, { timeZone });
|
|
9459
|
+
var isFreePiProvider = (provider) => provider === PROVIDER_NAME || provider === DEFENSE_PROVIDER_NAME;
|
|
9460
|
+
function friendlyRawText(raw) {
|
|
9461
|
+
const text = raw.trim();
|
|
9462
|
+
if (/^(?:connection error\.?|request timed out\.?|terminated|fetch failed)$/i.test(text))
|
|
9463
|
+
return CONNECTION_NOTICE;
|
|
9464
|
+
const http = /^(5\d\d)(?: status code \(no body\)$|\s*<(?:!doctype html|html)\b)/i.exec(text);
|
|
9465
|
+
if (http)
|
|
9466
|
+
return `${PROVIDER_FAILING_NOTICE} (HTTP ${http[1]})`;
|
|
9467
|
+
const finish = /^Provider finish_reason: (\w+)$/.exec(text);
|
|
9468
|
+
if (finish)
|
|
9469
|
+
return finish[1] === "content_filter" ? CONTENT_FILTER_NOTICE : `${PROVIDER_FAILING_NOTICE} (${finish[1].replace(/_/g, " ")})`;
|
|
9470
|
+
return;
|
|
9471
|
+
}
|
|
9472
|
+
function friendlyError(raw, timeZone) {
|
|
9473
|
+
if (typeof raw !== "string")
|
|
9474
|
+
return;
|
|
9475
|
+
const env = envelopeOf(raw);
|
|
9476
|
+
return env ? friendlyText(env, timeZone) : friendlyRawText(raw);
|
|
9477
|
+
}
|
|
9233
9478
|
function createErrorNoticeExtension(opts = {}) {
|
|
9234
9479
|
const lastAt = new Map;
|
|
9235
9480
|
const now = opts.now ?? Date.now;
|
|
@@ -9262,21 +9507,44 @@ function createErrorNoticeExtension(opts = {}) {
|
|
|
9262
9507
|
return {
|
|
9263
9508
|
name: "free-pi-error-notice",
|
|
9264
9509
|
factory(pi) {
|
|
9510
|
+
pi.on("message_end", (event) => {
|
|
9511
|
+
const msg = event.message;
|
|
9512
|
+
if (msg.role !== "assistant" || !isFreePiProvider(msg.provider) || msg.stopReason !== "error" || typeof msg.errorMessage !== "string")
|
|
9513
|
+
return;
|
|
9514
|
+
const env = envelopeOf(msg.errorMessage);
|
|
9515
|
+
const friendly = env ? friendlyText(env, timeZone) : friendlyRawText(msg.errorMessage);
|
|
9516
|
+
if (friendly === undefined)
|
|
9517
|
+
return;
|
|
9518
|
+
const same = (text) => {
|
|
9519
|
+
const next = { ...msg, errorMessage: text };
|
|
9520
|
+
return isRetryableAssistantError(next) === retryable && isContextOverflow(next, 0) === overflow;
|
|
9521
|
+
};
|
|
9522
|
+
const retryable = isRetryableAssistantError(msg);
|
|
9523
|
+
const overflow = isContextOverflow(msg, 0);
|
|
9524
|
+
let text = friendly;
|
|
9525
|
+
if (env && retryable && !isRetryableAssistantError({ ...msg, errorMessage: text }))
|
|
9526
|
+
text += ` (HTTP ${env.status})`;
|
|
9527
|
+
if (!same(text))
|
|
9528
|
+
return;
|
|
9529
|
+
return { message: { ...msg, errorMessage: text, [RAW_ERROR_FIELD]: msg.errorMessage } };
|
|
9530
|
+
});
|
|
9265
9531
|
pi.on("turn_end", async (event, ctx) => {
|
|
9266
9532
|
lastTurnEntryId = event.messageEntryId;
|
|
9267
9533
|
const msg = event.message;
|
|
9268
9534
|
if (!msg || msg.stopReason !== "error")
|
|
9269
9535
|
return;
|
|
9270
|
-
const env =
|
|
9536
|
+
const env = envelopeOf(rawErrorMessage(msg));
|
|
9271
9537
|
if (!env)
|
|
9272
9538
|
return;
|
|
9273
9539
|
const t = now();
|
|
9274
9540
|
if (t - (lastAt.get(env.code) ?? -Infinity) >= NOTICE_DEDUPE_MS) {
|
|
9275
9541
|
lastAt.set(env.code, t);
|
|
9276
9542
|
const extra = opts.extraNotice?.(env);
|
|
9277
|
-
const
|
|
9543
|
+
const shown = msg[RAW_ERROR_FIELD] !== undefined;
|
|
9544
|
+
const text = shown ? extra : friendlyText(env, timeZone) + (extra ? `
|
|
9278
9545
|
${extra}` : "");
|
|
9279
|
-
|
|
9546
|
+
if (text)
|
|
9547
|
+
ctx.ui.notify(text, LIMIT_CODES.has(env.code) ? "info" : env.status >= 500 ? "error" : "warning");
|
|
9280
9548
|
}
|
|
9281
9549
|
await offerFreeModel(pi, env, event.messageEntryId, ctx);
|
|
9282
9550
|
});
|
|
@@ -9770,6 +10038,20 @@ function installBottomPin(mode) {
|
|
|
9770
10038
|
return [...lines, ...Array(bottomPadding(lines.length, other, rows)).fill("")];
|
|
9771
10039
|
};
|
|
9772
10040
|
}
|
|
10041
|
+
var IMAGE_MARKS = ["\x1B_G", "\x1B]1337;File="];
|
|
10042
|
+
function clampLines(lines, width) {
|
|
10043
|
+
return lines.map((l) => IMAGE_MARKS.some((m) => l.includes(m)) || visibleWidth3(l) <= width ? l : truncateToWidth6(l, Math.max(0, width), ""));
|
|
10044
|
+
}
|
|
10045
|
+
function installWidthGuard(mode) {
|
|
10046
|
+
const m = mode;
|
|
10047
|
+
for (const k of ["headerContainer", "loadedResourcesContainer", "chatContainer", "pendingMessagesContainer", "statusContainer", "widgetContainerAbove", "editorContainer", "widgetContainerBelow", "footerContainer"]) {
|
|
10048
|
+
const c = m[k];
|
|
10049
|
+
if (!c)
|
|
10050
|
+
continue;
|
|
10051
|
+
const render = c.render.bind(c);
|
|
10052
|
+
c.render = (w) => clampLines(render(w), w);
|
|
10053
|
+
}
|
|
10054
|
+
}
|
|
9773
10055
|
|
|
9774
10056
|
// src/guest-handoff.ts
|
|
9775
10057
|
var LOGIN_VALUE_LINE = "sign in free with GitHub: full daily allowance, FREE abliterated DeepSeek, history saved";
|
|
@@ -9859,6 +10141,14 @@ function mergeAttribution(a, b) {
|
|
|
9859
10141
|
|
|
9860
10142
|
// src/changelog.ts
|
|
9861
10143
|
var CHANGELOG_HIGHLIGHTS = [
|
|
10144
|
+
{
|
|
10145
|
+
version: "0.6.20",
|
|
10146
|
+
highlights: ["Error messages are now plain sentences instead of raw server text."]
|
|
10147
|
+
},
|
|
10148
|
+
{
|
|
10149
|
+
version: "0.6.19",
|
|
10150
|
+
highlights: ["The ad banner now refreshes when you send a message, not on a timer."]
|
|
10151
|
+
},
|
|
9862
10152
|
{
|
|
9863
10153
|
version: "0.6.18",
|
|
9864
10154
|
highlights: ["Ads load correctly from your first prompt."]
|
|
@@ -10219,6 +10509,7 @@ function highlightsFor(version) {
|
|
|
10219
10509
|
|
|
10220
10510
|
// src/commands.ts
|
|
10221
10511
|
import { access } from "node:fs/promises";
|
|
10512
|
+
import { basename as basename3 } from "node:path";
|
|
10222
10513
|
|
|
10223
10514
|
// src/auth-flow.ts
|
|
10224
10515
|
class UnauthorizedError extends Error {
|
|
@@ -10233,13 +10524,23 @@ async function postJson(baseUrl, path, body, extraHeaders = {}) {
|
|
|
10233
10524
|
body: JSON.stringify(body)
|
|
10234
10525
|
});
|
|
10235
10526
|
}
|
|
10527
|
+
var NETWORK_TEXT = "Can't reach free-pi. Check your internet connection and try again.";
|
|
10528
|
+
var RATE_LIMIT_TEXT = "Too many sign-in attempts from your network. Wait a minute and try again.";
|
|
10529
|
+
var UNEXPECTED_REPLY_TEXT = "free-pi sent an unexpected reply. If you're on public wifi, sign in to the network first, then try again.";
|
|
10530
|
+
async function parseReply(res, schema) {
|
|
10531
|
+
try {
|
|
10532
|
+
return schema.parse(await res.json());
|
|
10533
|
+
} catch (err) {
|
|
10534
|
+
throw new Error(UNEXPECTED_REPLY_TEXT, { cause: err });
|
|
10535
|
+
}
|
|
10536
|
+
}
|
|
10236
10537
|
async function postJsonRetry(baseUrl, path, body, attempts = 4, sleep = (ms) => new Promise((r) => setTimeout(r, ms)), extraHeaders = {}) {
|
|
10237
10538
|
for (let attempt = 1;; attempt++) {
|
|
10238
10539
|
try {
|
|
10239
10540
|
return await postJson(baseUrl, path, body, extraHeaders);
|
|
10240
10541
|
} catch (err) {
|
|
10241
10542
|
if (attempt >= attempts) {
|
|
10242
|
-
throw new Error(
|
|
10543
|
+
throw new Error(NETWORK_TEXT, { cause: err });
|
|
10243
10544
|
}
|
|
10244
10545
|
await sleep(1500 * attempt);
|
|
10245
10546
|
}
|
|
@@ -10256,6 +10557,16 @@ function refusalMessage(status, body) {
|
|
|
10256
10557
|
}
|
|
10257
10558
|
return;
|
|
10258
10559
|
}
|
|
10560
|
+
function failureText(status, body) {
|
|
10561
|
+
if (status === 429 || body?.code === "rate_limited" && status < 500)
|
|
10562
|
+
return RATE_LIMIT_TEXT;
|
|
10563
|
+
const refusal = refusalMessage(status, body);
|
|
10564
|
+
if (refusal)
|
|
10565
|
+
return refusal;
|
|
10566
|
+
if (status >= 500)
|
|
10567
|
+
return `free-pi is having trouble right now (HTTP ${status}). Try again in a minute.`;
|
|
10568
|
+
return `free-pi could not complete sign-in (HTTP ${status}). Try again in a minute.`;
|
|
10569
|
+
}
|
|
10259
10570
|
async function readRefusal(res) {
|
|
10260
10571
|
return await res.json().catch(() => {
|
|
10261
10572
|
return;
|
|
@@ -10264,9 +10575,9 @@ async function readRefusal(res) {
|
|
|
10264
10575
|
async function startDeviceFlow(baseUrl) {
|
|
10265
10576
|
const res = await postJsonRetry(baseUrl, "/auth/github/device", {});
|
|
10266
10577
|
if (!res.ok) {
|
|
10267
|
-
throw new Error(
|
|
10578
|
+
throw new Error(failureText(res.status, await readRefusal(res)));
|
|
10268
10579
|
}
|
|
10269
|
-
return
|
|
10580
|
+
return parseReply(res, GithubDeviceResponseSchema);
|
|
10270
10581
|
}
|
|
10271
10582
|
async function startConnect(baseUrl, consentVersion, attribution = {}, client = {}) {
|
|
10272
10583
|
const res = await postJsonRetry(baseUrl, "/auth/connect/start", {
|
|
@@ -10276,8 +10587,8 @@ async function startConnect(baseUrl, consentVersion, attribution = {}, client =
|
|
|
10276
10587
|
...attribution.referral_code ? { referral_code: attribution.referral_code } : {}
|
|
10277
10588
|
}, undefined, undefined, { [SIGNIN_EXP_HEADER]: "1" });
|
|
10278
10589
|
if (!res.ok)
|
|
10279
|
-
throw new Error(
|
|
10280
|
-
return
|
|
10590
|
+
throw new Error(failureText(res.status, await readRefusal(res)));
|
|
10591
|
+
return parseReply(res, ConnectStartResponseSchema);
|
|
10281
10592
|
}
|
|
10282
10593
|
async function submitConsent(baseUrl, sessionId, consentVersion, attribution = {}) {
|
|
10283
10594
|
const res = await postJsonRetry(baseUrl, "/auth/consent", {
|
|
@@ -10287,7 +10598,7 @@ async function submitConsent(baseUrl, sessionId, consentVersion, attribution = {
|
|
|
10287
10598
|
...attribution.referral_code ? { referral_code: attribution.referral_code } : {}
|
|
10288
10599
|
});
|
|
10289
10600
|
if (!res.ok) {
|
|
10290
|
-
throw new Error(
|
|
10601
|
+
throw new Error(failureText(res.status, await readRefusal(res)));
|
|
10291
10602
|
}
|
|
10292
10603
|
}
|
|
10293
10604
|
function deviceReport() {
|
|
@@ -10320,8 +10631,8 @@ async function startGuestSession(baseUrl, consentVersion, attribution = {}) {
|
|
|
10320
10631
|
throw new GuestUnavailableError(body?.reason ?? "disabled");
|
|
10321
10632
|
}
|
|
10322
10633
|
if (!res.ok)
|
|
10323
|
-
throw new Error(
|
|
10324
|
-
const parsed =
|
|
10634
|
+
throw new Error(failureText(res.status, await readRefusal(res)));
|
|
10635
|
+
const parsed = await parseReply(res, GuestStartResponseSchema);
|
|
10325
10636
|
return {
|
|
10326
10637
|
token: parsed.token,
|
|
10327
10638
|
allowance_usd: parsed.allowance_usd,
|
|
@@ -10401,7 +10712,7 @@ async function pollForToken(baseUrl, sessionId, interval, sleep, opts = {}) {
|
|
|
10401
10712
|
throw new PollAbortedError;
|
|
10402
10713
|
consecutiveNetworkFailures++;
|
|
10403
10714
|
if (consecutiveNetworkFailures >= 6) {
|
|
10404
|
-
throw new Error(
|
|
10715
|
+
throw new Error(NETWORK_TEXT, { cause: err });
|
|
10405
10716
|
}
|
|
10406
10717
|
await sleep(Math.max(waitSeconds, 5) * 1000);
|
|
10407
10718
|
continue;
|
|
@@ -10419,9 +10730,9 @@ async function pollForToken(baseUrl, sessionId, interval, sleep, opts = {}) {
|
|
|
10419
10730
|
throw new LoginTimeoutError;
|
|
10420
10731
|
if (isExpiredTokenPoll(body))
|
|
10421
10732
|
throw new DeviceCodeExpiredError;
|
|
10422
|
-
throw new Error(
|
|
10733
|
+
throw new Error(failureText(res.status, body));
|
|
10423
10734
|
}
|
|
10424
|
-
const parsed =
|
|
10735
|
+
const parsed = await parseReply(res, AuthTokenResponseSchema);
|
|
10425
10736
|
if (opts.signal?.aborted)
|
|
10426
10737
|
throw new PollAbortedError;
|
|
10427
10738
|
if ("token" in parsed)
|
|
@@ -11395,7 +11706,7 @@ async function runLoginUnguarded(opts, ctx, lead) {
|
|
|
11395
11706
|
ctx.ui.notify(LOGIN_DENIED_LINE, "info");
|
|
11396
11707
|
return false;
|
|
11397
11708
|
}
|
|
11398
|
-
const reason = err instanceof Error && err.message ?
|
|
11709
|
+
const reason = err instanceof Error && err.message ? `: ${err.message.replace(/\.$/, "")}` : "";
|
|
11399
11710
|
ctx.ui.notify(`Couldn't finish GitHub sign-in${reason}. Run /login to try again.`, "error");
|
|
11400
11711
|
return false;
|
|
11401
11712
|
}
|
|
@@ -11413,6 +11724,26 @@ function installLoginHandler(mode, opts) {
|
|
|
11413
11724
|
return runLogin(opts, { ui: { notify } });
|
|
11414
11725
|
};
|
|
11415
11726
|
}
|
|
11727
|
+
var IMPORT_NEXT_STEP = "Run /import again, or start a fresh session.";
|
|
11728
|
+
var FS_REASONS = {
|
|
11729
|
+
EACCES: "permission denied",
|
|
11730
|
+
EPERM: "permission denied",
|
|
11731
|
+
ENOENT: "not found",
|
|
11732
|
+
EISDIR: "that is a folder, not a file",
|
|
11733
|
+
EMFILE: "too many files are open",
|
|
11734
|
+
ENOSPC: "the disk is full"
|
|
11735
|
+
};
|
|
11736
|
+
function friendlyImportError(err, action = "import") {
|
|
11737
|
+
const e = err;
|
|
11738
|
+
const code = typeof e?.code === "string" && /^E[A-Z0-9]+$/.test(e.code) ? e.code : undefined;
|
|
11739
|
+
if (code) {
|
|
11740
|
+
const file = typeof e.path === "string" && e.path ? basename3(e.path) : undefined;
|
|
11741
|
+
const reason = FS_REASONS[code] ?? `error ${code}`;
|
|
11742
|
+
return `Couldn't ${action}${file ? ` ${file}` : ""}: ${reason}. ${IMPORT_NEXT_STEP}`;
|
|
11743
|
+
}
|
|
11744
|
+
const msg = (err instanceof Error ? err.message : String(err)).trim().replace(/[.\s]+$/, "");
|
|
11745
|
+
return `Couldn't ${action}: ${msg || "unknown error"}. ${IMPORT_NEXT_STEP}`;
|
|
11746
|
+
}
|
|
11416
11747
|
function installImportHandler(mode, opts) {
|
|
11417
11748
|
const m = mode;
|
|
11418
11749
|
mode.handleImportCommand = async (text) => {
|
|
@@ -11435,11 +11766,11 @@ function installImportHandler(mode, opts) {
|
|
|
11435
11766
|
for (let waited = 0;m.session.isCompacting && waited < 60000; waited += 100)
|
|
11436
11767
|
await new Promise((r) => setTimeout(r, 100));
|
|
11437
11768
|
const options = m.session.isStreaming ? { streamingBehavior: "followUp" } : undefined;
|
|
11438
|
-
m.session.prompt(message, options).catch((err) => m.showError(err
|
|
11769
|
+
m.session.prompt(message, options).catch((err) => m.showError(friendlyImportError(err, "send the import")));
|
|
11439
11770
|
}
|
|
11440
11771
|
});
|
|
11441
11772
|
} catch (err) {
|
|
11442
|
-
m.showError(
|
|
11773
|
+
m.showError(friendlyImportError(err));
|
|
11443
11774
|
}
|
|
11444
11775
|
};
|
|
11445
11776
|
}
|
|
@@ -11587,7 +11918,7 @@ function createGuestLoginExtension(opts) {
|
|
|
11587
11918
|
if (!opts.isGuest?.())
|
|
11588
11919
|
return;
|
|
11589
11920
|
const msg = event.message;
|
|
11590
|
-
const envelope = msg?.stopReason === "error" ? parseErrorEnvelope(msg
|
|
11921
|
+
const envelope = msg?.stopReason === "error" ? parseErrorEnvelope(rawErrorMessage(msg)) : undefined;
|
|
11591
11922
|
if (envelope && (envelope.code === "guest_allowance" || envelope.code === "guest_unavailable" || envelope.prompt === "github_signin")) {
|
|
11592
11923
|
if (!inFlight && !signedIn) {
|
|
11593
11924
|
const busy = envelope.code === "guest_allowance" && (envelope.remaining_credits_today ?? 0) > 0;
|
|
@@ -11697,7 +12028,7 @@ import { mkdirSync as mkdirSync2, readFileSync as readFileSync4, writeFileSync a
|
|
|
11697
12028
|
import { homedir as homedir4 } from "node:os";
|
|
11698
12029
|
import { dirname as dirname8 } from "node:path";
|
|
11699
12030
|
import { ToolExecutionComponent } from "@earendil-works/pi-coding-agent";
|
|
11700
|
-
import { Box, Text as Text2, truncateToWidth as truncateToWidth7 } from "@earendil-works/pi-tui";
|
|
12031
|
+
import { Box, Text as Text2, truncateToWidth as truncateToWidth7, visibleWidth as visibleWidth4 } from "@earendil-works/pi-tui";
|
|
11701
12032
|
var TOOL_MODES = ["compact", "full", "hidden"];
|
|
11702
12033
|
var TOOLS_USAGE = "Usage: /tools compact|full|hidden (compact: one line per tool call; full: every call in full; hidden: just a count)";
|
|
11703
12034
|
function readToolMode(path) {
|
|
@@ -11797,12 +12128,30 @@ function toolLines(i) {
|
|
|
11797
12128
|
}
|
|
11798
12129
|
return done;
|
|
11799
12130
|
}
|
|
12131
|
+
var graphemes = new Intl.Segmenter(undefined, { granularity: "grapheme" });
|
|
12132
|
+
function wrapByWidth(text, room) {
|
|
12133
|
+
const clean = text.replace(/\x1b\][\s\S]*?(?:\x07|\x1b\\)|\x1b\[[0-9;?]*[ -\/]*[@-~]/g, "").replace(/\t/g, " ").replace(/[\x00-\x08\x0a-\x1f\x7f-\x9f]/g, "");
|
|
12134
|
+
const pieces = [];
|
|
12135
|
+
let cur = "";
|
|
12136
|
+
let curW = 0;
|
|
12137
|
+
for (const { segment } of graphemes.segment(clean)) {
|
|
12138
|
+
const w = visibleWidth4(segment);
|
|
12139
|
+
if (curW + w > room && cur) {
|
|
12140
|
+
pieces.push(cur);
|
|
12141
|
+
cur = "";
|
|
12142
|
+
curW = 0;
|
|
12143
|
+
}
|
|
12144
|
+
cur += segment;
|
|
12145
|
+
curW += w;
|
|
12146
|
+
}
|
|
12147
|
+
return [...pieces, cur];
|
|
12148
|
+
}
|
|
11800
12149
|
function component(rows, theme, pad) {
|
|
11801
12150
|
return {
|
|
11802
12151
|
render: (w) => rows.flatMap((r) => {
|
|
11803
12152
|
const room = Math.max(1, w - pad);
|
|
11804
|
-
const pieces = r.raw ?
|
|
11805
|
-
return pieces.map((t) => " ".repeat(pad) + theme.fg(r.error ? "error" : "dim", t));
|
|
12153
|
+
const pieces = r.raw ? wrapByWidth(r.text, room) : [truncateToWidth7(r.text, room, "…")];
|
|
12154
|
+
return pieces.map((t) => truncateToWidth7(" ".repeat(pad) + theme.fg(r.error ? "error" : "dim", t), Math.max(0, w), ""));
|
|
11806
12155
|
}),
|
|
11807
12156
|
invalidate() {}
|
|
11808
12157
|
};
|
|
@@ -12074,16 +12423,63 @@ function resolveFreePiScope(modelRuntime, providerName, modelIds, env = process.
|
|
|
12074
12423
|
return scoped;
|
|
12075
12424
|
}
|
|
12076
12425
|
|
|
12426
|
+
// src/default-model.ts
|
|
12427
|
+
import { mkdirSync as mkdirSync3, readFileSync as readFileSync5, writeFileSync as writeFileSync3 } from "node:fs";
|
|
12428
|
+
import { dirname as dirname9, join as join8 } from "node:path";
|
|
12429
|
+
var getDefaultModelPath = (agentDir) => join8(agentDir, "default-model.json");
|
|
12430
|
+
function readDefaultModel(agentDir) {
|
|
12431
|
+
if (!agentDir)
|
|
12432
|
+
return;
|
|
12433
|
+
try {
|
|
12434
|
+
const v = JSON.parse(readFileSync5(getDefaultModelPath(agentDir), "utf8"));
|
|
12435
|
+
return typeof v.provider === "string" && typeof v.model === "string" && v.provider && v.model ? { provider: v.provider, model: v.model } : undefined;
|
|
12436
|
+
} catch {
|
|
12437
|
+
return;
|
|
12438
|
+
}
|
|
12439
|
+
}
|
|
12440
|
+
|
|
12441
|
+
class DefaultModelStorage {
|
|
12442
|
+
agentDir;
|
|
12443
|
+
global;
|
|
12444
|
+
project;
|
|
12445
|
+
constructor(agentDir) {
|
|
12446
|
+
this.agentDir = agentDir;
|
|
12447
|
+
}
|
|
12448
|
+
withLock(scope, fn) {
|
|
12449
|
+
const next = fn(scope === "global" ? this.global : this.project);
|
|
12450
|
+
if (next === undefined)
|
|
12451
|
+
return;
|
|
12452
|
+
if (scope === "project") {
|
|
12453
|
+
this.project = next;
|
|
12454
|
+
return;
|
|
12455
|
+
}
|
|
12456
|
+
const before = this.global;
|
|
12457
|
+
this.global = next;
|
|
12458
|
+
if (before === undefined || !this.agentDir)
|
|
12459
|
+
return;
|
|
12460
|
+
try {
|
|
12461
|
+
const a = JSON.parse(before);
|
|
12462
|
+
const b = JSON.parse(next);
|
|
12463
|
+
if (a.defaultProvider === b.defaultProvider && a.defaultModel === b.defaultModel)
|
|
12464
|
+
return;
|
|
12465
|
+
if (!b.defaultProvider || !b.defaultModel)
|
|
12466
|
+
return;
|
|
12467
|
+
mkdirSync3(dirname9(getDefaultModelPath(this.agentDir)), { recursive: true });
|
|
12468
|
+
writeFileSync3(getDefaultModelPath(this.agentDir), JSON.stringify({ provider: b.defaultProvider, model: b.defaultModel }));
|
|
12469
|
+
} catch {}
|
|
12470
|
+
}
|
|
12471
|
+
}
|
|
12472
|
+
|
|
12077
12473
|
// src/hive.ts
|
|
12078
12474
|
import { createHash as createHash3 } from "node:crypto";
|
|
12079
12475
|
import { execFileSync } from "node:child_process";
|
|
12080
|
-
import { lstatSync as lstatSync2, readFileSync as
|
|
12081
|
-
import { join as
|
|
12476
|
+
import { lstatSync as lstatSync2, readFileSync as readFileSync6, readlinkSync as readlinkSync2 } from "node:fs";
|
|
12477
|
+
import { join as join10 } from "node:path";
|
|
12082
12478
|
|
|
12083
12479
|
// src/hive-guard.ts
|
|
12084
12480
|
import { existsSync as existsSync4, lstatSync, readdirSync, readlinkSync, realpathSync as realpathSync3 } from "node:fs";
|
|
12085
12481
|
import { homedir as homedir5 } from "node:os";
|
|
12086
|
-
import { basename as
|
|
12482
|
+
import { basename as basename4, dirname as dirname10, isAbsolute as isAbsolute2, join as join9, relative as relative2, resolve as resolve2, sep as sep3 } from "node:path";
|
|
12087
12483
|
import { fileURLToPath as fileURLToPath3 } from "node:url";
|
|
12088
12484
|
|
|
12089
12485
|
// src/subagents.ts
|
|
@@ -12200,8 +12596,10 @@ function lastAssistant(session) {
|
|
|
12200
12596
|
continue;
|
|
12201
12597
|
const text = Array.isArray(m.content) ? m.content.filter((p) => p.type === "text").map((p) => p.text ?? "").join(`
|
|
12202
12598
|
`).trim() : "";
|
|
12203
|
-
if (m.stopReason === "error")
|
|
12204
|
-
|
|
12599
|
+
if (m.stopReason === "error") {
|
|
12600
|
+
const raw = rawErrorMessage(m);
|
|
12601
|
+
return { text, error: (raw && isFreePiProvider(m.provider) && !raw.includes("subagents_unavailable") ? friendlyError(raw) : undefined) ?? (raw || "the model request failed") };
|
|
12602
|
+
}
|
|
12205
12603
|
if (m.stopReason === "aborted")
|
|
12206
12604
|
return { text, error: "aborted" };
|
|
12207
12605
|
return { text };
|
|
@@ -12592,7 +12990,7 @@ var SAFE_READ_TOOLS = ["read", "grep", "find", "ls"];
|
|
|
12592
12990
|
var ALWAYS_ASK = ["codemode", "tool_search", FUSION_PLAN_TOOL, WEB_SEARCH_TOOL_NAME, DOCS_LOOKUP_TOOL_NAME];
|
|
12593
12991
|
var CLASSIFIED = ["bash", "write", "edit"];
|
|
12594
12992
|
var SECRET_BASENAME = [/^\.env(\..*)?$/, /\.pem$/, /^id_(rsa|dsa|ecdsa|ed25519)/];
|
|
12595
|
-
var SECRET_HOME_DIRS = [".ssh", ".aws",
|
|
12993
|
+
var SECRET_HOME_DIRS = [".ssh", ".aws", join9(".config", "freepi"), ".free-pi"];
|
|
12596
12994
|
var SECRET_IN_TEXT = /(?:^|[\s"'=:/])\.env(?:\.[\w.-]+)?(?=$|[\s"';|&)])|\.pem\b|\bid_(?:rsa|dsa|ecdsa|ed25519)|\.ssh\b|\.aws\b|\.config\/freepi|\.free-pi\b/;
|
|
12597
12995
|
var BASH_DENY = [
|
|
12598
12996
|
[/\brm\s+(?:-\S+\s+)*(?:\/|~|\$HOME)\*?(?:\s|$)/, "destructive command"],
|
|
@@ -12615,7 +13013,7 @@ function protectedLinkTargets(root, limit) {
|
|
|
12615
13013
|
const note = (link) => {
|
|
12616
13014
|
let target;
|
|
12617
13015
|
try {
|
|
12618
|
-
target = resolve2(
|
|
13016
|
+
target = resolve2(dirname10(link), readlinkSync(link));
|
|
12619
13017
|
} catch {
|
|
12620
13018
|
return;
|
|
12621
13019
|
}
|
|
@@ -12625,7 +13023,7 @@ function protectedLinkTargets(root, limit) {
|
|
|
12625
13023
|
out.push(real);
|
|
12626
13024
|
};
|
|
12627
13025
|
for (const entry of [".git/hooks", ".git/config"]) {
|
|
12628
|
-
const link =
|
|
13026
|
+
const link = join9(root, entry);
|
|
12629
13027
|
try {
|
|
12630
13028
|
if (lstatSync(link).isSymbolicLink())
|
|
12631
13029
|
note(link);
|
|
@@ -12644,7 +13042,7 @@ function protectedLinkTargets(root, limit) {
|
|
|
12644
13042
|
for (const entry of entries) {
|
|
12645
13043
|
if (++seen > limit)
|
|
12646
13044
|
return null;
|
|
12647
|
-
const path =
|
|
13045
|
+
const path = join9(dir, entry.name);
|
|
12648
13046
|
if (entry.isSymbolicLink()) {
|
|
12649
13047
|
if (protectedByName(relative2(root, path)))
|
|
12650
13048
|
note(path);
|
|
@@ -12694,7 +13092,7 @@ function resolveLikePi(raw, cwd, home, platform) {
|
|
|
12694
13092
|
if (p === "~")
|
|
12695
13093
|
p = home;
|
|
12696
13094
|
else if (p.startsWith("~/") || platform === "win32" && p.startsWith("~\\"))
|
|
12697
|
-
p =
|
|
13095
|
+
p = join9(home, p.slice(2));
|
|
12698
13096
|
if (/^file:\/\//.test(p))
|
|
12699
13097
|
p = fileURLToPath3(p);
|
|
12700
13098
|
return resolve2(cwd, p);
|
|
@@ -12704,17 +13102,17 @@ function realpathDeepest(p) {
|
|
|
12704
13102
|
const rest = [];
|
|
12705
13103
|
for (;; ) {
|
|
12706
13104
|
try {
|
|
12707
|
-
return
|
|
13105
|
+
return join9(realpathSync3(current), ...rest.reverse());
|
|
12708
13106
|
} catch {
|
|
12709
13107
|
try {
|
|
12710
13108
|
lstatSync(current);
|
|
12711
13109
|
return null;
|
|
12712
13110
|
} catch {}
|
|
12713
13111
|
}
|
|
12714
|
-
const parent =
|
|
13112
|
+
const parent = dirname10(current);
|
|
12715
13113
|
if (parent === current)
|
|
12716
13114
|
return null;
|
|
12717
|
-
rest.push(
|
|
13115
|
+
rest.push(basename4(current));
|
|
12718
13116
|
current = parent;
|
|
12719
13117
|
}
|
|
12720
13118
|
}
|
|
@@ -12723,10 +13121,10 @@ function isInside(root, target) {
|
|
|
12723
13121
|
return rel === "" || rel !== ".." && !rel.startsWith(`..${sep3}`) && !isAbsolute2(rel);
|
|
12724
13122
|
}
|
|
12725
13123
|
function isSecret(target, home) {
|
|
12726
|
-
if (SECRET_BASENAME.some((re) => re.test(
|
|
13124
|
+
if (SECRET_BASENAME.some((re) => re.test(basename4(target))))
|
|
12727
13125
|
return true;
|
|
12728
13126
|
return SECRET_HOME_DIRS.some((dir) => {
|
|
12729
|
-
const lexical =
|
|
13127
|
+
const lexical = join9(home, dir);
|
|
12730
13128
|
return isInside(lexical, target) || isInside(realpathDeepest(lexical) ?? lexical, target);
|
|
12731
13129
|
});
|
|
12732
13130
|
}
|
|
@@ -12747,7 +13145,7 @@ function scanForSecret(dir) {
|
|
|
12747
13145
|
if (SECRET_BASENAME.some((re) => re.test(entry.name)))
|
|
12748
13146
|
return "yes";
|
|
12749
13147
|
if (entry.isDirectory())
|
|
12750
|
-
queue.push(
|
|
13148
|
+
queue.push(join9(current, entry.name));
|
|
12751
13149
|
}
|
|
12752
13150
|
}
|
|
12753
13151
|
return "no";
|
|
@@ -12871,6 +13269,33 @@ async function decideToolCall(event, opts) {
|
|
|
12871
13269
|
}
|
|
12872
13270
|
|
|
12873
13271
|
// src/hive.ts
|
|
13272
|
+
var HIVE_ERRORS = new Map(Object.entries({
|
|
13273
|
+
rate_limited: "Hive: slow down, too many messages in a short time.",
|
|
13274
|
+
pending: "Hive: another request is still waiting for an answer.",
|
|
13275
|
+
not_driver: "Hive: only the person driving the room can do that.",
|
|
13276
|
+
already_driver: "Hive: you are already driving the room.",
|
|
13277
|
+
unknown_conn: "Hive: that person has left the room.",
|
|
13278
|
+
unknown_prompt: "Hive: that prompt is no longer waiting.",
|
|
13279
|
+
unknown_request: "Hive: that request is no longer waiting.",
|
|
13280
|
+
bad_frame: "Hive: the room got a message it could not read. Try again.",
|
|
13281
|
+
internal: "Hive: something went wrong on the server. Try again."
|
|
13282
|
+
}));
|
|
13283
|
+
var HIVE_CLOSED = new Map(Object.entries({
|
|
13284
|
+
replaced: "Hive room closed: you opened a newer room.",
|
|
13285
|
+
host_left: "Hive room closed: the host left.",
|
|
13286
|
+
ended: "Hive room closed: the host ended it.",
|
|
13287
|
+
expired: "Hive room closed: it expired.",
|
|
13288
|
+
kicked: "Hive room closed: you were removed from it.",
|
|
13289
|
+
shutdown: "Hive room closed: the server is restarting.",
|
|
13290
|
+
no_room: "Hive room closed: the room no longer exists.",
|
|
13291
|
+
unauthorized: "Hive room closed: you are not signed in.",
|
|
13292
|
+
auth_timeout: "Hive room closed: sign-in took too long.",
|
|
13293
|
+
busy: "Hive room closed: the server is busy. Try again soon.",
|
|
13294
|
+
overloaded: "Hive room closed: the server is busy. Try again soon.",
|
|
13295
|
+
slow: "Hive room closed: the connection was too slow."
|
|
13296
|
+
}));
|
|
13297
|
+
var hiveErrorText = (code) => HIVE_ERRORS.get(code) ?? `Hive: ${code}`;
|
|
13298
|
+
var hiveClosedText = (reason) => HIVE_CLOSED.get(reason) ?? `Hive room closed (${reason}).`;
|
|
12874
13299
|
var RUN_IN_SANDBOX = "Run in sandbox";
|
|
12875
13300
|
var RUN_OUTSIDE = "Run outside the sandbox";
|
|
12876
13301
|
var DENY = "Deny";
|
|
@@ -12894,7 +13319,7 @@ function fingerprint(path) {
|
|
|
12894
13319
|
return "";
|
|
12895
13320
|
if (st.size > HASH_MAX_BYTES)
|
|
12896
13321
|
return `${st.size}:${st.mtimeMs}`;
|
|
12897
|
-
return createHash3("sha256").update(
|
|
13322
|
+
return createHash3("sha256").update(readFileSync6(path)).digest("hex");
|
|
12898
13323
|
} catch {
|
|
12899
13324
|
return "";
|
|
12900
13325
|
}
|
|
@@ -12924,7 +13349,7 @@ function worktreeSnapshot(g) {
|
|
|
12924
13349
|
const status = entry.slice(0, 2);
|
|
12925
13350
|
if (status[0] === "R" || status[0] === "C")
|
|
12926
13351
|
i++;
|
|
12927
|
-
files.set(entry.slice(3), `${status}:${fingerprint(
|
|
13352
|
+
files.set(entry.slice(3), `${status}:${fingerprint(join10(root, entry.slice(3)))}`);
|
|
12928
13353
|
}
|
|
12929
13354
|
return { files, head: runGit(g, "rev-parse", "HEAD")?.trim() };
|
|
12930
13355
|
} catch (e) {
|
|
@@ -13086,10 +13511,13 @@ function createHiveExtension(opts) {
|
|
|
13086
13511
|
};
|
|
13087
13512
|
socket.onopen = () => socket.send(JSON.stringify({ type: "auth", token }));
|
|
13088
13513
|
socket.onerror = failed;
|
|
13089
|
-
socket.onclose = () => {
|
|
13514
|
+
socket.onclose = (event) => {
|
|
13090
13515
|
if (ws !== socket)
|
|
13091
13516
|
return;
|
|
13092
|
-
|
|
13517
|
+
const known = event?.reason ? HIVE_CLOSED.get(event.reason) : undefined;
|
|
13518
|
+
if (known)
|
|
13519
|
+
endRoom(known);
|
|
13520
|
+
else if (!roomUrl)
|
|
13093
13521
|
failed();
|
|
13094
13522
|
else
|
|
13095
13523
|
endRoom("Hive room closed.");
|
|
@@ -13163,10 +13591,10 @@ Guests sign in with GitHub to watch and chat. They can ask to drive; you approve
|
|
|
13163
13591
|
break;
|
|
13164
13592
|
case "error":
|
|
13165
13593
|
if (frame.code !== "stale")
|
|
13166
|
-
notify(
|
|
13594
|
+
notify(hiveErrorText(frame.code), "warning");
|
|
13167
13595
|
break;
|
|
13168
13596
|
case "closed":
|
|
13169
|
-
endRoom(
|
|
13597
|
+
endRoom(hiveClosedText(frame.reason));
|
|
13170
13598
|
break;
|
|
13171
13599
|
}
|
|
13172
13600
|
}
|
|
@@ -13383,16 +13811,26 @@ import {
|
|
|
13383
13811
|
chmodSync,
|
|
13384
13812
|
existsSync as existsSync5,
|
|
13385
13813
|
lstatSync as lstatSync3,
|
|
13386
|
-
mkdirSync as
|
|
13814
|
+
mkdirSync as mkdirSync4,
|
|
13387
13815
|
mkdtempSync,
|
|
13388
13816
|
readdirSync as readdirSync2,
|
|
13389
13817
|
realpathSync as realpathSync4,
|
|
13390
13818
|
rmSync,
|
|
13391
13819
|
statSync as statSync3,
|
|
13392
|
-
writeFileSync as
|
|
13820
|
+
writeFileSync as writeFileSync4
|
|
13393
13821
|
} from "node:fs";
|
|
13394
13822
|
import { homedir as homedir6, tmpdir } from "node:os";
|
|
13395
|
-
import { dirname as
|
|
13823
|
+
import { dirname as dirname11, join as join11 } from "node:path";
|
|
13824
|
+
var ERRNO_WORDS = { EACCES: "permission denied", EPERM: "operation not permitted", ENOENT: "not found", ENOSPC: "disk full", EROFS: "read-only file system" };
|
|
13825
|
+
function sandboxCause(err) {
|
|
13826
|
+
const e = err;
|
|
13827
|
+
if (typeof e?.code === "string") {
|
|
13828
|
+
const where = [e.syscall, e.path].filter((x) => typeof x === "string").join(" ");
|
|
13829
|
+
return `${Object.hasOwn(ERRNO_WORDS, e.code) ? ERRNO_WORDS[e.code] : e.code}${where ? ` (${where})` : ""}`;
|
|
13830
|
+
}
|
|
13831
|
+
return (err instanceof Error ? err.message : String(err)).split(`
|
|
13832
|
+
`)[0].replace(/\.$/, "");
|
|
13833
|
+
}
|
|
13396
13834
|
var SECRET_NAME_PATTERNS = [
|
|
13397
13835
|
"\\.env(\\..*)?",
|
|
13398
13836
|
"[^/]*\\.pem",
|
|
@@ -13428,7 +13866,7 @@ var TOOLCHAIN_ROOTS = [
|
|
|
13428
13866
|
"Library/pnpm"
|
|
13429
13867
|
];
|
|
13430
13868
|
function toolchainRoots(home, exists = existsSync5) {
|
|
13431
|
-
return TOOLCHAIN_ROOTS.map((r) =>
|
|
13869
|
+
return TOOLCHAIN_ROOTS.map((r) => join11(home, r)).filter((p) => exists(p));
|
|
13432
13870
|
}
|
|
13433
13871
|
var ENV_ALLOW = ["PATH", "LANG", "LC_ALL", "LC_CTYPE", "TERM", "COLORTERM", "TZ", "USER", "LOGNAME", "SHELL", "PI_PROVIDER", "PI_MODEL"];
|
|
13434
13872
|
function under(path, dir) {
|
|
@@ -13446,10 +13884,10 @@ function sandboxEnv(base, o) {
|
|
|
13446
13884
|
for (const k of ENV_ALLOW)
|
|
13447
13885
|
if (base[k] !== undefined)
|
|
13448
13886
|
env[k] = base[k];
|
|
13449
|
-
env.PATH = [...o.developerDir ? [
|
|
13887
|
+
env.PATH = [...o.developerDir ? [join11(o.developerDir, "usr/bin")] : [], ...o.pathPrefix ?? [], ...(base.PATH ?? "/usr/bin:/bin").split(":")].filter((p) => p.startsWith("/") && !under(p, o.project) && !under(realOrSelf(p), o.project)).join(":");
|
|
13450
13888
|
if (o.developerDir)
|
|
13451
13889
|
env.DEVELOPER_DIR = o.developerDir;
|
|
13452
|
-
env.HOME =
|
|
13890
|
+
env.HOME = join11(o.scratch, "home");
|
|
13453
13891
|
env.TMP = env.TEMP = o.scratch;
|
|
13454
13892
|
env.TMPDIR = `${o.scratch}/`;
|
|
13455
13893
|
env.BUN_OPTIONS = "--no-env-file";
|
|
@@ -13459,7 +13897,7 @@ function sandboxEnv(base, o) {
|
|
|
13459
13897
|
env.GIT_CONFIG_COUNT = "1";
|
|
13460
13898
|
env.GIT_CONFIG_KEY_0 = "credential.helper";
|
|
13461
13899
|
env.GIT_CONFIG_VALUE_0 = "";
|
|
13462
|
-
env.npm_config_cache =
|
|
13900
|
+
env.npm_config_cache = join11(o.scratch, "npm-cache");
|
|
13463
13901
|
env.FREEPI_HIVE_CALL = o.callId;
|
|
13464
13902
|
return env;
|
|
13465
13903
|
}
|
|
@@ -13505,7 +13943,7 @@ function scanProject(project, limit = SCAN_LIMIT2) {
|
|
|
13505
13943
|
scan.overflow = true;
|
|
13506
13944
|
break;
|
|
13507
13945
|
}
|
|
13508
|
-
const path =
|
|
13946
|
+
const path = join11(dir, e.name);
|
|
13509
13947
|
if (e.isSymbolicLink())
|
|
13510
13948
|
continue;
|
|
13511
13949
|
if (e.isDirectory()) {
|
|
@@ -13605,7 +14043,7 @@ function developerDir(o = {}) {
|
|
|
13605
14043
|
continue;
|
|
13606
14044
|
try {
|
|
13607
14045
|
const real = realpath(c);
|
|
13608
|
-
if (exists(
|
|
14046
|
+
if (exists(join11(real, "usr/bin/git")))
|
|
13609
14047
|
return real;
|
|
13610
14048
|
} catch {}
|
|
13611
14049
|
}
|
|
@@ -13623,18 +14061,18 @@ function opensslFiles(o = {}) {
|
|
|
13623
14061
|
const files = [];
|
|
13624
14062
|
const dirs = [];
|
|
13625
14063
|
for (const prefix of o.prefixes ?? ["/opt/homebrew", "/usr/local"]) {
|
|
13626
|
-
const etc =
|
|
14064
|
+
const etc = join11(prefix, "etc");
|
|
13627
14065
|
for (const name of list(etc)) {
|
|
13628
14066
|
if (!/^openssl(@[0-9.]+)?$/.test(name))
|
|
13629
14067
|
continue;
|
|
13630
14068
|
for (const f of ["openssl.cnf", "ct_log_list.cnf", "cert.pem"])
|
|
13631
|
-
if (exists(
|
|
13632
|
-
files.push(
|
|
13633
|
-
if (exists(
|
|
13634
|
-
dirs.push(
|
|
14069
|
+
if (exists(join11(etc, name, f)))
|
|
14070
|
+
files.push(join11(etc, name, f));
|
|
14071
|
+
if (exists(join11(etc, name, "certs")))
|
|
14072
|
+
dirs.push(join11(etc, name, "certs"));
|
|
13635
14073
|
}
|
|
13636
|
-
if (exists(
|
|
13637
|
-
files.push(
|
|
14074
|
+
if (exists(join11(etc, "ca-certificates/cert.pem")))
|
|
14075
|
+
files.push(join11(etc, "ca-certificates/cert.pem"));
|
|
13638
14076
|
}
|
|
13639
14077
|
return { files, dirs };
|
|
13640
14078
|
}
|
|
@@ -13642,7 +14080,7 @@ var MAC_DEV_READ = ["/dev/null", "/dev/zero", "/dev/random", "/dev/urandom", "/d
|
|
|
13642
14080
|
var MAC_DEV_WRITE = ["/dev/null", "/dev/tty", "/dev/zero", "/dev/dtracehelper"];
|
|
13643
14081
|
function ancestors(p) {
|
|
13644
14082
|
const out = [];
|
|
13645
|
-
for (let d =
|
|
14083
|
+
for (let d = dirname11(p);; d = dirname11(d)) {
|
|
13646
14084
|
out.push(d);
|
|
13647
14085
|
if (d === "/")
|
|
13648
14086
|
break;
|
|
@@ -13678,9 +14116,9 @@ function buildMacProfile(i) {
|
|
|
13678
14116
|
L.push(`(deny file-read* file-write* (literal ${sbString(s)}))`);
|
|
13679
14117
|
for (const f of i.links)
|
|
13680
14118
|
L.push(`(deny file-write* (literal ${sbString(f)}))`);
|
|
13681
|
-
const guarded = new Set([i.project,
|
|
13682
|
-
for (const f of [...i.sockets, ...i.links, ...i.nodeModules ?? [],
|
|
13683
|
-
for (let d =
|
|
14119
|
+
const guarded = new Set([i.project, join11(i.project, ".git"), ...i.nodeModules ?? []]);
|
|
14120
|
+
for (const f of [...i.sockets, ...i.links, ...i.nodeModules ?? [], join11(i.project, ".git")]) {
|
|
14121
|
+
for (let d = dirname11(f);d.length >= i.project.length && (d === i.project || d.startsWith(i.project + "/")); d = dirname11(d))
|
|
13684
14122
|
guarded.add(d);
|
|
13685
14123
|
}
|
|
13686
14124
|
for (const d of [...guarded].sort())
|
|
@@ -13749,10 +14187,10 @@ function buildBwrapArgs(i) {
|
|
|
13749
14187
|
a.push("--bind", i.scratch, i.scratch);
|
|
13750
14188
|
for (const d of i.nodeModules)
|
|
13751
14189
|
ro(d);
|
|
13752
|
-
if (exists(
|
|
13753
|
-
ro(
|
|
13754
|
-
if (exists(
|
|
13755
|
-
ro(
|
|
14190
|
+
if (exists(join11(i.project, ".git/hooks")))
|
|
14191
|
+
ro(join11(i.project, ".git/hooks"));
|
|
14192
|
+
if (exists(join11(i.project, ".git/config")))
|
|
14193
|
+
ro(join11(i.project, ".git/config"));
|
|
13756
14194
|
for (const f of i.links)
|
|
13757
14195
|
ro(f);
|
|
13758
14196
|
for (const s of i.sockets)
|
|
@@ -13782,7 +14220,7 @@ function removeTree(path) {
|
|
|
13782
14220
|
return;
|
|
13783
14221
|
chmodSync(p, 448);
|
|
13784
14222
|
for (const e of readdirSync2(p))
|
|
13785
|
-
walk(
|
|
14223
|
+
walk(join11(p, e));
|
|
13786
14224
|
} catch {}
|
|
13787
14225
|
};
|
|
13788
14226
|
walk(path);
|
|
@@ -13820,7 +14258,7 @@ function createShellSandbox(deps = {}) {
|
|
|
13820
14258
|
if (deps.python !== undefined)
|
|
13821
14259
|
return deps.python;
|
|
13822
14260
|
const dev = platform === "darwin" ? macHost().developerDir : null;
|
|
13823
|
-
return dev && existsSync5(
|
|
14261
|
+
return dev && existsSync5(join11(dev, "usr/bin/python3")) ? join11(dev, "usr/bin/python3") : null;
|
|
13824
14262
|
};
|
|
13825
14263
|
let cached;
|
|
13826
14264
|
function probe() {
|
|
@@ -13938,13 +14376,13 @@ function createShellSandbox(deps = {}) {
|
|
|
13938
14376
|
return { ready: false, reason: "a folder in the project cannot be read, so it cannot be checked for sockets and hard links" };
|
|
13939
14377
|
let scratch;
|
|
13940
14378
|
try {
|
|
13941
|
-
scratch = mkdtempSync(
|
|
14379
|
+
scratch = mkdtempSync(join11(realpathSync4(tmpdir()), "freepi-hive-"));
|
|
13942
14380
|
const dir = scratch;
|
|
13943
|
-
|
|
13944
|
-
|
|
13945
|
-
const emptyFile =
|
|
13946
|
-
const marker =
|
|
13947
|
-
|
|
14381
|
+
mkdirSync4(join11(dir, "home"));
|
|
14382
|
+
mkdirSync4(join11(dir, "npm-cache"));
|
|
14383
|
+
const emptyFile = join11(dir, "empty");
|
|
14384
|
+
const marker = join11(dir, "call.fd");
|
|
14385
|
+
writeFileSync4(emptyFile, "", { mode: 292 });
|
|
13948
14386
|
const roots = toolchainRoots(home);
|
|
13949
14387
|
const mac = platform === "darwin";
|
|
13950
14388
|
const host = mac ? macHost() : undefined;
|
|
@@ -13996,7 +14434,7 @@ function createShellSandbox(deps = {}) {
|
|
|
13996
14434
|
} catch (err) {
|
|
13997
14435
|
if (scratch)
|
|
13998
14436
|
removeTree(scratch);
|
|
13999
|
-
return { ready: false, reason: `
|
|
14437
|
+
return { ready: false, reason: `Couldn't set up the command sandbox: ${sandboxCause(err)}.` };
|
|
14000
14438
|
}
|
|
14001
14439
|
},
|
|
14002
14440
|
mark(toolCallId, mode) {
|
|
@@ -14093,6 +14531,44 @@ function installSafeUrlOpen(mode, open = openLink) {
|
|
|
14093
14531
|
});
|
|
14094
14532
|
}
|
|
14095
14533
|
|
|
14534
|
+
// src/retry-failed-line.ts
|
|
14535
|
+
var RETRY_FAILED = /^Retry failed after (\d+) attempts?: ([\s\S]*)$/;
|
|
14536
|
+
var CRASH_WARNING = /^\S+ crashed on /;
|
|
14537
|
+
var PRODUCT = "free-pi-cli";
|
|
14538
|
+
function installRetryFailedLine(mode) {
|
|
14539
|
+
const m = mode;
|
|
14540
|
+
const original = m.showError.bind(m);
|
|
14541
|
+
m.showError = (msg) => {
|
|
14542
|
+
const hit = typeof msg === "string" ? RETRY_FAILED.exec(msg) : null;
|
|
14543
|
+
if (!hit)
|
|
14544
|
+
return original(msg);
|
|
14545
|
+
const [, n, cause = ""] = hit;
|
|
14546
|
+
if (cause === "Retry cancelled")
|
|
14547
|
+
return m.showWarning("Retry cancelled.");
|
|
14548
|
+
const tries = `${n} ${n === "1" ? "try" : "tries"}`;
|
|
14549
|
+
m.showWarning(`Gave up after ${tries}: ${cause}`);
|
|
14550
|
+
};
|
|
14551
|
+
const warn = m.showWarning.bind(m);
|
|
14552
|
+
m.showWarning = (msg) => warn(typeof msg === "string" ? msg.replace(CRASH_WARNING, `${PRODUCT} crashed on `) : msg);
|
|
14553
|
+
for (const name of ["renderProjectTrustWarningIfNeeded", "suggestBugReport"]) {
|
|
14554
|
+
const orig = m[name]?.bind(m);
|
|
14555
|
+
if (!orig)
|
|
14556
|
+
continue;
|
|
14557
|
+
m[name] = () => {
|
|
14558
|
+
const before = m.chatContainer?.children?.length ?? 0;
|
|
14559
|
+
orig();
|
|
14560
|
+
for (const child of m.chatContainer?.children?.slice(before) ?? []) {
|
|
14561
|
+
const build = child.build;
|
|
14562
|
+
if (typeof build === "function")
|
|
14563
|
+
child.build = () => reword(build.call(child));
|
|
14564
|
+
}
|
|
14565
|
+
};
|
|
14566
|
+
}
|
|
14567
|
+
}
|
|
14568
|
+
function reword(text) {
|
|
14569
|
+
return text.replace(/restart pi\./, `restart ${PRODUCT}.`).replace(/looks like a pi bug/, "looks like a free-pi bug");
|
|
14570
|
+
}
|
|
14571
|
+
|
|
14096
14572
|
// src/project-trust.ts
|
|
14097
14573
|
import { createInterface } from "node:readline/promises";
|
|
14098
14574
|
import { resolve as resolve3 } from "node:path";
|
|
@@ -14239,14 +14715,20 @@ var SDK_TOOLS = [
|
|
|
14239
14715
|
"list_mcp_resource_templates",
|
|
14240
14716
|
"read_mcp_resource"
|
|
14241
14717
|
];
|
|
14242
|
-
var SKILLS_DIR = resolve4(
|
|
14718
|
+
var SKILLS_DIR = resolve4(dirname12(fileURLToPath4(import.meta.url)), "..", "skills");
|
|
14243
14719
|
function resolveModelName(opts) {
|
|
14244
14720
|
return opts.models?.[0]?.name || opts.model || MODEL_ID;
|
|
14245
14721
|
}
|
|
14246
14722
|
function buildSettingsManager(opts, models, projectTrusted) {
|
|
14247
|
-
|
|
14248
|
-
|
|
14249
|
-
|
|
14723
|
+
const saved = readDefaultModel(opts.agentDir);
|
|
14724
|
+
const known = new Map([
|
|
14725
|
+
[PROVIDER_NAME, new Set([...models.map((m) => m.id), FUSION_PICKER_ID, ...models.some((m) => m.id === ULTRAFAST_CATALOG_ID) ? [ULTRAFAST_PICKER_ID] : []])],
|
|
14726
|
+
[DEFENSE_PROVIDER_NAME, new Set(defenseModelsFor(opts).map((m) => m.id))]
|
|
14727
|
+
]);
|
|
14728
|
+
const start = saved && known.get(saved.provider)?.has(saved.model) ? saved : undefined;
|
|
14729
|
+
const settings = {
|
|
14730
|
+
defaultProvider: start?.provider ?? PROVIDER_NAME,
|
|
14731
|
+
defaultModel: start?.model ?? models[0].id,
|
|
14250
14732
|
defaultTools: [...SAFE_TOOLS, TODO_TOOL_NAME],
|
|
14251
14733
|
packages: [],
|
|
14252
14734
|
quietStartup: true,
|
|
@@ -14256,7 +14738,10 @@ function buildSettingsManager(opts, models, projectTrusted) {
|
|
|
14256
14738
|
theme: "light/dark",
|
|
14257
14739
|
enableInstallTelemetry: false,
|
|
14258
14740
|
...compactionSettingsFor(opts.costArm) ? { compaction: compactionSettingsFor(opts.costArm) } : {}
|
|
14259
|
-
}
|
|
14741
|
+
};
|
|
14742
|
+
const storage = new DefaultModelStorage(opts.agentDir);
|
|
14743
|
+
storage.withLock("global", () => JSON.stringify(settings, null, 2));
|
|
14744
|
+
return SettingsManager2.fromStorage(storage, { projectTrusted });
|
|
14260
14745
|
}
|
|
14261
14746
|
function settingsForRuntime(opts, rtCwd, launch) {
|
|
14262
14747
|
return buildSettingsManager(opts, catalogModelsFor(opts), projectTrustForCwd(rtCwd, launch, opts.agentDir));
|
|
@@ -14397,6 +14882,7 @@ function buildRuntimeOptions(opts, sessionId) {
|
|
|
14397
14882
|
hasCredit: () => (getAllowanceCache()?.boughtCredits ?? 0) > 0,
|
|
14398
14883
|
extensionsFor: (subSessionId) => [
|
|
14399
14884
|
{ name: "free-pi-subagent-provider", factory: (pi) => registerProviders(pi, subSessionId, { "x-freepi-subagent": "1" }) },
|
|
14885
|
+
createTurnHeaderExtension(),
|
|
14400
14886
|
createToolGuardExtension({
|
|
14401
14887
|
baseUrl: opts.baseUrl,
|
|
14402
14888
|
getToken: () => auth.getJwt(),
|
|
@@ -14451,7 +14937,7 @@ ${CHOICE_RULES}` : agentRulesFor(opts.costArm), ...shellPromptLine(shell) ? [she
|
|
|
14451
14937
|
function getSessionDir(cwd, agentDir) {
|
|
14452
14938
|
const resolvedCwd = resolve4(cwd);
|
|
14453
14939
|
const safePath = `--${resolvedCwd.replace(/^[/\\]/, "").replace(/[/\\:]/g, "-")}--`;
|
|
14454
|
-
return
|
|
14940
|
+
return join12(resolve4(agentDir), "sessions", safePath);
|
|
14455
14941
|
}
|
|
14456
14942
|
async function findLatestSession(cwd, sessionDir, api = SessionManager2) {
|
|
14457
14943
|
const infos = await api.list(cwd, sessionDir);
|
|
@@ -14553,11 +15039,13 @@ async function launchPi(opts) {
|
|
|
14553
15039
|
installLogoutHandler(mode, { credentialsPath: opts.credentialsPath, isGuest: () => auth.isGuest() });
|
|
14554
15040
|
installModelAllowanceHandler(mode);
|
|
14555
15041
|
installSafeUrlOpen(mode);
|
|
15042
|
+
installRetryFailedLine(mode);
|
|
14556
15043
|
installFooterRightAlign(mode, () => {
|
|
14557
15044
|
const id = runtime.session?.model?.id;
|
|
14558
15045
|
return freeModelBase(pickerModelName(opts, id));
|
|
14559
15046
|
});
|
|
14560
15047
|
installEditorPlaceholder(mode);
|
|
15048
|
+
installWidthGuard(mode);
|
|
14561
15049
|
if (process.stdin.isTTY && process.stdout.isTTY && !process.env.CI) {
|
|
14562
15050
|
process.stdout.write("\x1B[2J\x1B[H");
|
|
14563
15051
|
installBottomPin(mode);
|
|
@@ -14610,7 +15098,7 @@ function createRealSpawn(io = { platform: process.platform, spawnSyncImpl: spawn
|
|
|
14610
15098
|
// src/relaunch.ts
|
|
14611
15099
|
import { existsSync as existsSync6, mkdtempSync as mkdtempSync2, realpathSync as realpathSync5, rmSync as rmSync2, statSync as statSync4 } from "node:fs";
|
|
14612
15100
|
import { constants as osConstants, tmpdir as tmpdir2 } from "node:os";
|
|
14613
|
-
import { delimiter, dirname as
|
|
15101
|
+
import { delimiter, dirname as dirname13, isAbsolute as isAbsolute3, join as join13, resolve as resolve5, sep as sep4, win32 } from "node:path";
|
|
14614
15102
|
function applyRelaunchCwd(env, chdir = (d) => process.chdir(d), isDir = (p) => {
|
|
14615
15103
|
try {
|
|
14616
15104
|
return statSync4(p).isDirectory();
|
|
@@ -14651,7 +15139,7 @@ function hasNodeModulesBin(entry) {
|
|
|
14651
15139
|
}
|
|
14652
15140
|
function sanitizedEnv(env, nodeDir, project, home, win) {
|
|
14653
15141
|
const proj = canon(project, win);
|
|
14654
|
-
const broad = proj === canon(home, win) ||
|
|
15142
|
+
const broad = proj === canon(home, win) || dirname13(proj) === proj;
|
|
14655
15143
|
const out = {};
|
|
14656
15144
|
let pathKey = "PATH";
|
|
14657
15145
|
let oldPath = "";
|
|
@@ -14688,13 +15176,13 @@ async function relaunchViaNpx(latest, deps) {
|
|
|
14688
15176
|
if (!latest || !SEMVER.test(latest))
|
|
14689
15177
|
return;
|
|
14690
15178
|
const target = latest;
|
|
14691
|
-
const nodeDir =
|
|
14692
|
-
const npxCli = win ?
|
|
15179
|
+
const nodeDir = dirname13(deps.execPath);
|
|
15180
|
+
const npxCli = win ? join13(nodeDir, "node_modules", "npm", "bin", "npx-cli.js") : join13(dirname13(nodeDir), "lib", "node_modules", "npm", "bin", "npx-cli.js");
|
|
14693
15181
|
const exists = deps.exists ?? existsSync6;
|
|
14694
15182
|
const distroNpxCli = "/usr/share/nodejs/npm/bin/npx-cli.js";
|
|
14695
15183
|
const healthy = (cli) => {
|
|
14696
|
-
const root =
|
|
14697
|
-
return exists(
|
|
15184
|
+
const root = join13(dirname13(cli), "..");
|
|
15185
|
+
return exists(join13(root, "package.json")) && exists(join13(root, "lib", "cli.js"));
|
|
14698
15186
|
};
|
|
14699
15187
|
let npxCliPath = npxCli;
|
|
14700
15188
|
if (!exists(npxCli)) {
|
|
@@ -14706,8 +15194,8 @@ async function relaunchViaNpx(latest, deps) {
|
|
|
14706
15194
|
if (!healthy(npxCliPath))
|
|
14707
15195
|
return;
|
|
14708
15196
|
const childEnv = sanitizedEnv(env, nodeDir, deps.cwd, deps.homedir, win);
|
|
14709
|
-
const readyDir = mkdtempSync2(
|
|
14710
|
-
const readyFile =
|
|
15197
|
+
const readyDir = mkdtempSync2(join13(tmpdir2(), "free-pi-relaunch-"));
|
|
15198
|
+
const readyFile = join13(readyDir, "started");
|
|
14711
15199
|
try {
|
|
14712
15200
|
deps.log(`Updating free-pi to ${target}…`);
|
|
14713
15201
|
return await new Promise((resolve) => {
|
|
@@ -14749,13 +15237,13 @@ async function relaunchViaNpx(latest, deps) {
|
|
|
14749
15237
|
|
|
14750
15238
|
// src/run.ts
|
|
14751
15239
|
import { createInterface as createInterface3 } from "node:readline/promises";
|
|
14752
|
-
import { mkdirSync as
|
|
15240
|
+
import { mkdirSync as mkdirSync6, writeFileSync as writeFileSync6 } from "node:fs";
|
|
14753
15241
|
import { SessionManager as SessionManager3 } from "@earendil-works/pi-coding-agent";
|
|
14754
15242
|
|
|
14755
15243
|
// src/onboarding.ts
|
|
14756
15244
|
import { existsSync as existsSync7, readdirSync as readdirSync3, realpathSync as realpathSync6 } from "node:fs";
|
|
14757
15245
|
import { homedir as homedir8 } from "node:os";
|
|
14758
|
-
import { join as
|
|
15246
|
+
import { join as join14 } from "node:path";
|
|
14759
15247
|
var INTRO_ENV_SKIP = "FREEPI_NO_INTRO";
|
|
14760
15248
|
var INTRO_TEXT = "Tell free-pi what to build, fix, or explain. Resume later with: npx free-pi-cli@latest --continue";
|
|
14761
15249
|
function introSkipped(env = process.env) {
|
|
@@ -14775,7 +15263,7 @@ function looksLikeProject(cwd, home = homedir8()) {
|
|
|
14775
15263
|
try {
|
|
14776
15264
|
if (realpathSync6(cwd) === realpathSync6(home))
|
|
14777
15265
|
return false;
|
|
14778
|
-
if (PROJECT_MARKERS.some((m) => existsSync7(
|
|
15266
|
+
if (PROJECT_MARKERS.some((m) => existsSync7(join14(cwd, m))))
|
|
14779
15267
|
return true;
|
|
14780
15268
|
return readdirSync3(cwd).some((f) => /\.(sln|csproj)$/.test(f));
|
|
14781
15269
|
} catch {
|
|
@@ -14804,7 +15292,7 @@ ${text}
|
|
|
14804
15292
|
// src/starter.ts
|
|
14805
15293
|
import { existsSync as existsSync8, readdirSync as readdirSync4, realpathSync as realpathSync7 } from "node:fs";
|
|
14806
15294
|
import { homedir as homedir9 } from "node:os";
|
|
14807
|
-
import { join as
|
|
15295
|
+
import { join as join15 } from "node:path";
|
|
14808
15296
|
var STARTER_EXPERIMENT = "first_session_starter";
|
|
14809
15297
|
var STARTER_PROJECT = [
|
|
14810
15298
|
{
|
|
@@ -14837,7 +15325,7 @@ var STARTER_EMPTY = [
|
|
|
14837
15325
|
var STARTER_HEADER_PROJECT = "Not sure where to start? Press 1, 2 or 3 to send one of these, or any other key to skip:";
|
|
14838
15326
|
var STARTER_HEADER_EMPTY = "This folder is empty. Press 1, 2 or 3 to start with one of these, or any other key to skip:";
|
|
14839
15327
|
function getStarterMarkerPath(agentDir = getFreePiAgentDir()) {
|
|
14840
|
-
return
|
|
15328
|
+
return join15(agentDir, "starter-shown");
|
|
14841
15329
|
}
|
|
14842
15330
|
function starterShownBefore(agentDir) {
|
|
14843
15331
|
return existsSync8(getStarterMarkerPath(agentDir));
|
|
@@ -14869,12 +15357,12 @@ function parseStarterKey(raw, count) {
|
|
|
14869
15357
|
}
|
|
14870
15358
|
|
|
14871
15359
|
// src/whats-new.ts
|
|
14872
|
-
import { existsSync as existsSync9, mkdirSync as
|
|
14873
|
-
import { dirname as
|
|
15360
|
+
import { existsSync as existsSync9, mkdirSync as mkdirSync5, readFileSync as readFileSync7, writeFileSync as writeFileSync5 } from "node:fs";
|
|
15361
|
+
import { dirname as dirname14 } from "node:path";
|
|
14874
15362
|
function maybeWhatsNew(cliVersion, agentDir) {
|
|
14875
15363
|
try {
|
|
14876
15364
|
const path = getLastSeenVersionPath(agentDir);
|
|
14877
|
-
const lastSeen = existsSync9(path) ?
|
|
15365
|
+
const lastSeen = existsSync9(path) ? readFileSync7(path, "utf-8").trim() : undefined;
|
|
14878
15366
|
writeLastSeen(path, cliVersion);
|
|
14879
15367
|
if (lastSeen === undefined)
|
|
14880
15368
|
return;
|
|
@@ -14890,8 +15378,8 @@ function maybeWhatsNew(cliVersion, agentDir) {
|
|
|
14890
15378
|
}
|
|
14891
15379
|
}
|
|
14892
15380
|
function writeLastSeen(path, version) {
|
|
14893
|
-
|
|
14894
|
-
|
|
15381
|
+
mkdirSync5(dirname14(path), { recursive: true });
|
|
15382
|
+
writeFileSync5(path, version, "utf-8");
|
|
14895
15383
|
}
|
|
14896
15384
|
|
|
14897
15385
|
// src/run.ts
|
|
@@ -15142,7 +15630,7 @@ Your free-pi sign-in has expired. Signing in with GitHub again.
|
|
|
15142
15630
|
deps.log(SIGNIN_UNFINISHED_LINE);
|
|
15143
15631
|
return 0;
|
|
15144
15632
|
}
|
|
15145
|
-
const reason = err instanceof Error && err.message ? err.message : "unknown error";
|
|
15633
|
+
const reason = (err instanceof Error && err.message ? err.message : "unknown error").replace(/\.$/, "");
|
|
15146
15634
|
deps.log(`Couldn't finish GitHub sign-in: ${reason}. Run npx free-pi-cli again to retry.`);
|
|
15147
15635
|
return 1;
|
|
15148
15636
|
}
|
|
@@ -15338,8 +15826,8 @@ ${r.text}
|
|
|
15338
15826
|
const choices = starterChoices();
|
|
15339
15827
|
if (choices) {
|
|
15340
15828
|
try {
|
|
15341
|
-
|
|
15342
|
-
|
|
15829
|
+
mkdirSync6(deps.agentDir, { recursive: true });
|
|
15830
|
+
writeFileSync6(getStarterMarkerPath(deps.agentDir), "1");
|
|
15343
15831
|
} catch {}
|
|
15344
15832
|
const report = (event) => deps.reportStarterEvent ? deps.reportStarterEvent(event) : reportStarterEvent(deps, jwt, event);
|
|
15345
15833
|
deps.log(formatStarterMenu(choices));
|
|
@@ -15492,7 +15980,7 @@ async function main() {
|
|
|
15492
15980
|
}
|
|
15493
15981
|
if (process.env.FREEPI_RELAUNCH_READY) {
|
|
15494
15982
|
try {
|
|
15495
|
-
|
|
15983
|
+
writeFileSync7(process.env.FREEPI_RELAUNCH_READY, "");
|
|
15496
15984
|
} catch {}
|
|
15497
15985
|
}
|
|
15498
15986
|
const command = parseCliArgs(process.argv.slice(2));
|
|
@@ -15553,7 +16041,7 @@ ${HELP_TEXT}`);
|
|
|
15553
16041
|
launchPi,
|
|
15554
16042
|
log: (message) => console.log(message),
|
|
15555
16043
|
checkClientVersion: (baseUrl, cliVersion, jwt) => checkClientVersion(baseUrl, cliVersion, fetch, jwt),
|
|
15556
|
-
startGravityPixel: (jwt) => startGravityPixel(jwt,
|
|
16044
|
+
startGravityPixel: (jwt) => startGravityPixel(jwt, join16(dirname15(agentDir), "gravity")),
|
|
15557
16045
|
relaunchLatest: (latest) => relaunchViaNpx(latest, {
|
|
15558
16046
|
env: process.env,
|
|
15559
16047
|
argv: process.argv.slice(2),
|