free-pi-cli 0.4.1 → 0.4.3
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-r8jqgh5n.js → main-xn8n5cb2.js} +255 -16
- package/package.json +2 -1
- package/skills/long-jobs/SKILL.md +14 -0
package/dist/index.js
CHANGED
|
@@ -44,7 +44,7 @@ function openBrowser(url, platform = process.platform, spawnImpl = spawn) {
|
|
|
44
44
|
// package.json
|
|
45
45
|
var package_default = {
|
|
46
46
|
name: "free-pi-cli",
|
|
47
|
-
version: "0.4.
|
|
47
|
+
version: "0.4.3",
|
|
48
48
|
description: "Free, ad-supported distribution of the pi coding agent. Inference paid for by ads.",
|
|
49
49
|
license: "MIT",
|
|
50
50
|
type: "module",
|
|
@@ -53,6 +53,7 @@ var package_default = {
|
|
|
53
53
|
},
|
|
54
54
|
files: [
|
|
55
55
|
"dist",
|
|
56
|
+
"skills",
|
|
56
57
|
"README.md"
|
|
57
58
|
],
|
|
58
59
|
engines: {
|
|
@@ -4828,6 +4829,7 @@ var MeResponseSchema = object({
|
|
|
4828
4829
|
cap_usd_today: number2(),
|
|
4829
4830
|
tier: AccountTierSchema,
|
|
4830
4831
|
credit_usd: number2(),
|
|
4832
|
+
consent_version: string2().nullable().optional(),
|
|
4831
4833
|
notice: string2().optional(),
|
|
4832
4834
|
buy_url: string2().optional(),
|
|
4833
4835
|
allowance_credits: number2().optional(),
|
|
@@ -5086,7 +5088,7 @@ function formatCreditsLeftLine(remaining, allowance, refillAt) {
|
|
|
5086
5088
|
}
|
|
5087
5089
|
var SLOW_LANE_METER_LINE = "Slow lane: cheapest model until refill";
|
|
5088
5090
|
// ../pi-ads/src/api.ts
|
|
5089
|
-
var REQUEST_TIMEOUT_MS =
|
|
5091
|
+
var REQUEST_TIMEOUT_MS = 5000;
|
|
5090
5092
|
async function authedFetch(deps, path, init) {
|
|
5091
5093
|
const token = await deps.getToken();
|
|
5092
5094
|
const doFetch = deps.fetchImpl ?? fetch;
|
|
@@ -5202,6 +5204,8 @@ function createBannerRenderer(opts = {}) {
|
|
|
5202
5204
|
return {
|
|
5203
5205
|
async render(deps, ctx, config, columnsOverride) {
|
|
5204
5206
|
const ad = await fetchAdNext(deps, "banner");
|
|
5207
|
+
if (opts.isActive && !opts.isActive())
|
|
5208
|
+
return;
|
|
5205
5209
|
if (!ad) {
|
|
5206
5210
|
ctx.ui.setWidget(BANNER_WIDGET_ID, undefined);
|
|
5207
5211
|
opts.onBannerAd?.(undefined);
|
|
@@ -5361,6 +5365,7 @@ async function renderMeter(deps, ctx) {
|
|
|
5361
5365
|
|
|
5362
5366
|
// ../pi-ads/src/index.ts
|
|
5363
5367
|
var BANNER_REFRESH_MS = 10 * 60 * 1000;
|
|
5368
|
+
var BANNER_MIN_REFRESH_MS = 60 * 1000;
|
|
5364
5369
|
function createAdsExtension(opts) {
|
|
5365
5370
|
const deps = {
|
|
5366
5371
|
baseUrl: opts.baseUrl,
|
|
@@ -5372,23 +5377,42 @@ function createAdsExtension(opts) {
|
|
|
5372
5377
|
return {
|
|
5373
5378
|
name: "free-pi-ads",
|
|
5374
5379
|
factory(pi) {
|
|
5375
|
-
|
|
5380
|
+
let stopped = false;
|
|
5381
|
+
const banner = createBannerRenderer({ onBannerAd: opts.onBannerAd, isActive: () => !stopped });
|
|
5376
5382
|
const inline = createInlineRenderer();
|
|
5377
5383
|
let bannerInterval;
|
|
5384
|
+
let lastBannerAt = 0;
|
|
5385
|
+
let bannerInFlight = false;
|
|
5386
|
+
const renderBanner = async (ctx, force = false) => {
|
|
5387
|
+
if (stopped)
|
|
5388
|
+
return;
|
|
5389
|
+
if (!force && (bannerInFlight || Date.now() - lastBannerAt < BANNER_MIN_REFRESH_MS))
|
|
5390
|
+
return;
|
|
5391
|
+
bannerInFlight = true;
|
|
5392
|
+
try {
|
|
5393
|
+
await banner.render(deps, ctx, config);
|
|
5394
|
+
} finally {
|
|
5395
|
+
bannerInFlight = false;
|
|
5396
|
+
lastBannerAt = Date.now();
|
|
5397
|
+
}
|
|
5398
|
+
};
|
|
5378
5399
|
pi.on("session_start", async (_event, ctx) => {
|
|
5400
|
+
stopped = false;
|
|
5379
5401
|
inline.reset(ctx);
|
|
5380
|
-
await Promise.all([
|
|
5402
|
+
await Promise.all([renderBanner(ctx, true), renderMeter(deps, ctx)]);
|
|
5381
5403
|
if (bannerInterval)
|
|
5382
5404
|
clearInterval(bannerInterval);
|
|
5383
5405
|
bannerInterval = setInterval(() => {
|
|
5384
|
-
|
|
5406
|
+
renderBanner(ctx);
|
|
5385
5407
|
}, BANNER_REFRESH_MS);
|
|
5386
5408
|
bannerInterval.unref?.();
|
|
5387
5409
|
});
|
|
5388
5410
|
pi.on("turn_end", async (_event, ctx) => {
|
|
5411
|
+
renderBanner(ctx);
|
|
5389
5412
|
await Promise.all([inline.onTurnEnd(deps, ctx, config), renderMeter(deps, ctx)]);
|
|
5390
5413
|
});
|
|
5391
5414
|
pi.on("session_shutdown", () => {
|
|
5415
|
+
stopped = true;
|
|
5392
5416
|
if (bannerInterval) {
|
|
5393
5417
|
clearInterval(bannerInterval);
|
|
5394
5418
|
bannerInterval = undefined;
|
|
@@ -5399,7 +5423,8 @@ function createAdsExtension(opts) {
|
|
|
5399
5423
|
}
|
|
5400
5424
|
|
|
5401
5425
|
// src/pi-launch.ts
|
|
5402
|
-
import { join as
|
|
5426
|
+
import { dirname as dirname3, join as join4, resolve as resolve2 } from "node:path";
|
|
5427
|
+
import { fileURLToPath as fileURLToPath3 } from "node:url";
|
|
5403
5428
|
|
|
5404
5429
|
// src/provider.ts
|
|
5405
5430
|
var PROVIDER_NAME = "free-pi";
|
|
@@ -6211,6 +6236,64 @@ function createTodoToolExtension(options = {}) {
|
|
|
6211
6236
|
};
|
|
6212
6237
|
}
|
|
6213
6238
|
|
|
6239
|
+
// src/tool-aliases.ts
|
|
6240
|
+
import { homedir as homedir2 } from "node:os";
|
|
6241
|
+
import { join as join3 } from "node:path";
|
|
6242
|
+
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
6243
|
+
import {
|
|
6244
|
+
createBashToolDefinition,
|
|
6245
|
+
createReadToolDefinition
|
|
6246
|
+
} from "@earendil-works/pi-coding-agent";
|
|
6247
|
+
function aliasArgs(input, key, aliases) {
|
|
6248
|
+
if (!input || typeof input !== "object" || key in input)
|
|
6249
|
+
return input;
|
|
6250
|
+
const args = input;
|
|
6251
|
+
const alias = aliases.find((a) => (a in args));
|
|
6252
|
+
if (!alias)
|
|
6253
|
+
return input;
|
|
6254
|
+
const { [alias]: value, ...rest } = args;
|
|
6255
|
+
return { ...rest, [key]: value };
|
|
6256
|
+
}
|
|
6257
|
+
function normalizeShellPath(input, platform = process.platform, home = homedir2()) {
|
|
6258
|
+
let p = input;
|
|
6259
|
+
if (platform === "win32" && p.startsWith("/") && !p.startsWith("//") && !p.includes("\\")) {
|
|
6260
|
+
const m = p.match(/^\/(?:mnt\/|cygdrive\/)?([a-z])(?:\/(.*))?$/i);
|
|
6261
|
+
if (m)
|
|
6262
|
+
p = `${m[1].toUpperCase()}:\\${m[2]?.replaceAll("/", "\\") ?? ""}`;
|
|
6263
|
+
}
|
|
6264
|
+
if (p === "~")
|
|
6265
|
+
return home;
|
|
6266
|
+
if (p.startsWith("~/") || platform === "win32" && p.startsWith("~\\"))
|
|
6267
|
+
return join3(home, p.slice(2));
|
|
6268
|
+
if (/^file:\/\//.test(p))
|
|
6269
|
+
return fileURLToPath2(p);
|
|
6270
|
+
return p;
|
|
6271
|
+
}
|
|
6272
|
+
function createToolAliasesExtension() {
|
|
6273
|
+
return {
|
|
6274
|
+
name: "free-pi-tool-aliases",
|
|
6275
|
+
factory: (pi) => {
|
|
6276
|
+
const cwd = process.cwd();
|
|
6277
|
+
const bash = createBashToolDefinition(cwd);
|
|
6278
|
+
const read = createReadToolDefinition(cwd);
|
|
6279
|
+
pi.registerTool({
|
|
6280
|
+
...bash,
|
|
6281
|
+
prepareArguments: (a) => aliasArgs(bash.prepareArguments ? bash.prepareArguments(a) : a, "command", ["cmd"]),
|
|
6282
|
+
execute: (...args) => {
|
|
6283
|
+
const s = pi.getSettings();
|
|
6284
|
+
const shellPath = s.shellPath ? normalizeShellPath(s.shellPath) : s.shellPath;
|
|
6285
|
+
return createBashToolDefinition(cwd, { commandPrefix: s.shellCommandPrefix, shellPath }).execute(...args);
|
|
6286
|
+
}
|
|
6287
|
+
});
|
|
6288
|
+
pi.registerTool({
|
|
6289
|
+
...read,
|
|
6290
|
+
prepareArguments: (a) => aliasArgs(read.prepareArguments ? read.prepareArguments(a) : a, "path", ["file", "file_path"]),
|
|
6291
|
+
execute: (...args) => createReadToolDefinition(cwd, { autoResizeImages: pi.getSettings().images?.autoResize ?? true }).execute(...args)
|
|
6292
|
+
});
|
|
6293
|
+
}
|
|
6294
|
+
};
|
|
6295
|
+
}
|
|
6296
|
+
|
|
6214
6297
|
// src/error-notice.ts
|
|
6215
6298
|
var NOTICE_DEDUPE_MS = 15000;
|
|
6216
6299
|
function parseErrorEnvelope(errorMessage) {
|
|
@@ -6253,6 +6336,16 @@ function createErrorNoticeExtension(opts = {}) {
|
|
|
6253
6336
|
};
|
|
6254
6337
|
}
|
|
6255
6338
|
|
|
6339
|
+
// src/agent-rules.ts
|
|
6340
|
+
var AGENT_RULES = `## Working rules
|
|
6341
|
+
- When you say you will run, check, or change something, make that tool call in the same
|
|
6342
|
+
turn. Never end a turn with only a plan or a script printed as text.
|
|
6343
|
+
- If the same error appears twice, stop retrying variants. Read the relevant source, docs
|
|
6344
|
+
or full error output, or try a different approach. Do not re-read a file you already read
|
|
6345
|
+
unless it has changed.
|
|
6346
|
+
- Before saying a task is done, check the result the user will see: run the program, open
|
|
6347
|
+
the output, or run the specific test. Say plainly what you checked and what you did not.`;
|
|
6348
|
+
|
|
6256
6349
|
// src/tool-guard.ts
|
|
6257
6350
|
async function reportToolGuardTrigger(opts, toolName) {
|
|
6258
6351
|
try {
|
|
@@ -6351,8 +6444,37 @@ function registerBugAutocomplete(pi) {
|
|
|
6351
6444
|
});
|
|
6352
6445
|
}
|
|
6353
6446
|
|
|
6447
|
+
// src/footer-align.ts
|
|
6448
|
+
var ANSI = /\x1b\[[0-9;]*m/g;
|
|
6449
|
+
var SIMPLE = /^[\x20-\x7e\xa0-\xff]*$/;
|
|
6450
|
+
function installFooterRightAlign(mode) {
|
|
6451
|
+
const footer = mode.footer;
|
|
6452
|
+
const render = footer.render.bind(footer);
|
|
6453
|
+
footer.render = (w) => {
|
|
6454
|
+
const lines = render(w);
|
|
6455
|
+
if (lines.length <= 2)
|
|
6456
|
+
return lines;
|
|
6457
|
+
const last = lines[lines.length - 1];
|
|
6458
|
+
const text = last.replace(ANSI, "");
|
|
6459
|
+
if (!SIMPLE.test(text))
|
|
6460
|
+
return lines;
|
|
6461
|
+
return [...lines.slice(0, -1), " ".repeat(Math.max(0, w - text.length)) + last];
|
|
6462
|
+
};
|
|
6463
|
+
}
|
|
6464
|
+
|
|
6354
6465
|
// src/changelog.ts
|
|
6355
6466
|
var CHANGELOG_HIGHLIGHTS = [
|
|
6467
|
+
{
|
|
6468
|
+
version: "0.4.3",
|
|
6469
|
+
highlights: [
|
|
6470
|
+
"The ad banner now refreshes while you work, at most once a minute.",
|
|
6471
|
+
"If our terms have changed since you signed up, you'll be asked to review and accept them."
|
|
6472
|
+
]
|
|
6473
|
+
},
|
|
6474
|
+
{
|
|
6475
|
+
version: "0.4.2",
|
|
6476
|
+
highlights: ["Credits now sit on the right of the footer, under the model name."]
|
|
6477
|
+
},
|
|
6356
6478
|
{
|
|
6357
6479
|
version: "0.4.1",
|
|
6358
6480
|
highlights: [
|
|
@@ -6714,12 +6836,89 @@ function createHeaderExtension(opts) {
|
|
|
6714
6836
|
};
|
|
6715
6837
|
}
|
|
6716
6838
|
|
|
6839
|
+
// src/fusion.ts
|
|
6840
|
+
var FUSION_ID = "fusion";
|
|
6841
|
+
var FUSION_NAME = "Fusion · Argon plans, DeepSeek builds";
|
|
6842
|
+
var FUSION_PLANNER_ID = "gemini-4-argon-flex";
|
|
6843
|
+
var FUSION_EXECUTOR_ID = "deepseek-v4-flash";
|
|
6844
|
+
function editedThisTurn(messages) {
|
|
6845
|
+
for (let i = messages.length - 1;i >= 0; i--) {
|
|
6846
|
+
const message = messages[i];
|
|
6847
|
+
if (message.role === "user")
|
|
6848
|
+
break;
|
|
6849
|
+
if (message.role === "toolResult" && (message.toolName === "edit" || message.toolName === "write") && !message.isError) {
|
|
6850
|
+
return true;
|
|
6851
|
+
}
|
|
6852
|
+
}
|
|
6853
|
+
return false;
|
|
6854
|
+
}
|
|
6855
|
+
function hasNewUserInput(request) {
|
|
6856
|
+
const failedAt = request.failed?.message.timestamp;
|
|
6857
|
+
if (failedAt === undefined)
|
|
6858
|
+
return false;
|
|
6859
|
+
for (let i = request.messages.length - 1;i >= 0; i--) {
|
|
6860
|
+
const message = request.messages[i];
|
|
6861
|
+
if (message.role === "assistant")
|
|
6862
|
+
return false;
|
|
6863
|
+
if (message.role === "user")
|
|
6864
|
+
return message.timestamp > failedAt;
|
|
6865
|
+
}
|
|
6866
|
+
return false;
|
|
6867
|
+
}
|
|
6868
|
+
function routeFusion(request, models) {
|
|
6869
|
+
const phase = request.state?.phase ?? "plan";
|
|
6870
|
+
const restartPlan = request.reason === "user" || request.reason === "retry" && hasNewUserInput(request);
|
|
6871
|
+
const route = (model, nextPhase = phase) => ({
|
|
6872
|
+
model,
|
|
6873
|
+
thinkingLevel: request.thinkingLevel,
|
|
6874
|
+
state: request.reason === "direct" ? undefined : request.state?.phase === nextPhase ? request.state : { phase: nextPhase }
|
|
6875
|
+
});
|
|
6876
|
+
if (!models.planner || !models.executor) {
|
|
6877
|
+
return route(models.defaultModel, restartPlan ? "plan" : phase);
|
|
6878
|
+
}
|
|
6879
|
+
switch (request.reason) {
|
|
6880
|
+
case "user":
|
|
6881
|
+
return route(models.planner, "plan");
|
|
6882
|
+
case "direct":
|
|
6883
|
+
return route(models.executor);
|
|
6884
|
+
case "retry": {
|
|
6885
|
+
if (restartPlan)
|
|
6886
|
+
return route(models.planner, "plan");
|
|
6887
|
+
const failed = request.failed;
|
|
6888
|
+
if (!failed || failed.model.provider === PROVIDER_NAME && failed.model.id === FUSION_PLANNER_ID) {
|
|
6889
|
+
return route(models.executor, "build");
|
|
6890
|
+
}
|
|
6891
|
+
return { ...route(failed.model), thinkingLevel: failed.thinkingLevel ?? request.thinkingLevel };
|
|
6892
|
+
}
|
|
6893
|
+
case "continuation":
|
|
6894
|
+
return phase === "build" || editedThisTurn(request.messages) ? route(models.executor, "build") : route(models.planner, "plan");
|
|
6895
|
+
}
|
|
6896
|
+
}
|
|
6897
|
+
function createFusionExtension(defaultModelId) {
|
|
6898
|
+
return {
|
|
6899
|
+
name: "free-pi-fusion",
|
|
6900
|
+
factory: (pi) => {
|
|
6901
|
+
pi.registerVirtualModel({
|
|
6902
|
+
provider: PROVIDER_NAME,
|
|
6903
|
+
id: FUSION_ID,
|
|
6904
|
+
name: FUSION_NAME,
|
|
6905
|
+
input: ["text"],
|
|
6906
|
+
route: (request, ctx) => routeFusion(request, {
|
|
6907
|
+
planner: ctx.modelRegistry.find(PROVIDER_NAME, FUSION_PLANNER_ID),
|
|
6908
|
+
executor: ctx.modelRegistry.find(PROVIDER_NAME, FUSION_EXECUTOR_ID),
|
|
6909
|
+
defaultModel: ctx.modelRegistry.find(PROVIDER_NAME, defaultModelId)
|
|
6910
|
+
})
|
|
6911
|
+
});
|
|
6912
|
+
}
|
|
6913
|
+
};
|
|
6914
|
+
}
|
|
6915
|
+
|
|
6717
6916
|
// src/provider-lock.ts
|
|
6718
6917
|
function resolveFreePiScope(modelRuntime, providerName, modelIds, env = process.env) {
|
|
6719
6918
|
if (env.FREEPI_KEEP_ENV_PROVIDERS)
|
|
6720
6919
|
return [];
|
|
6721
6920
|
const scoped = [];
|
|
6722
|
-
for (const id of modelIds) {
|
|
6921
|
+
for (const id of new Set([...modelIds, FUSION_ID])) {
|
|
6723
6922
|
const model = modelRuntime.getModel(providerName, id);
|
|
6724
6923
|
if (model)
|
|
6725
6924
|
scoped.push({ model });
|
|
@@ -6812,6 +7011,7 @@ var SDK_TOOLS = [
|
|
|
6812
7011
|
"list_mcp_resource_templates",
|
|
6813
7012
|
"read_mcp_resource"
|
|
6814
7013
|
];
|
|
7014
|
+
var SKILLS_DIR = resolve2(dirname3(fileURLToPath3(import.meta.url)), "..", "skills");
|
|
6815
7015
|
function resolveModelName(opts) {
|
|
6816
7016
|
return opts.models?.[0]?.name || opts.model || MODEL_ID;
|
|
6817
7017
|
}
|
|
@@ -6873,11 +7073,13 @@ function buildRuntimeOptions(opts, sessionId) {
|
|
|
6873
7073
|
updateLatest: opts.updateLatest
|
|
6874
7074
|
});
|
|
6875
7075
|
const todoToolExtension = createTodoToolExtension();
|
|
7076
|
+
const toolAliasesExtension = createToolAliasesExtension();
|
|
6876
7077
|
const mcpExtension = { name: "pi-mcp", factory: createMcpExtension() };
|
|
6877
7078
|
const codemodeExtension = { name: "pi-codemode", factory: createCodemodeExtension({ models: false }) };
|
|
6878
7079
|
const toolSearchExtension = { name: "pi-tool-search", factory: createToolSearchExtension() };
|
|
6879
7080
|
const extensionFactories = [
|
|
6880
7081
|
providerExtension,
|
|
7082
|
+
createFusionExtension(models[0].id),
|
|
6881
7083
|
adsExtension,
|
|
6882
7084
|
usageToolExtension,
|
|
6883
7085
|
buyToolExtension,
|
|
@@ -6887,6 +7089,7 @@ function buildRuntimeOptions(opts, sessionId) {
|
|
|
6887
7089
|
commandsExtension,
|
|
6888
7090
|
headerExtension,
|
|
6889
7091
|
todoToolExtension,
|
|
7092
|
+
toolAliasesExtension,
|
|
6890
7093
|
mcpExtension,
|
|
6891
7094
|
codemodeExtension,
|
|
6892
7095
|
toolSearchExtension
|
|
@@ -6894,7 +7097,11 @@ function buildRuntimeOptions(opts, sessionId) {
|
|
|
6894
7097
|
const settingsManager = buildSettingsManager(opts, models, opts.projectTrusted === true);
|
|
6895
7098
|
return {
|
|
6896
7099
|
settingsManager,
|
|
6897
|
-
resourceLoaderOptions: {
|
|
7100
|
+
resourceLoaderOptions: {
|
|
7101
|
+
extensionFactories,
|
|
7102
|
+
additionalSkillPaths: [SKILLS_DIR],
|
|
7103
|
+
appendSystemPromptOverride: (base) => [...base, AGENT_RULES]
|
|
7104
|
+
},
|
|
6898
7105
|
extensionNames: extensionFactories.map((e) => e.name),
|
|
6899
7106
|
tools: SDK_TOOLS
|
|
6900
7107
|
};
|
|
@@ -6902,7 +7109,7 @@ function buildRuntimeOptions(opts, sessionId) {
|
|
|
6902
7109
|
function getSessionDir(cwd, agentDir) {
|
|
6903
7110
|
const resolvedCwd = resolve2(cwd);
|
|
6904
7111
|
const safePath = `--${resolvedCwd.replace(/^[/\\]/, "").replace(/[/\\:]/g, "-")}--`;
|
|
6905
|
-
return
|
|
7112
|
+
return join4(resolve2(agentDir), "sessions", safePath);
|
|
6906
7113
|
}
|
|
6907
7114
|
async function createSessionManager(cwd, sessionDir, sessionId, api = SessionManager) {
|
|
6908
7115
|
if (sessionId) {
|
|
@@ -6961,6 +7168,7 @@ async function launchPi(opts) {
|
|
|
6961
7168
|
initialMessages: []
|
|
6962
7169
|
});
|
|
6963
7170
|
installBugHandler(mode, { baseUrl: opts.baseUrl, getToken: () => opts.jwt });
|
|
7171
|
+
installFooterRightAlign(mode);
|
|
6964
7172
|
await mode.run();
|
|
6965
7173
|
return sm.getSessionId();
|
|
6966
7174
|
}
|
|
@@ -7000,18 +7208,19 @@ var CONSENT_PROMPT = "Accept? [y/N] ";
|
|
|
7000
7208
|
function parseConsentAnswer(answer) {
|
|
7001
7209
|
return /^\s*y(es)?\s*$/i.test(answer);
|
|
7002
7210
|
}
|
|
7211
|
+
var REPROMPT_CONSENT_TEXT = CONSENT_TEXT.replace("Decline and nothing is sent to the free-pi server.", "Decline and free-pi exits; you will be asked again next time.");
|
|
7003
7212
|
|
|
7004
7213
|
// src/prompt.ts
|
|
7005
|
-
async function promptConsent(deps) {
|
|
7214
|
+
async function promptConsent(deps, text = CONSENT_TEXT) {
|
|
7006
7215
|
if (!deps.stdinIsTTY) {
|
|
7007
7216
|
deps.log(NON_INTERACTIVE_CONSENT_TEXT);
|
|
7008
7217
|
return false;
|
|
7009
7218
|
}
|
|
7010
|
-
deps.log(
|
|
7219
|
+
deps.log(text);
|
|
7011
7220
|
const answer = await deps.askLine(CONSENT_PROMPT);
|
|
7012
7221
|
return parseConsentAnswer(answer);
|
|
7013
7222
|
}
|
|
7014
|
-
async function promptConsentInteractive() {
|
|
7223
|
+
async function promptConsentInteractive(text) {
|
|
7015
7224
|
return promptConsent({
|
|
7016
7225
|
stdinIsTTY: process.stdin.isTTY === true,
|
|
7017
7226
|
log: (message) => console.log(message),
|
|
@@ -7023,7 +7232,7 @@ async function promptConsentInteractive() {
|
|
|
7023
7232
|
rl.close();
|
|
7024
7233
|
}
|
|
7025
7234
|
}
|
|
7026
|
-
});
|
|
7235
|
+
}, text);
|
|
7027
7236
|
}
|
|
7028
7237
|
|
|
7029
7238
|
// src/real-spawn.ts
|
|
@@ -7119,6 +7328,24 @@ async function checkToken(baseUrl, jwt) {
|
|
|
7119
7328
|
});
|
|
7120
7329
|
if (res.status === 401)
|
|
7121
7330
|
throw new UnauthorizedError;
|
|
7331
|
+
if (!res.ok)
|
|
7332
|
+
return;
|
|
7333
|
+
const body = await res.json().catch(() => {
|
|
7334
|
+
return;
|
|
7335
|
+
});
|
|
7336
|
+
const v = body?.consent_version;
|
|
7337
|
+
return typeof v === "string" || v === null ? v : undefined;
|
|
7338
|
+
}
|
|
7339
|
+
async function submitUserConsent(baseUrl, jwt, consentVersion) {
|
|
7340
|
+
const res = await fetch(new URL("/me/consent", baseUrl), {
|
|
7341
|
+
method: "POST",
|
|
7342
|
+
headers: { authorization: `Bearer ${jwt}`, "content-type": "application/json" },
|
|
7343
|
+
body: JSON.stringify({ consent_version: consentVersion })
|
|
7344
|
+
});
|
|
7345
|
+
if (res.status === 401)
|
|
7346
|
+
throw new UnauthorizedError;
|
|
7347
|
+
if (!res.ok)
|
|
7348
|
+
throw new Error(`consent submission failed: HTTP ${res.status}`);
|
|
7122
7349
|
}
|
|
7123
7350
|
|
|
7124
7351
|
// src/run.ts
|
|
@@ -7152,7 +7379,7 @@ ${INTRO_TEXT}
|
|
|
7152
7379
|
|
|
7153
7380
|
// src/whats-new.ts
|
|
7154
7381
|
import { existsSync as existsSync2, mkdirSync, readFileSync as readFileSync2, writeFileSync } from "node:fs";
|
|
7155
|
-
import { dirname as
|
|
7382
|
+
import { dirname as dirname4 } from "node:path";
|
|
7156
7383
|
function maybeWhatsNew(cliVersion, agentDir) {
|
|
7157
7384
|
try {
|
|
7158
7385
|
const path = getLastSeenVersionPath(agentDir);
|
|
@@ -7172,7 +7399,7 @@ function maybeWhatsNew(cliVersion, agentDir) {
|
|
|
7172
7399
|
}
|
|
7173
7400
|
}
|
|
7174
7401
|
function writeLastSeen(path, version) {
|
|
7175
|
-
mkdirSync(
|
|
7402
|
+
mkdirSync(dirname4(path), { recursive: true });
|
|
7176
7403
|
writeFileSync(path, version, "utf-8");
|
|
7177
7404
|
}
|
|
7178
7405
|
|
|
@@ -7212,7 +7439,19 @@ async function run(deps, argv = process.argv) {
|
|
|
7212
7439
|
let jwt = await loadJwt(deps.credentialsPath);
|
|
7213
7440
|
if (jwt) {
|
|
7214
7441
|
try {
|
|
7215
|
-
await checkToken(deps.baseUrl, jwt);
|
|
7442
|
+
const serverConsent = await checkToken(deps.baseUrl, jwt);
|
|
7443
|
+
if (serverConsent !== undefined && serverConsent !== CONSENT_VERSION) {
|
|
7444
|
+
if (!await deps.promptConsent(REPROMPT_CONSENT_TEXT)) {
|
|
7445
|
+
deps.log("Declined. free-pi needs you to accept the updated terms to continue; you will be asked again next time.");
|
|
7446
|
+
return 1;
|
|
7447
|
+
}
|
|
7448
|
+
try {
|
|
7449
|
+
await submitUserConsent(deps.baseUrl, jwt, CONSENT_VERSION);
|
|
7450
|
+
} catch (err) {
|
|
7451
|
+
if (err instanceof UnauthorizedError)
|
|
7452
|
+
throw err;
|
|
7453
|
+
}
|
|
7454
|
+
}
|
|
7216
7455
|
} catch (err) {
|
|
7217
7456
|
if (err instanceof UnauthorizedError) {
|
|
7218
7457
|
await clearJwt(deps.credentialsPath);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "free-pi-cli",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.3",
|
|
4
4
|
"description": "Free, ad-supported distribution of the pi coding agent. Inference paid for by ads.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
},
|
|
10
10
|
"files": [
|
|
11
11
|
"dist",
|
|
12
|
+
"skills",
|
|
12
13
|
"README.md"
|
|
13
14
|
],
|
|
14
15
|
"engines": {
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: long-jobs
|
|
3
|
+
description: Use when running builds, test suites, dev servers, downloads, decompilers, or any command expected to take longer than about a minute. Covers checking tools first, running jobs in the background with a log, waiting once, and not hiding errors.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Long-running jobs
|
|
7
|
+
|
|
8
|
+
- Before relying on a tool, check it exists once (`command -v gh cmake timeout`) and note what is missing. Do not assume `sudo`.
|
|
9
|
+
- Run the specific test or target first. Run a full suite once, at the end.
|
|
10
|
+
- Before rerunning a long job, check whether its earlier output or log already exists.
|
|
11
|
+
- Start long processes in the background with output to a log file and the PID saved: `nohup cmd > job.log 2>&1 & echo $! > job.pid`. Stop them with `kill $(cat job.pid)`. Never use `pkill -f` with a pattern that also matches your own command line.
|
|
12
|
+
- Wait once, with a time limit, for a marker in the log: `timeout 600 sh -c 'until grep -q "done" job.log; do sleep 5; done'`. To wait for the process instead: `timeout 600 sh -c 'while kill -0 $(cat job.pid) 2>/dev/null; do sleep 5; done'`. Do not poll with repeated short sleep-and-tail calls, and do not use `tail -f | grep` (it keeps waiting after the match).
|
|
13
|
+
- Check a port is free before starting a server, and stop the servers you started.
|
|
14
|
+
- Do not hide errors: no `2>/dev/null` and no `| tail` on a command whose failure you need to diagnose. Test a new parser or script on a small sample first.
|