free-pi-cli 0.4.2 → 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-37kszx0y.js → main-xn8n5cb2.js} +232 -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 {
|
|
@@ -6371,6 +6464,13 @@ function installFooterRightAlign(mode) {
|
|
|
6371
6464
|
|
|
6372
6465
|
// src/changelog.ts
|
|
6373
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
|
+
},
|
|
6374
6474
|
{
|
|
6375
6475
|
version: "0.4.2",
|
|
6376
6476
|
highlights: ["Credits now sit on the right of the footer, under the model name."]
|
|
@@ -6736,12 +6836,89 @@ function createHeaderExtension(opts) {
|
|
|
6736
6836
|
};
|
|
6737
6837
|
}
|
|
6738
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
|
+
|
|
6739
6916
|
// src/provider-lock.ts
|
|
6740
6917
|
function resolveFreePiScope(modelRuntime, providerName, modelIds, env = process.env) {
|
|
6741
6918
|
if (env.FREEPI_KEEP_ENV_PROVIDERS)
|
|
6742
6919
|
return [];
|
|
6743
6920
|
const scoped = [];
|
|
6744
|
-
for (const id of modelIds) {
|
|
6921
|
+
for (const id of new Set([...modelIds, FUSION_ID])) {
|
|
6745
6922
|
const model = modelRuntime.getModel(providerName, id);
|
|
6746
6923
|
if (model)
|
|
6747
6924
|
scoped.push({ model });
|
|
@@ -6834,6 +7011,7 @@ var SDK_TOOLS = [
|
|
|
6834
7011
|
"list_mcp_resource_templates",
|
|
6835
7012
|
"read_mcp_resource"
|
|
6836
7013
|
];
|
|
7014
|
+
var SKILLS_DIR = resolve2(dirname3(fileURLToPath3(import.meta.url)), "..", "skills");
|
|
6837
7015
|
function resolveModelName(opts) {
|
|
6838
7016
|
return opts.models?.[0]?.name || opts.model || MODEL_ID;
|
|
6839
7017
|
}
|
|
@@ -6895,11 +7073,13 @@ function buildRuntimeOptions(opts, sessionId) {
|
|
|
6895
7073
|
updateLatest: opts.updateLatest
|
|
6896
7074
|
});
|
|
6897
7075
|
const todoToolExtension = createTodoToolExtension();
|
|
7076
|
+
const toolAliasesExtension = createToolAliasesExtension();
|
|
6898
7077
|
const mcpExtension = { name: "pi-mcp", factory: createMcpExtension() };
|
|
6899
7078
|
const codemodeExtension = { name: "pi-codemode", factory: createCodemodeExtension({ models: false }) };
|
|
6900
7079
|
const toolSearchExtension = { name: "pi-tool-search", factory: createToolSearchExtension() };
|
|
6901
7080
|
const extensionFactories = [
|
|
6902
7081
|
providerExtension,
|
|
7082
|
+
createFusionExtension(models[0].id),
|
|
6903
7083
|
adsExtension,
|
|
6904
7084
|
usageToolExtension,
|
|
6905
7085
|
buyToolExtension,
|
|
@@ -6909,6 +7089,7 @@ function buildRuntimeOptions(opts, sessionId) {
|
|
|
6909
7089
|
commandsExtension,
|
|
6910
7090
|
headerExtension,
|
|
6911
7091
|
todoToolExtension,
|
|
7092
|
+
toolAliasesExtension,
|
|
6912
7093
|
mcpExtension,
|
|
6913
7094
|
codemodeExtension,
|
|
6914
7095
|
toolSearchExtension
|
|
@@ -6916,7 +7097,11 @@ function buildRuntimeOptions(opts, sessionId) {
|
|
|
6916
7097
|
const settingsManager = buildSettingsManager(opts, models, opts.projectTrusted === true);
|
|
6917
7098
|
return {
|
|
6918
7099
|
settingsManager,
|
|
6919
|
-
resourceLoaderOptions: {
|
|
7100
|
+
resourceLoaderOptions: {
|
|
7101
|
+
extensionFactories,
|
|
7102
|
+
additionalSkillPaths: [SKILLS_DIR],
|
|
7103
|
+
appendSystemPromptOverride: (base) => [...base, AGENT_RULES]
|
|
7104
|
+
},
|
|
6920
7105
|
extensionNames: extensionFactories.map((e) => e.name),
|
|
6921
7106
|
tools: SDK_TOOLS
|
|
6922
7107
|
};
|
|
@@ -6924,7 +7109,7 @@ function buildRuntimeOptions(opts, sessionId) {
|
|
|
6924
7109
|
function getSessionDir(cwd, agentDir) {
|
|
6925
7110
|
const resolvedCwd = resolve2(cwd);
|
|
6926
7111
|
const safePath = `--${resolvedCwd.replace(/^[/\\]/, "").replace(/[/\\:]/g, "-")}--`;
|
|
6927
|
-
return
|
|
7112
|
+
return join4(resolve2(agentDir), "sessions", safePath);
|
|
6928
7113
|
}
|
|
6929
7114
|
async function createSessionManager(cwd, sessionDir, sessionId, api = SessionManager) {
|
|
6930
7115
|
if (sessionId) {
|
|
@@ -7023,18 +7208,19 @@ var CONSENT_PROMPT = "Accept? [y/N] ";
|
|
|
7023
7208
|
function parseConsentAnswer(answer) {
|
|
7024
7209
|
return /^\s*y(es)?\s*$/i.test(answer);
|
|
7025
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.");
|
|
7026
7212
|
|
|
7027
7213
|
// src/prompt.ts
|
|
7028
|
-
async function promptConsent(deps) {
|
|
7214
|
+
async function promptConsent(deps, text = CONSENT_TEXT) {
|
|
7029
7215
|
if (!deps.stdinIsTTY) {
|
|
7030
7216
|
deps.log(NON_INTERACTIVE_CONSENT_TEXT);
|
|
7031
7217
|
return false;
|
|
7032
7218
|
}
|
|
7033
|
-
deps.log(
|
|
7219
|
+
deps.log(text);
|
|
7034
7220
|
const answer = await deps.askLine(CONSENT_PROMPT);
|
|
7035
7221
|
return parseConsentAnswer(answer);
|
|
7036
7222
|
}
|
|
7037
|
-
async function promptConsentInteractive() {
|
|
7223
|
+
async function promptConsentInteractive(text) {
|
|
7038
7224
|
return promptConsent({
|
|
7039
7225
|
stdinIsTTY: process.stdin.isTTY === true,
|
|
7040
7226
|
log: (message) => console.log(message),
|
|
@@ -7046,7 +7232,7 @@ async function promptConsentInteractive() {
|
|
|
7046
7232
|
rl.close();
|
|
7047
7233
|
}
|
|
7048
7234
|
}
|
|
7049
|
-
});
|
|
7235
|
+
}, text);
|
|
7050
7236
|
}
|
|
7051
7237
|
|
|
7052
7238
|
// src/real-spawn.ts
|
|
@@ -7142,6 +7328,24 @@ async function checkToken(baseUrl, jwt) {
|
|
|
7142
7328
|
});
|
|
7143
7329
|
if (res.status === 401)
|
|
7144
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}`);
|
|
7145
7349
|
}
|
|
7146
7350
|
|
|
7147
7351
|
// src/run.ts
|
|
@@ -7175,7 +7379,7 @@ ${INTRO_TEXT}
|
|
|
7175
7379
|
|
|
7176
7380
|
// src/whats-new.ts
|
|
7177
7381
|
import { existsSync as existsSync2, mkdirSync, readFileSync as readFileSync2, writeFileSync } from "node:fs";
|
|
7178
|
-
import { dirname as
|
|
7382
|
+
import { dirname as dirname4 } from "node:path";
|
|
7179
7383
|
function maybeWhatsNew(cliVersion, agentDir) {
|
|
7180
7384
|
try {
|
|
7181
7385
|
const path = getLastSeenVersionPath(agentDir);
|
|
@@ -7195,7 +7399,7 @@ function maybeWhatsNew(cliVersion, agentDir) {
|
|
|
7195
7399
|
}
|
|
7196
7400
|
}
|
|
7197
7401
|
function writeLastSeen(path, version) {
|
|
7198
|
-
mkdirSync(
|
|
7402
|
+
mkdirSync(dirname4(path), { recursive: true });
|
|
7199
7403
|
writeFileSync(path, version, "utf-8");
|
|
7200
7404
|
}
|
|
7201
7405
|
|
|
@@ -7235,7 +7439,19 @@ async function run(deps, argv = process.argv) {
|
|
|
7235
7439
|
let jwt = await loadJwt(deps.credentialsPath);
|
|
7236
7440
|
if (jwt) {
|
|
7237
7441
|
try {
|
|
7238
|
-
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
|
+
}
|
|
7239
7455
|
} catch (err) {
|
|
7240
7456
|
if (err instanceof UnauthorizedError) {
|
|
7241
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.
|