free-pi-cli 0.6.7 → 0.6.9
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-tm302552.js → main-cgxt6te1.js} +511 -107
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -14382,7 +14382,8 @@ var GithubDeviceResponseSchema = exports_external.object({
|
|
|
14382
14382
|
user_code: exports_external.string(),
|
|
14383
14383
|
verification_uri: exports_external.string(),
|
|
14384
14384
|
interval: exports_external.number(),
|
|
14385
|
-
expires_in: exports_external.number().optional()
|
|
14385
|
+
expires_in: exports_external.number().optional(),
|
|
14386
|
+
verification_uri_complete: exports_external.string().optional()
|
|
14386
14387
|
});
|
|
14387
14388
|
var AuthTokenRequestSchema = exports_external.object({
|
|
14388
14389
|
session_id: exports_external.string().uuid()
|
|
@@ -14391,7 +14392,10 @@ var AuthTokenPendingSchema = exports_external.object({
|
|
|
14391
14392
|
status: exports_external.literal("pending"),
|
|
14392
14393
|
retry_after: exports_external.number().optional()
|
|
14393
14394
|
});
|
|
14394
|
-
var AuthTokenIssuedSchema = exports_external.object({
|
|
14395
|
+
var AuthTokenIssuedSchema = exports_external.object({
|
|
14396
|
+
token: exports_external.string(),
|
|
14397
|
+
guest_bonus_credits_granted: exports_external.number().int().optional()
|
|
14398
|
+
});
|
|
14395
14399
|
var AuthTokenConsentRequiredSchema = exports_external.object({
|
|
14396
14400
|
code: exports_external.literal("consent_required"),
|
|
14397
14401
|
message: exports_external.string()
|
|
@@ -14437,15 +14441,24 @@ var GuestStartResponseSchema = exports_external.object({
|
|
|
14437
14441
|
guest: exports_external.literal(true),
|
|
14438
14442
|
allowance_usd: exports_external.string(),
|
|
14439
14443
|
remaining_usd: exports_external.string(),
|
|
14440
|
-
experiments: ExperimentAssignmentSchema.optional()
|
|
14444
|
+
experiments: ExperimentAssignmentSchema.optional(),
|
|
14445
|
+
claim_code: exports_external.string().optional(),
|
|
14446
|
+
github_bonus_credits: exports_external.number().int().optional()
|
|
14441
14447
|
});
|
|
14442
14448
|
var GuestUnavailableResponseSchema = exports_external.object({
|
|
14443
14449
|
code: exports_external.literal("guest_unavailable"),
|
|
14444
14450
|
message: exports_external.string(),
|
|
14445
14451
|
reason: exports_external.enum(["disabled", "global_budget", "ip_sessions", "ip_allowance"])
|
|
14446
14452
|
});
|
|
14453
|
+
var FunnelEventNameSchema = exports_external.enum([
|
|
14454
|
+
"device_code_shown",
|
|
14455
|
+
"browser_opened",
|
|
14456
|
+
"device_code_reminted",
|
|
14457
|
+
"device_code_expired",
|
|
14458
|
+
"device_poll_slow_down"
|
|
14459
|
+
]);
|
|
14447
14460
|
var FunnelEventRequestSchema = exports_external.object({
|
|
14448
|
-
event:
|
|
14461
|
+
event: FunnelEventNameSchema,
|
|
14449
14462
|
session_id: exports_external.string().uuid().optional()
|
|
14450
14463
|
});
|
|
14451
14464
|
var ReturnBonusTelemetrySchema = exports_external.object({
|
|
@@ -14624,6 +14637,11 @@ var ConfigSchema = exports_external.object({
|
|
|
14624
14637
|
maxOutputTokens: exports_external.number().int().min(1).default(32768),
|
|
14625
14638
|
accountsPerDayCeiling: exports_external.number().int().default(500),
|
|
14626
14639
|
authRatePerIpPerHour: exports_external.number().int().default(30),
|
|
14640
|
+
siteLoginStartRateLimitEnabled: exports_external.boolean().default(false),
|
|
14641
|
+
siteLoginStartPerIp: exports_external.number().int().min(0).default(5),
|
|
14642
|
+
siteLoginStartPerSubject: exports_external.number().int().min(0).default(5),
|
|
14643
|
+
siteLoginStartWindowSeconds: exports_external.number().int().min(1).default(600),
|
|
14644
|
+
siteLoginStartCooldownSeconds: exports_external.number().int().min(0).default(60),
|
|
14627
14645
|
webBuyUrlRatePerProviderPerHour: exports_external.number().int().default(20),
|
|
14628
14646
|
handoffTokenRatePerProviderPerHour: exports_external.number().int().default(60),
|
|
14629
14647
|
handoffHourlyCompletionCap: exports_external.number().int().default(120),
|
|
@@ -14643,6 +14661,7 @@ var ConfigSchema = exports_external.object({
|
|
|
14643
14661
|
lifecycleEmailSend: exports_external.boolean().default(false),
|
|
14644
14662
|
emailProviderConfigured: exports_external.boolean().default(false),
|
|
14645
14663
|
emailFrom: exports_external.string().optional(),
|
|
14664
|
+
emailPostalAddress: exports_external.string().optional(),
|
|
14646
14665
|
emailProviderUrl: exports_external.string().optional(),
|
|
14647
14666
|
hiveEnabled: exports_external.boolean().default(false),
|
|
14648
14667
|
providerOrder: exports_external.array(exports_external.string()).optional(),
|
|
@@ -14701,6 +14720,7 @@ var ConfigSchema = exports_external.object({
|
|
|
14701
14720
|
guestIpHashRetentionDays: exports_external.number().int().default(7),
|
|
14702
14721
|
returnBonusDay2Credits: exports_external.number().int().default(2000),
|
|
14703
14722
|
returnBonusDay7Credits: exports_external.number().int().default(1000),
|
|
14723
|
+
guestGithubBonusCredits: exports_external.number().int().default(1e4),
|
|
14704
14724
|
returnNudgeEmailEnabled: exports_external.boolean().default(false),
|
|
14705
14725
|
referralBonusCreditsPerDay: exports_external.number().int().default(5000),
|
|
14706
14726
|
referralBonusDays: exports_external.number().int().default(30),
|
|
@@ -14742,6 +14762,9 @@ function envBoolean(value) {
|
|
|
14742
14762
|
return;
|
|
14743
14763
|
return value === "true" || value === "1";
|
|
14744
14764
|
}
|
|
14765
|
+
function envFlagOn(value) {
|
|
14766
|
+
return ["true", "1", "on", "yes"].includes((value ?? "").trim().toLowerCase());
|
|
14767
|
+
}
|
|
14745
14768
|
function envList(value) {
|
|
14746
14769
|
if (!value)
|
|
14747
14770
|
return;
|
|
@@ -14773,6 +14796,11 @@ function loadConfig(env = process.env) {
|
|
|
14773
14796
|
maxOutputTokens: envNumber(env.MAX_OUTPUT_TOKENS),
|
|
14774
14797
|
accountsPerDayCeiling: envNumber(env.ACCOUNTS_PER_DAY_CEILING),
|
|
14775
14798
|
authRatePerIpPerHour: envNumber(env.AUTH_RATE_PER_IP_PER_HOUR),
|
|
14799
|
+
siteLoginStartRateLimitEnabled: envFlagOn(env.SITE_LOGIN_START_RATE_LIMIT),
|
|
14800
|
+
siteLoginStartPerIp: envNumber(env.SITE_LOGIN_START_PER_IP),
|
|
14801
|
+
siteLoginStartPerSubject: envNumber(env.SITE_LOGIN_START_PER_SUBJECT),
|
|
14802
|
+
siteLoginStartWindowSeconds: envNumber(env.SITE_LOGIN_START_WINDOW_SECONDS),
|
|
14803
|
+
siteLoginStartCooldownSeconds: envNumber(env.SITE_LOGIN_START_COOLDOWN_SECONDS),
|
|
14776
14804
|
webBuyUrlRatePerProviderPerHour: envNumber(env.WEB_BUY_URL_RATE_PER_PROVIDER_PER_HOUR),
|
|
14777
14805
|
handoffTokenRatePerProviderPerHour: envNumber(env.HANDOFF_TOKEN_RATE_PER_PROVIDER_PER_HOUR),
|
|
14778
14806
|
handoffHourlyCompletionCap: envNumber(env.HANDOFF_HOURLY_COMPLETION_CAP),
|
|
@@ -14792,6 +14820,7 @@ function loadConfig(env = process.env) {
|
|
|
14792
14820
|
lifecycleEmailSend: envBoolean(env.LIFECYCLE_EMAIL_SEND),
|
|
14793
14821
|
emailProviderConfigured: env.EMAIL_PROVIDER_KEY ? env.EMAIL_PROVIDER_KEY.trim().length > 0 : undefined,
|
|
14794
14822
|
emailFrom: env.EMAIL_FROM?.trim() || undefined,
|
|
14823
|
+
emailPostalAddress: env.EMAIL_POSTAL_ADDRESS?.trim() || undefined,
|
|
14795
14824
|
emailProviderUrl: env.EMAIL_PROVIDER_URL?.trim() || undefined,
|
|
14796
14825
|
hiveEnabled: envBoolean(env.HIVE_ENABLED),
|
|
14797
14826
|
providerOrder: envList(env.PROVIDER_ORDER),
|
|
@@ -14845,6 +14874,7 @@ function loadConfig(env = process.env) {
|
|
|
14845
14874
|
guestIpHashRetentionDays: envStrictInt("GUEST_IP_HASH_RETENTION_DAYS", env.GUEST_IP_HASH_RETENTION_DAYS),
|
|
14846
14875
|
returnBonusDay2Credits: envStrictInt("RETURN_BONUS_DAY2_CREDITS", env.RETURN_BONUS_DAY2_CREDITS),
|
|
14847
14876
|
returnBonusDay7Credits: envStrictInt("RETURN_BONUS_DAY7_CREDITS", env.RETURN_BONUS_DAY7_CREDITS),
|
|
14877
|
+
guestGithubBonusCredits: envStrictInt("GUEST_GITHUB_BONUS_CREDITS", env.GUEST_GITHUB_BONUS_CREDITS),
|
|
14848
14878
|
returnNudgeEmailEnabled: envBoolean(env.RETURN_NUDGE_EMAIL_ENABLED),
|
|
14849
14879
|
referralBonusCreditsPerDay: envStrictInt("REFERRAL_BONUS_CREDITS_PER_DAY", env.REFERRAL_BONUS_CREDITS_PER_DAY),
|
|
14850
14880
|
referralBonusDays: envStrictInt("REFERRAL_BONUS_DAYS", env.REFERRAL_BONUS_DAYS),
|
|
@@ -14929,42 +14959,46 @@ var LIMIT_NOTICE_LABELS = {
|
|
|
14929
14959
|
concurrent_session: "another session is active",
|
|
14930
14960
|
guest_allowance: "guest trial"
|
|
14931
14961
|
};
|
|
14932
|
-
var EXTRA_CREDITS_URL = "freepi.ai/extra-credits";
|
|
14933
14962
|
var LIMIT_NOTICE_LOCAL_TIME_AFTER_SECONDS = 90 * 60;
|
|
14934
14963
|
var REFILL_CLOCK_LIMITS = new Set(["daily_cap", "global_cap", "queue_wait"]);
|
|
14935
14964
|
function formatLimitNotice(fields, opts = {}) {
|
|
14936
14965
|
const slow = fields.lane === "slow" && fields.limit === "hourly_ceiling" && fields.hourly_ceiling != null;
|
|
14937
|
-
const label = slow ? `You've used today's free allowance, so you're on the slow lane (${fields.hourly_ceiling.toLocaleString("en-US")} requests an hour).` : LIMIT_NOTICE_LABELS[fields.limit] ?? fields.limit.replace(/_/g, " ");
|
|
14938
|
-
const parts = [label];
|
|
14939
14966
|
const retry = fields.retry_after_seconds;
|
|
14940
|
-
|
|
14967
|
+
let when;
|
|
14941
14968
|
if (retry != null && retry > 0) {
|
|
14942
14969
|
const useClock = Boolean(opts.localTime) && Boolean(fields.clears_at) && retry >= LIMIT_NOTICE_LOCAL_TIME_AFTER_SECONDS;
|
|
14943
|
-
|
|
14944
|
-
const formatted = new Intl.DateTimeFormat("en-US", {
|
|
14945
|
-
timeZone: opts.timeZone,
|
|
14946
|
-
hour: "numeric",
|
|
14947
|
-
minute: "2-digit"
|
|
14948
|
-
}).format(new Date(fields.clears_at));
|
|
14949
|
-
waitBits.push(`back at ${formatted}`);
|
|
14950
|
-
} else {
|
|
14951
|
-
waitBits.push(`back in ${Math.max(1, Math.ceil(retry / 60))} min`);
|
|
14952
|
-
}
|
|
14970
|
+
when = useClock ? `at ${new Intl.DateTimeFormat("en-US", { timeZone: opts.timeZone, hour: "numeric", minute: "2-digit" }).format(new Date(fields.clears_at))}` : `in ${Math.max(1, Math.ceil(retry / 60))} min`;
|
|
14953
14971
|
}
|
|
14954
|
-
|
|
14972
|
+
const resets = () => {
|
|
14973
|
+
if (opts.copyStyle !== "treatment" || !REFILL_CLOCK_LIMITS.has(fields.limit) || !fields.clears_at)
|
|
14974
|
+
return "";
|
|
14955
14975
|
const clears = new Date(fields.clears_at);
|
|
14956
|
-
|
|
14957
|
-
|
|
14958
|
-
|
|
14959
|
-
}
|
|
14960
|
-
if (waitBits.length > 0)
|
|
14961
|
-
parts.push(waitBits.join(" · "));
|
|
14976
|
+
return Number.isNaN(clears.getTime()) ? "" : ` Resets ${formatRefillEt(clears)}.`;
|
|
14977
|
+
};
|
|
14978
|
+
const refillOrBuy = (lead) => `${lead} ${when ? `Credits refill ${when}, or keep going with /buy-credits.` : "Keep going with /buy-credits."}${resets()}`;
|
|
14962
14979
|
if (slow) {
|
|
14963
|
-
const
|
|
14964
|
-
|
|
14980
|
+
const label2 = `You've used today's free allowance, so you're on the slow lane (${fields.hourly_ceiling.toLocaleString("en-US")} requests an hour).`;
|
|
14981
|
+
const wait = when ? ` Back ${when} — or use` : " Use";
|
|
14982
|
+
return `${label2}${wait} /buy-credits to keep going at full speed.`;
|
|
14965
14983
|
}
|
|
14966
|
-
|
|
14967
|
-
|
|
14984
|
+
switch (fields.limit) {
|
|
14985
|
+
case "guest_allowance":
|
|
14986
|
+
return fields.remaining_credits_today > 0 ? "Guest access is busy right now." : "Your guest trial is used up.";
|
|
14987
|
+
case "daily_cap":
|
|
14988
|
+
return refillOrBuy("You've used today's free credits.");
|
|
14989
|
+
case "rolling_cap":
|
|
14990
|
+
return `You've reached your usage limit for now.${when ? ` Back ${when}.` : ""}`;
|
|
14991
|
+
case "queue_wait":
|
|
14992
|
+
return `free-pi is busy right now. ${when ? `Try again ${when}, or keep going with /buy-credits.` : "Try again soon, or keep going with /buy-credits."}${resets()}`;
|
|
14993
|
+
case "global_cap":
|
|
14994
|
+
return `free-pi has reached its shared daily limit; this isn't your limit.${when ? ` Back ${when}.` : ""}${resets()}`;
|
|
14995
|
+
}
|
|
14996
|
+
const label = LIMIT_NOTICE_LABELS[fields.limit] ?? fields.limit.replace(/_/g, " ");
|
|
14997
|
+
const bits = [label];
|
|
14998
|
+
if (when)
|
|
14999
|
+
bits.push(`back ${when}`);
|
|
15000
|
+
bits.push(`${fields.remaining_credits_today.toLocaleString("en-US")} credits left today`);
|
|
15001
|
+
return bits.join(", ");
|
|
14968
15002
|
}
|
|
14969
15003
|
// ../shared/src/version.ts
|
|
14970
15004
|
function compareVersions(a, b) {
|
|
@@ -15095,7 +15129,7 @@ var HiveClassifyResponseSchema = exports_external.object({
|
|
|
15095
15129
|
// package.json
|
|
15096
15130
|
var package_default = {
|
|
15097
15131
|
name: "free-pi-cli",
|
|
15098
|
-
version: "0.6.
|
|
15132
|
+
version: "0.6.9",
|
|
15099
15133
|
description: "Free, ad-supported distribution of the pi coding agent. Inference paid for by ads.",
|
|
15100
15134
|
license: "MIT",
|
|
15101
15135
|
type: "module",
|
|
@@ -15732,13 +15766,13 @@ async function renderMeter(deps, ctx) {
|
|
|
15732
15766
|
confirm = { addedUsd: added, turnsLeft: CONFIRM_TURNS };
|
|
15733
15767
|
if (confirm && confirm.turnsLeft > 0) {
|
|
15734
15768
|
confirm.turnsLeft--;
|
|
15735
|
-
ctx.ui.setWidget(METER_WIDGET_ID, [
|
|
15769
|
+
ctx.ui.setWidget(METER_WIDGET_ID, [theme.fg("accent", creditAddedMessage(confirm.addedUsd))]);
|
|
15736
15770
|
return;
|
|
15737
15771
|
}
|
|
15738
15772
|
confirm = undefined;
|
|
15739
15773
|
const notice = typeof me.notice === "string" ? sanitizeText(me.notice).trim() : "";
|
|
15740
15774
|
if (notice !== "") {
|
|
15741
|
-
ctx.ui.setWidget(METER_WIDGET_ID, [
|
|
15775
|
+
ctx.ui.setWidget(METER_WIDGET_ID, [theme.fg("accent", notice)]);
|
|
15742
15776
|
return;
|
|
15743
15777
|
}
|
|
15744
15778
|
if (me.lane === "fallback") {
|
|
@@ -20178,12 +20212,14 @@ function formatStats(stats) {
|
|
|
20178
20212
|
const allowance = stats.allowance_credits;
|
|
20179
20213
|
const refillAt = stats.refill_at;
|
|
20180
20214
|
const lane2 = stats.lane;
|
|
20215
|
+
const bonus = stats.bonus_wallet_credits;
|
|
20181
20216
|
const todayLine = typeof remaining === "number" && typeof allowance === "number" && typeof refillAt === "string" ? formatCreditsLeftLine(remaining, allowance, new Date(refillAt)) : `today: ${percentUsed(stats.spent_usd_today, stats.cap_usd_today)}% of your free daily allowance used`;
|
|
20182
20217
|
return [
|
|
20183
20218
|
`tier: ${stats.tier}`,
|
|
20184
20219
|
todayLine,
|
|
20185
20220
|
...lane2 === "fallback" ? [SLOW_LANE_METER_LINE] : [],
|
|
20186
20221
|
...typeof credits2 === "number" ? [`credit: ${credits2.toLocaleString("en-US")} credits remaining`] : [],
|
|
20222
|
+
...typeof bonus === "number" && bonus > 0 ? [`bonus: ${bonus.toLocaleString("en-US")} credits remaining`] : [],
|
|
20187
20223
|
`today tokens: ${stats.prompt_tokens_today} prompt / ${stats.completion_tokens_today} completion`,
|
|
20188
20224
|
`lifetime: ${stats.lifetime.prompt_tokens} prompt / ${stats.lifetime.completion_tokens} completion tokens`,
|
|
20189
20225
|
"buy more credits: /buy-credits",
|
|
@@ -21186,6 +21222,22 @@ function formatLimitEnvelope(env, opts) {
|
|
|
21186
21222
|
hourly_ceiling: env.hourly_ceiling
|
|
21187
21223
|
}, { localTime: true, timeZone: opts?.timeZone, copyStyle: env.copy_variant });
|
|
21188
21224
|
}
|
|
21225
|
+
var LIMIT_CODES = new Set([
|
|
21226
|
+
"guest_allowance",
|
|
21227
|
+
"daily_cap",
|
|
21228
|
+
"queue_wait",
|
|
21229
|
+
"rolling_cap",
|
|
21230
|
+
"hourly_ceiling",
|
|
21231
|
+
"concurrent",
|
|
21232
|
+
"concurrent_session",
|
|
21233
|
+
"model_requires_credits",
|
|
21234
|
+
"global_cap",
|
|
21235
|
+
"upgrade_required",
|
|
21236
|
+
"guest_unavailable",
|
|
21237
|
+
"account_review",
|
|
21238
|
+
"ads_required",
|
|
21239
|
+
"region_unavailable"
|
|
21240
|
+
]);
|
|
21189
21241
|
function createErrorNoticeExtension(opts = {}) {
|
|
21190
21242
|
const lastAt = new Map;
|
|
21191
21243
|
const now = opts.now ?? Date.now;
|
|
@@ -21204,15 +21256,97 @@ function createErrorNoticeExtension(opts = {}) {
|
|
|
21204
21256
|
if (t - (lastAt.get(env.code) ?? -Infinity) < NOTICE_DEDUPE_MS)
|
|
21205
21257
|
return;
|
|
21206
21258
|
lastAt.set(env.code, t);
|
|
21207
|
-
ctx.ui.notify(formatLimitEnvelope(env, { timeZone }), env.status >= 500 ? "error" : "warning");
|
|
21208
21259
|
const extra = opts.extraNotice?.(env);
|
|
21209
|
-
|
|
21210
|
-
|
|
21260
|
+
const text = formatLimitEnvelope(env, { timeZone }) + (extra ? `
|
|
21261
|
+
${extra}` : "");
|
|
21262
|
+
ctx.ui.notify(text, LIMIT_CODES.has(env.code) ? "info" : env.status >= 500 ? "error" : "warning");
|
|
21211
21263
|
});
|
|
21212
21264
|
}
|
|
21213
21265
|
};
|
|
21214
21266
|
}
|
|
21215
21267
|
|
|
21268
|
+
// src/upgrade-notice.ts
|
|
21269
|
+
var UPGRADE_NOTICE_MARKER = ": freepi_notice";
|
|
21270
|
+
var UPGRADE_NOTICE_TEXT = "free-pi is upgrading — short delay. Your request will continue automatically.";
|
|
21271
|
+
var SLOW_SERVER_HINT = "Waiting for the server — free-pi may be upgrading; your request will continue automatically.";
|
|
21272
|
+
var SLOW_HEADERS_MS = 8000;
|
|
21273
|
+
var UPGRADE_NOTICE_DEDUPE_MS = 60000;
|
|
21274
|
+
function isCompletionsCall(input, completionsUrl) {
|
|
21275
|
+
try {
|
|
21276
|
+
const raw = input instanceof Request ? input.url : String(input);
|
|
21277
|
+
const got = new URL(raw);
|
|
21278
|
+
const want = new URL(completionsUrl);
|
|
21279
|
+
return got.origin === want.origin && got.pathname === want.pathname;
|
|
21280
|
+
} catch {
|
|
21281
|
+
return false;
|
|
21282
|
+
}
|
|
21283
|
+
}
|
|
21284
|
+
function wrapFetchForUpgrade(base, opts) {
|
|
21285
|
+
return async (input, init) => {
|
|
21286
|
+
if (!isCompletionsCall(input, opts.completionsUrl))
|
|
21287
|
+
return base(input, init);
|
|
21288
|
+
const timer = setTimeout(opts.onSlow, opts.slowMs ?? SLOW_HEADERS_MS);
|
|
21289
|
+
let res;
|
|
21290
|
+
try {
|
|
21291
|
+
res = await base(input, init);
|
|
21292
|
+
} finally {
|
|
21293
|
+
clearTimeout(timer);
|
|
21294
|
+
}
|
|
21295
|
+
if (!res.body || !(res.headers.get("content-type") ?? "").includes("text/event-stream"))
|
|
21296
|
+
return res;
|
|
21297
|
+
const decoder = new TextDecoder;
|
|
21298
|
+
let tail = `
|
|
21299
|
+
`;
|
|
21300
|
+
let seen = false;
|
|
21301
|
+
const body = res.body.pipeThrough(new TransformStream({
|
|
21302
|
+
transform(chunk, ctl) {
|
|
21303
|
+
ctl.enqueue(chunk);
|
|
21304
|
+
if (seen)
|
|
21305
|
+
return;
|
|
21306
|
+
const text = tail + decoder.decode(chunk, { stream: true });
|
|
21307
|
+
if (text.includes(`
|
|
21308
|
+
${UPGRADE_NOTICE_MARKER}`)) {
|
|
21309
|
+
seen = true;
|
|
21310
|
+
opts.onNotice();
|
|
21311
|
+
} else
|
|
21312
|
+
tail = text.slice(-UPGRADE_NOTICE_MARKER.length);
|
|
21313
|
+
}
|
|
21314
|
+
}));
|
|
21315
|
+
return new Response(body, { status: res.status, statusText: res.statusText, headers: res.headers });
|
|
21316
|
+
};
|
|
21317
|
+
}
|
|
21318
|
+
var notify;
|
|
21319
|
+
var lastAt = new Map;
|
|
21320
|
+
var WRAPPED = Symbol.for("free-pi.upgrade-notice");
|
|
21321
|
+
function createUpgradeNoticeExtension(opts) {
|
|
21322
|
+
const now = opts.now ?? Date.now;
|
|
21323
|
+
const show = (message) => {
|
|
21324
|
+
const t = now();
|
|
21325
|
+
if (t - (lastAt.get(message) ?? -Infinity) < UPGRADE_NOTICE_DEDUPE_MS)
|
|
21326
|
+
return;
|
|
21327
|
+
lastAt.set(message, t);
|
|
21328
|
+
notify?.(message);
|
|
21329
|
+
};
|
|
21330
|
+
return {
|
|
21331
|
+
name: "free-pi-upgrade-notice",
|
|
21332
|
+
factory(pi) {
|
|
21333
|
+
pi.on("session_start", (_event, ctx) => {
|
|
21334
|
+
notify = (m) => ctx.ui.notify(m, "info");
|
|
21335
|
+
});
|
|
21336
|
+
if (globalThis.fetch[WRAPPED])
|
|
21337
|
+
return;
|
|
21338
|
+
const wrapped = wrapFetchForUpgrade(globalThis.fetch, {
|
|
21339
|
+
completionsUrl: `${completionsBaseUrl(opts.baseUrl)}/chat/completions`,
|
|
21340
|
+
slowMs: opts.slowMs,
|
|
21341
|
+
onNotice: () => show(UPGRADE_NOTICE_TEXT),
|
|
21342
|
+
onSlow: () => show(SLOW_SERVER_HINT)
|
|
21343
|
+
});
|
|
21344
|
+
wrapped[WRAPPED] = true;
|
|
21345
|
+
globalThis.fetch = wrapped;
|
|
21346
|
+
}
|
|
21347
|
+
};
|
|
21348
|
+
}
|
|
21349
|
+
|
|
21216
21350
|
// src/agent-rules.ts
|
|
21217
21351
|
var AGENT_RULES = `## Working rules
|
|
21218
21352
|
- When you say you will run, check, or change something, make that tool call in the same
|
|
@@ -21380,6 +21514,17 @@ var LOGIN_VALUE_LINE = "sign in free with GitHub: full daily allowance, FREE abl
|
|
|
21380
21514
|
var GUEST_NEAR_WALL_LINE = `A few guest requests left. ${LOGIN_VALUE_LINE}`;
|
|
21381
21515
|
var SIGNED_IN_CONTINUE_LINE = "Signed in. Your conversation continues — full daily allowance is unlocked.";
|
|
21382
21516
|
var HEADLESS_FALLBACK_LINE = "No browser or clipboard here — open the URL and enter the code above.";
|
|
21517
|
+
function formatGuestBonusBox(claimCode, credits2) {
|
|
21518
|
+
const amount = credits2.toLocaleString("en-US");
|
|
21519
|
+
const rows = [
|
|
21520
|
+
`GUEST BONUS * code ${claimCode}`,
|
|
21521
|
+
`Connect GitHub with /login -> +${amount} credits`,
|
|
21522
|
+
"One time, on your first GitHub sign-in."
|
|
21523
|
+
];
|
|
21524
|
+
const inner = Math.max(...rows.map((r) => r.length)) + 4;
|
|
21525
|
+
const edge = `+${"-".repeat(inner)}+`;
|
|
21526
|
+
return [edge, ...rows.map((r) => `| ${r.padEnd(inner - 2)}|`), edge];
|
|
21527
|
+
}
|
|
21383
21528
|
function isHandoffTreatment(variant) {
|
|
21384
21529
|
return variant === "treatment";
|
|
21385
21530
|
}
|
|
@@ -21452,6 +21597,22 @@ function mergeAttribution(a, b) {
|
|
|
21452
21597
|
|
|
21453
21598
|
// src/changelog.ts
|
|
21454
21599
|
var CHANGELOG_HIGHLIGHTS = [
|
|
21600
|
+
{
|
|
21601
|
+
version: "0.6.9",
|
|
21602
|
+
highlights: [
|
|
21603
|
+
"When the guest trial runs out, sign-in opens in your browser automatically, and the link and code stay on screen.",
|
|
21604
|
+
"An expired sign-in takes you back to GitHub sign-in instead of starting a new guest trial.",
|
|
21605
|
+
"Limits (daily credits, slow lane, busy, usage limit) now show as information with a next step, not as errors."
|
|
21606
|
+
]
|
|
21607
|
+
},
|
|
21608
|
+
{
|
|
21609
|
+
version: "0.6.8",
|
|
21610
|
+
highlights: [
|
|
21611
|
+
"During a server upgrade free-pi now says it is upgrading and that your request will continue automatically.",
|
|
21612
|
+
"Guest trials show a bonus code: sign in with GitHub (/login) for a one-time +10,000 credits.",
|
|
21613
|
+
"/usage now shows your bonus credits on their own line, matching the footer."
|
|
21614
|
+
]
|
|
21615
|
+
},
|
|
21455
21616
|
{
|
|
21456
21617
|
version: "0.6.7",
|
|
21457
21618
|
highlights: [
|
|
@@ -21836,7 +21997,9 @@ async function startGuestSession(baseUrl, consentVersion, attribution = {}) {
|
|
|
21836
21997
|
allowance_usd: parsed.allowance_usd,
|
|
21837
21998
|
remaining_usd: parsed.remaining_usd,
|
|
21838
21999
|
guestHandoffVariant: parsed.experiments?.guest_handoff,
|
|
21839
|
-
firstTaskVariant: parsed.experiments?.first_task
|
|
22000
|
+
firstTaskVariant: parsed.experiments?.first_task,
|
|
22001
|
+
claimCode: parsed.claim_code,
|
|
22002
|
+
githubBonusCredits: parsed.github_bonus_credits
|
|
21840
22003
|
};
|
|
21841
22004
|
}
|
|
21842
22005
|
async function reportFunnelEvent(baseUrl, event, opts = {}) {
|
|
@@ -21864,8 +22027,18 @@ class LoginDeniedError extends Error {
|
|
|
21864
22027
|
super("login_denied");
|
|
21865
22028
|
}
|
|
21866
22029
|
}
|
|
22030
|
+
|
|
22031
|
+
class DeviceCodeExpiredError extends Error {
|
|
22032
|
+
constructor() {
|
|
22033
|
+
super("expired_token");
|
|
22034
|
+
}
|
|
22035
|
+
}
|
|
22036
|
+
function isExpiredTokenPoll(body) {
|
|
22037
|
+
return body?.message === "expired_token";
|
|
22038
|
+
}
|
|
21867
22039
|
async function pollForToken(baseUrl, sessionId, interval, sleep, opts = {}) {
|
|
21868
22040
|
let consecutiveNetworkFailures = 0;
|
|
22041
|
+
let waitSeconds = Math.max(0, interval);
|
|
21869
22042
|
const now = opts.now ?? Date.now;
|
|
21870
22043
|
for (;; ) {
|
|
21871
22044
|
if (opts.deadline !== undefined && now() >= opts.deadline) {
|
|
@@ -21888,7 +22061,7 @@ async function pollForToken(baseUrl, sessionId, interval, sleep, opts = {}) {
|
|
|
21888
22061
|
if (consecutiveNetworkFailures >= 6) {
|
|
21889
22062
|
throw new Error(`token poll failed: network unreachable after ${consecutiveNetworkFailures} attempts (${err instanceof Error ? err.message : String(err)})`);
|
|
21890
22063
|
}
|
|
21891
|
-
await sleep(Math.max(
|
|
22064
|
+
await sleep(Math.max(waitSeconds, 5) * 1000);
|
|
21892
22065
|
continue;
|
|
21893
22066
|
}
|
|
21894
22067
|
if (!res.ok) {
|
|
@@ -21900,6 +22073,8 @@ async function pollForToken(baseUrl, sessionId, interval, sleep, opts = {}) {
|
|
|
21900
22073
|
throw new LoginDeniedError;
|
|
21901
22074
|
if (res.status === 410 && body?.code === "expired")
|
|
21902
22075
|
throw new LoginTimeoutError;
|
|
22076
|
+
if (isExpiredTokenPoll(body))
|
|
22077
|
+
throw new DeviceCodeExpiredError;
|
|
21903
22078
|
throw new Error(refusalMessage(res.status, body) ?? `token poll failed: HTTP ${res.status}`);
|
|
21904
22079
|
}
|
|
21905
22080
|
const parsed = AuthTokenResponseSchema.parse(await res.json());
|
|
@@ -21909,7 +22084,13 @@ async function pollForToken(baseUrl, sessionId, interval, sleep, opts = {}) {
|
|
|
21909
22084
|
throw new Error("consent_required: server did not record consent before polling");
|
|
21910
22085
|
}
|
|
21911
22086
|
const retryAfter = "retry_after" in parsed && typeof parsed.retry_after === "number" ? parsed.retry_after : 0;
|
|
21912
|
-
|
|
22087
|
+
if (retryAfter > waitSeconds) {
|
|
22088
|
+
waitSeconds = retryAfter;
|
|
22089
|
+
opts.onSlowDown?.(waitSeconds);
|
|
22090
|
+
} else if (retryAfter > 0) {
|
|
22091
|
+
opts.onSlowDown?.(waitSeconds);
|
|
22092
|
+
}
|
|
22093
|
+
await sleep(waitSeconds * 1000);
|
|
21913
22094
|
}
|
|
21914
22095
|
}
|
|
21915
22096
|
async function checkToken(baseUrl, jwt2) {
|
|
@@ -22002,6 +22183,7 @@ function clipboardCommand(platform) {
|
|
|
22002
22183
|
var CONSENT_TEXT = `
|
|
22003
22184
|
free-pi is free. In exchange, we show you ads and may train on your data.
|
|
22004
22185
|
If you want to use free-pi, you accept that. If you don't, don't use it.
|
|
22186
|
+
You must be at least 13 in the US, 16 in the EEA/UK, or 18 elsewhere.
|
|
22005
22187
|
Terms: https://freepi.ai/terms Privacy: https://freepi.ai/privacy
|
|
22006
22188
|
`.trim();
|
|
22007
22189
|
var CONSENT_VERSION = "4";
|
|
@@ -22179,6 +22361,37 @@ var LOGIN_REWARD_LINE = "Sign in with GitHub to get 15,000 free credits a day";
|
|
|
22179
22361
|
var LOGIN_WAITING_LINE = "Waiting for GitHub…";
|
|
22180
22362
|
var LOGIN_TIMEOUT_LINE = "GitHub sign-in timed out. Run /login to try again.";
|
|
22181
22363
|
var BROWSER_FAILED_LINE = "Couldn't open a browser here. Open the URL above yourself and enter the code.";
|
|
22364
|
+
var LOGIN_CODE_EXPIRED_LINE = "That code expired. Enter this new one before it times out.";
|
|
22365
|
+
var LOGIN_CODE_EXPIRY_HINT = "Enter the code on GitHub before it expires.";
|
|
22366
|
+
var MAX_DEVICE_CODE_REMINTS = 2;
|
|
22367
|
+
function formatRemaining(seconds) {
|
|
22368
|
+
const s = Math.max(0, Math.floor(seconds));
|
|
22369
|
+
if (s >= 90)
|
|
22370
|
+
return `${Math.round(s / 60)} min`;
|
|
22371
|
+
if (s >= 60)
|
|
22372
|
+
return "1 min";
|
|
22373
|
+
if (s > 0)
|
|
22374
|
+
return `${s} sec`;
|
|
22375
|
+
return "0 sec";
|
|
22376
|
+
}
|
|
22377
|
+
function formatCodeExpiryLine(expiresInSeconds) {
|
|
22378
|
+
return ` Code expires in ${formatRemaining(expiresInSeconds)}. ${LOGIN_CODE_EXPIRY_HINT}`;
|
|
22379
|
+
}
|
|
22380
|
+
function formatWaitingLine(expiresInSeconds) {
|
|
22381
|
+
return `${LOGIN_WAITING_LINE} code expires in ${formatRemaining(expiresInSeconds)}.`;
|
|
22382
|
+
}
|
|
22383
|
+
function deviceBrowserUrl(device) {
|
|
22384
|
+
if (device.verification_uri_complete?.trim())
|
|
22385
|
+
return device.verification_uri_complete.trim();
|
|
22386
|
+
try {
|
|
22387
|
+
const url2 = new URL(device.verification_uri);
|
|
22388
|
+
if (!url2.searchParams.has("user_code"))
|
|
22389
|
+
url2.searchParams.set("user_code", device.user_code);
|
|
22390
|
+
return url2.toString();
|
|
22391
|
+
} catch {
|
|
22392
|
+
return device.verification_uri;
|
|
22393
|
+
}
|
|
22394
|
+
}
|
|
22182
22395
|
function formatDeviceCodePrompt(device, variant) {
|
|
22183
22396
|
const value = isHandoffTreatment(variant) ? LOGIN_VALUE_LINE : LOGIN_REWARD_LINE;
|
|
22184
22397
|
return [
|
|
@@ -22188,12 +22401,51 @@ function formatDeviceCodePrompt(device, variant) {
|
|
|
22188
22401
|
"",
|
|
22189
22402
|
` Open ${device.verification_uri}`,
|
|
22190
22403
|
` Enter code: ${device.user_code}`,
|
|
22404
|
+
formatCodeExpiryLine(device.expires_in ?? 900),
|
|
22191
22405
|
""
|
|
22192
22406
|
].join(`
|
|
22193
22407
|
`);
|
|
22194
22408
|
}
|
|
22195
22409
|
async function runDeviceLogin(deps, attribution = {}) {
|
|
22410
|
+
let remints = 0;
|
|
22411
|
+
let lastSessionId;
|
|
22412
|
+
for (;; ) {
|
|
22413
|
+
try {
|
|
22414
|
+
return await runDeviceLoginOnce(deps, attribution, {
|
|
22415
|
+
reminted: remints > 0,
|
|
22416
|
+
onSession: (id) => {
|
|
22417
|
+
lastSessionId = id;
|
|
22418
|
+
}
|
|
22419
|
+
});
|
|
22420
|
+
} catch (err) {
|
|
22421
|
+
const expired = err instanceof DeviceCodeExpiredError || err instanceof LoginTimeoutError;
|
|
22422
|
+
if (!expired || remints >= MAX_DEVICE_CODE_REMINTS) {
|
|
22423
|
+
throw err instanceof DeviceCodeExpiredError ? new LoginTimeoutError : err;
|
|
22424
|
+
}
|
|
22425
|
+
if (lastSessionId) {
|
|
22426
|
+
await reportFunnelEvent(deps.baseUrl, "device_code_expired", {
|
|
22427
|
+
sessionId: lastSessionId,
|
|
22428
|
+
jwt: deps.jwt,
|
|
22429
|
+
fetchImpl: deps.fetchImpl
|
|
22430
|
+
});
|
|
22431
|
+
}
|
|
22432
|
+
remints += 1;
|
|
22433
|
+
deps.log(`
|
|
22434
|
+
${LOGIN_CODE_EXPIRED_LINE}
|
|
22435
|
+
`);
|
|
22436
|
+
}
|
|
22437
|
+
}
|
|
22438
|
+
}
|
|
22439
|
+
async function runDeviceLoginOnce(deps, attribution, once) {
|
|
22196
22440
|
const device = await startDeviceFlow(deps.baseUrl);
|
|
22441
|
+
once.onSession(device.session_id);
|
|
22442
|
+
if (once.reminted) {
|
|
22443
|
+
await reportFunnelEvent(deps.baseUrl, "device_code_reminted", {
|
|
22444
|
+
sessionId: device.session_id,
|
|
22445
|
+
jwt: deps.jwt,
|
|
22446
|
+
fetchImpl: deps.fetchImpl
|
|
22447
|
+
});
|
|
22448
|
+
}
|
|
22197
22449
|
deps.log(formatDeviceCodePrompt(device, deps.variant));
|
|
22198
22450
|
if (deps.credentialsPath) {
|
|
22199
22451
|
await savePendingLogin(deps.credentialsPath, {
|
|
@@ -22207,10 +22459,12 @@ async function runDeviceLogin(deps, attribution = {}) {
|
|
|
22207
22459
|
if (copied)
|
|
22208
22460
|
deps.log(` (code copied to clipboard)
|
|
22209
22461
|
`);
|
|
22210
|
-
|
|
22462
|
+
const expiresIn = device.expires_in ?? 900;
|
|
22463
|
+
deps.log(formatWaitingLine(expiresIn));
|
|
22464
|
+
const openUrl = deviceBrowserUrl(device);
|
|
22211
22465
|
let browserOpened = false;
|
|
22212
22466
|
try {
|
|
22213
|
-
browserOpened = await deps.openBrowser(
|
|
22467
|
+
browserOpened = await deps.openBrowser(openUrl) !== false;
|
|
22214
22468
|
} catch {
|
|
22215
22469
|
browserOpened = false;
|
|
22216
22470
|
}
|
|
@@ -22224,16 +22478,23 @@ async function runDeviceLogin(deps, attribution = {}) {
|
|
|
22224
22478
|
deps.log(copied ? BROWSER_FAILED_LINE : `${BROWSER_FAILED_LINE} ${HEADLESS_FALLBACK_LINE}`);
|
|
22225
22479
|
}
|
|
22226
22480
|
await submitConsent(deps.baseUrl, device.session_id, CONSENT_VERSION, attribution);
|
|
22227
|
-
const
|
|
22228
|
-
const
|
|
22229
|
-
|
|
22230
|
-
|
|
22231
|
-
|
|
22232
|
-
|
|
22233
|
-
|
|
22234
|
-
|
|
22235
|
-
|
|
22236
|
-
|
|
22481
|
+
const deadline = (deps.now ?? Date.now)() + expiresIn * 1000;
|
|
22482
|
+
const token = await pollForToken(deps.baseUrl, device.session_id, device.interval, deps.sleep, {
|
|
22483
|
+
deadline,
|
|
22484
|
+
now: deps.now,
|
|
22485
|
+
jwt: deps.jwt,
|
|
22486
|
+
fetchImpl: deps.fetchImpl,
|
|
22487
|
+
onSlowDown: () => {
|
|
22488
|
+
reportFunnelEvent(deps.baseUrl, "device_poll_slow_down", {
|
|
22489
|
+
sessionId: device.session_id,
|
|
22490
|
+
jwt: deps.jwt,
|
|
22491
|
+
fetchImpl: deps.fetchImpl
|
|
22492
|
+
});
|
|
22493
|
+
}
|
|
22494
|
+
});
|
|
22495
|
+
if (deps.credentialsPath)
|
|
22496
|
+
await clearPendingLogin(deps.credentialsPath);
|
|
22497
|
+
return token;
|
|
22237
22498
|
}
|
|
22238
22499
|
var CONNECT_BROWSER_FAILED_LINE = "Couldn't open a browser here, so using the GitHub device code instead.";
|
|
22239
22500
|
var CONNECT_FALLBACK_LINE = "Couldn't start the browser sign-in, so using the GitHub device code instead.";
|
|
@@ -22454,7 +22715,25 @@ async function openPrivacyPolicy(opts, ctx) {
|
|
|
22454
22715
|
function resolveHandoffVariant(opts) {
|
|
22455
22716
|
return typeof opts.variant === "function" ? opts.variant() : opts.variant;
|
|
22456
22717
|
}
|
|
22457
|
-
|
|
22718
|
+
var LOGIN_ATTEMPT_MAX_MS = 900000;
|
|
22719
|
+
var loginAttempt;
|
|
22720
|
+
async function runLogin(opts, ctx, lead) {
|
|
22721
|
+
const startedAt = (opts.now ?? Date.now)();
|
|
22722
|
+
if (loginAttempt && startedAt - loginAttempt.startedAt < LOGIN_ATTEMPT_MAX_MS)
|
|
22723
|
+
return false;
|
|
22724
|
+
const attempt = { startedAt };
|
|
22725
|
+
loginAttempt = attempt;
|
|
22726
|
+
try {
|
|
22727
|
+
return await runLoginUnguarded(opts, ctx, lead);
|
|
22728
|
+
} finally {
|
|
22729
|
+
if (loginAttempt === attempt)
|
|
22730
|
+
loginAttempt = undefined;
|
|
22731
|
+
}
|
|
22732
|
+
}
|
|
22733
|
+
async function runLoginUnguarded(opts, ctx, lead) {
|
|
22734
|
+
const lines = lead ? [lead] : [];
|
|
22735
|
+
if (lead)
|
|
22736
|
+
ctx.ui.notify(lead, "info");
|
|
22458
22737
|
try {
|
|
22459
22738
|
const attribution = opts.agentDir ? await loadAttribution(getAttributionPath(opts.agentDir)) : {};
|
|
22460
22739
|
const variant = resolveHandoffVariant(opts);
|
|
@@ -22462,7 +22741,11 @@ async function runLogin(opts, ctx) {
|
|
|
22462
22741
|
baseUrl: opts.baseUrl,
|
|
22463
22742
|
openBrowser: opts.openBrowserImpl ?? openBrowser,
|
|
22464
22743
|
sleep: (ms) => new Promise((r) => setTimeout(r, ms)),
|
|
22465
|
-
log: (message) =>
|
|
22744
|
+
log: (message) => {
|
|
22745
|
+
lines.push(message.trim());
|
|
22746
|
+
ctx.ui.notify(lines.filter(Boolean).join(`
|
|
22747
|
+
`), "info");
|
|
22748
|
+
},
|
|
22466
22749
|
jwt: await opts.getToken(),
|
|
22467
22750
|
fetchImpl: opts.fetchImpl,
|
|
22468
22751
|
variant,
|
|
@@ -22472,33 +22755,32 @@ async function runLogin(opts, ctx) {
|
|
|
22472
22755
|
await saveJwt(opts.credentialsPath, token);
|
|
22473
22756
|
opts.onLogin?.(token);
|
|
22474
22757
|
ctx.ui.notify(isHandoffTreatment(variant) ? SIGNED_IN_CONTINUE_LINE : "Signed in. You now have 15,000 free credits a day, plus a one-time comeback bonus.", "info");
|
|
22758
|
+
return true;
|
|
22475
22759
|
} catch (err) {
|
|
22476
22760
|
if (err instanceof LoginTimeoutError) {
|
|
22477
|
-
ctx.ui.notify(LOGIN_TIMEOUT_LINE, "
|
|
22478
|
-
return;
|
|
22761
|
+
ctx.ui.notify(LOGIN_TIMEOUT_LINE, "info");
|
|
22762
|
+
return false;
|
|
22479
22763
|
}
|
|
22480
22764
|
if (err instanceof LoginDeniedError) {
|
|
22481
|
-
ctx.ui.notify(LOGIN_DENIED_LINE, "
|
|
22482
|
-
return;
|
|
22765
|
+
ctx.ui.notify(LOGIN_DENIED_LINE, "info");
|
|
22766
|
+
return false;
|
|
22483
22767
|
}
|
|
22484
22768
|
const reason = err instanceof Error && err.message ? ` (${err.message})` : "";
|
|
22485
22769
|
ctx.ui.notify(`Couldn't finish GitHub sign-in${reason}. Run /login to try again.`, "error");
|
|
22770
|
+
return false;
|
|
22486
22771
|
}
|
|
22487
22772
|
}
|
|
22488
22773
|
function installLoginHandler(mode, opts) {
|
|
22489
22774
|
const m = mode;
|
|
22490
22775
|
mode.handleLoginCommand = () => {
|
|
22491
|
-
const
|
|
22492
|
-
const notify = (message, type) => {
|
|
22776
|
+
const notify2 = (message, type) => {
|
|
22493
22777
|
if (type === "error")
|
|
22494
22778
|
return m.showError(message);
|
|
22495
22779
|
if (type === "warning")
|
|
22496
22780
|
return m.showWarning(message);
|
|
22497
|
-
|
|
22498
|
-
m.showStatus(lines.join(`
|
|
22499
|
-
`));
|
|
22781
|
+
m.showStatus(message);
|
|
22500
22782
|
};
|
|
22501
|
-
return runLogin(opts, { ui: { notify } });
|
|
22783
|
+
return runLogin(opts, { ui: { notify: notify2 } });
|
|
22502
22784
|
};
|
|
22503
22785
|
}
|
|
22504
22786
|
var LOGOUT_DONE_LINE = "Signed out of free-pi. Restart free-pi to sign in again.";
|
|
@@ -22615,14 +22897,24 @@ function loadRea(pi, opts, ctx) {
|
|
|
22615
22897
|
}
|
|
22616
22898
|
ctx.ui.notify("Loading REA (reverse-engineering tools) for this session. The first run downloads rea-agents, so it can take a minute. Deep native analysis needs your own Hopper, Ghidra, or IDA install.", "info");
|
|
22617
22899
|
}
|
|
22900
|
+
var GUEST_WALL_LINE = "Your guest trial is used up. Opening GitHub sign-in in your browser… (15,000 free credits a day)";
|
|
22901
|
+
var GUEST_BUSY_LINE = "Guest access is busy right now. Opening GitHub sign-in in your browser… (15,000 free credits a day)";
|
|
22902
|
+
var GUEST_UNAVAILABLE_LINE = "Sign in with GitHub to keep going. Opening your browser… (15,000 free credits a day)";
|
|
22618
22903
|
function createGuestLoginExtension(opts) {
|
|
22619
22904
|
let started = false;
|
|
22905
|
+
let inFlight = false;
|
|
22906
|
+
let signedIn = false;
|
|
22620
22907
|
let nearWallWarned = false;
|
|
22621
|
-
const start = (ctx) => {
|
|
22908
|
+
const start = (ctx, line) => {
|
|
22622
22909
|
started = true;
|
|
22910
|
+
inFlight = true;
|
|
22623
22911
|
const variant = resolveHandoffVariant(opts);
|
|
22624
|
-
|
|
22625
|
-
runLogin(opts, ctx)
|
|
22912
|
+
const lead = line ?? (isHandoffTreatment(variant) ? LOGIN_VALUE_LINE : LOGIN_REWARD_LINE);
|
|
22913
|
+
runLogin(opts, ctx, lead).then((ok) => {
|
|
22914
|
+
signedIn = ok;
|
|
22915
|
+
}).catch(() => {}).finally(() => {
|
|
22916
|
+
inFlight = false;
|
|
22917
|
+
});
|
|
22626
22918
|
};
|
|
22627
22919
|
return {
|
|
22628
22920
|
name: "free-pi-guest-login",
|
|
@@ -22631,8 +22923,12 @@ function createGuestLoginExtension(opts) {
|
|
|
22631
22923
|
if (!opts.isGuest?.())
|
|
22632
22924
|
return;
|
|
22633
22925
|
const msg = event.message;
|
|
22634
|
-
|
|
22635
|
-
|
|
22926
|
+
const envelope = msg?.stopReason === "error" ? parseErrorEnvelope(msg.errorMessage) : undefined;
|
|
22927
|
+
if (envelope && (envelope.code === "guest_allowance" || envelope.code === "guest_unavailable" || envelope.prompt === "github_signin")) {
|
|
22928
|
+
if (!inFlight && !signedIn) {
|
|
22929
|
+
const busy = envelope.code === "guest_allowance" && (envelope.remaining_credits_today ?? 0) > 0;
|
|
22930
|
+
start(ctx, envelope.code === "guest_unavailable" ? GUEST_UNAVAILABLE_LINE : busy ? GUEST_BUSY_LINE : GUEST_WALL_LINE);
|
|
22931
|
+
}
|
|
22636
22932
|
return;
|
|
22637
22933
|
}
|
|
22638
22934
|
if (started || nearWallWarned || !isHandoffTreatment(resolveHandoffVariant(opts)))
|
|
@@ -23452,7 +23748,7 @@ function createHiveExtension(opts) {
|
|
|
23452
23748
|
let connectTimer;
|
|
23453
23749
|
let roomWaiter;
|
|
23454
23750
|
const dialogs = new Set;
|
|
23455
|
-
const
|
|
23751
|
+
const notify2 = (message, type = "info") => ctxRef?.ui.notify(message, type);
|
|
23456
23752
|
function send(frame) {
|
|
23457
23753
|
if (ws && ws.readyState === OPEN)
|
|
23458
23754
|
ws.send(JSON.stringify(frame));
|
|
@@ -23496,13 +23792,13 @@ function createHiveExtension(opts) {
|
|
|
23496
23792
|
const message = [notice, changes].filter(Boolean).join(`
|
|
23497
23793
|
`);
|
|
23498
23794
|
if (message)
|
|
23499
|
-
|
|
23795
|
+
notify2(message, "info");
|
|
23500
23796
|
roomWaiter?.();
|
|
23501
23797
|
roomWaiter = undefined;
|
|
23502
23798
|
}
|
|
23503
23799
|
async function start(ctx) {
|
|
23504
23800
|
if (ws || starting) {
|
|
23505
|
-
|
|
23801
|
+
notify2(roomUrl ? `Hive room: ${roomUrl}` : "Hive: still connecting...");
|
|
23506
23802
|
return;
|
|
23507
23803
|
}
|
|
23508
23804
|
starting = true;
|
|
@@ -23511,7 +23807,7 @@ function createHiveExtension(opts) {
|
|
|
23511
23807
|
token = await opts.getToken();
|
|
23512
23808
|
} catch {
|
|
23513
23809
|
starting = false;
|
|
23514
|
-
|
|
23810
|
+
notify2("Hive: couldn't read your free-pi login. Try again after signing in.", "error");
|
|
23515
23811
|
return;
|
|
23516
23812
|
}
|
|
23517
23813
|
starting = false;
|
|
@@ -23578,7 +23874,7 @@ Sandboxing is available: when you approve a guest's shell command you can run it
|
|
|
23578
23874
|
clearTimeout(connectTimer);
|
|
23579
23875
|
if (!settled)
|
|
23580
23876
|
send({ type: "busy", settled: false });
|
|
23581
|
-
|
|
23877
|
+
notify2(`Hive room open: ${frame.url}
|
|
23582
23878
|
Guests sign in with GitHub to watch and chat. They can ask to drive; you approve each request, and risky actions still ask you.${sandboxLine()}`);
|
|
23583
23879
|
updateStatus();
|
|
23584
23880
|
roomWaiter?.();
|
|
@@ -23610,7 +23906,7 @@ Guests sign in with GitHub to watch and chat. They can ask to drive; you approve
|
|
|
23610
23906
|
break;
|
|
23611
23907
|
case "error":
|
|
23612
23908
|
if (frame.code !== "stale")
|
|
23613
|
-
|
|
23909
|
+
notify2(`Hive: ${frame.code}`, "warning");
|
|
23614
23910
|
break;
|
|
23615
23911
|
case "closed":
|
|
23616
23912
|
endRoom(`Hive room closed (${frame.reason}).`);
|
|
@@ -23783,7 +24079,7 @@ ${decision.reason}`, ACTION_DIALOG_MS);
|
|
|
23783
24079
|
handler: async (args, ctx) => {
|
|
23784
24080
|
ctxRef = ctx;
|
|
23785
24081
|
const [sub = "", ...rest3] = args.trim().split(/\s+/);
|
|
23786
|
-
const noRoom = () =>
|
|
24082
|
+
const noRoom = () => notify2("No hive room is open. Run /hive to start one.");
|
|
23787
24083
|
switch (sub.toLowerCase()) {
|
|
23788
24084
|
case "":
|
|
23789
24085
|
return start(ctx);
|
|
@@ -23802,20 +24098,20 @@ ${decision.reason}`, ACTION_DIALOG_MS);
|
|
|
23802
24098
|
const handle2 = (rest3[0] ?? "").replace(/^@/, "").toLowerCase();
|
|
23803
24099
|
const ids = new Set(members.filter((m) => handle2 && m.handle.toLowerCase() === handle2).map((m) => m.provider_id));
|
|
23804
24100
|
if (ids.size === 0)
|
|
23805
|
-
return
|
|
24101
|
+
return notify2(`No guest named "${handle2}" in the room. Usage: /hive kick <handle>`);
|
|
23806
24102
|
dropRecorded();
|
|
23807
24103
|
for (const provider_id of ids) {
|
|
23808
24104
|
kicked.add(provider_id);
|
|
23809
24105
|
send({ type: "kick", provider_id });
|
|
23810
24106
|
}
|
|
23811
|
-
return
|
|
24107
|
+
return notify2(`Kicked @${handle2}. They cannot rejoin this room.`);
|
|
23812
24108
|
}
|
|
23813
24109
|
case "end":
|
|
23814
24110
|
if (!ws)
|
|
23815
24111
|
return noRoom();
|
|
23816
24112
|
return endRoom("Hive room ended.", true);
|
|
23817
24113
|
default:
|
|
23818
|
-
return
|
|
24114
|
+
return notify2("Usage: /hive, /hive take, /hive kick <handle>, /hive end");
|
|
23819
24115
|
}
|
|
23820
24116
|
}
|
|
23821
24117
|
});
|
|
@@ -24743,6 +25039,7 @@ function buildRuntimeOptions(opts, sessionId) {
|
|
|
24743
25039
|
imageToolExtension,
|
|
24744
25040
|
visionToolExtension,
|
|
24745
25041
|
errorNoticeExtension,
|
|
25042
|
+
createUpgradeNoticeExtension({ baseUrl: opts.baseUrl }),
|
|
24746
25043
|
createOutputLimitExtension(),
|
|
24747
25044
|
toolGuardExtension,
|
|
24748
25045
|
commandsExtension,
|
|
@@ -25284,32 +25581,56 @@ async function resolveAttribution(agentDir, argv) {
|
|
|
25284
25581
|
await saveAttribution(getAttributionPath(agentDir), merged);
|
|
25285
25582
|
return { attribution: merged };
|
|
25286
25583
|
}
|
|
25287
|
-
|
|
25288
|
-
|
|
25289
|
-
if ("
|
|
25290
|
-
|
|
25584
|
+
var GUEST_SIGNIN_PROMPT = "You're using free-pi as a guest. Press Enter to sign in with GitHub (15,000 free credits a day), or Esc to keep going as a guest.";
|
|
25585
|
+
function parseGuestSigninKey(raw) {
|
|
25586
|
+
if (raw === "\r" || raw === `
|
|
25587
|
+
`)
|
|
25588
|
+
return "signin";
|
|
25589
|
+
if (raw === "\x1B")
|
|
25590
|
+
return "guest";
|
|
25591
|
+
if (raw === "\x03")
|
|
25592
|
+
return "aborted";
|
|
25593
|
+
return;
|
|
25594
|
+
}
|
|
25595
|
+
async function promptGuestSigninInteractive() {
|
|
25596
|
+
let prompt = `
|
|
25597
|
+
${GUEST_SIGNIN_PROMPT}
|
|
25598
|
+
`;
|
|
25599
|
+
for (;; ) {
|
|
25600
|
+
const choice = parseGuestSigninKey(await readOneKeypress(prompt));
|
|
25601
|
+
prompt = "";
|
|
25602
|
+
if (choice)
|
|
25603
|
+
return choice;
|
|
25291
25604
|
}
|
|
25605
|
+
}
|
|
25606
|
+
function storedTokenKind(jwt2) {
|
|
25292
25607
|
try {
|
|
25293
|
-
const
|
|
25294
|
-
|
|
25295
|
-
|
|
25296
|
-
|
|
25297
|
-
|
|
25298
|
-
|
|
25299
|
-
credentialsPath: deps.credentialsPath
|
|
25300
|
-
}, resolved.attribution ?? {}, { forceDevice: argv.includes("--device-login"), env: deps.env, platform: deps.platform });
|
|
25301
|
-
await saveJwt(deps.credentialsPath, token);
|
|
25302
|
-
return token;
|
|
25303
|
-
} catch (err) {
|
|
25304
|
-
if (err instanceof LoginTimeoutError) {
|
|
25305
|
-
deps.log(LOGIN_TIMEOUT_LINE);
|
|
25306
|
-
}
|
|
25307
|
-
if (err instanceof LoginDeniedError) {
|
|
25308
|
-
deps.log(LOGIN_DENIED_LINE);
|
|
25309
|
-
}
|
|
25310
|
-
throw err;
|
|
25608
|
+
const claims = JSON.parse(Buffer.from(jwt2.split(".")[1] ?? "", "base64url").toString("utf8"));
|
|
25609
|
+
if (typeof claims !== "object" || claims === null)
|
|
25610
|
+
return "unknown";
|
|
25611
|
+
return claims.guest === true ? "guest" : "github";
|
|
25612
|
+
} catch {
|
|
25613
|
+
return "unknown";
|
|
25311
25614
|
}
|
|
25312
25615
|
}
|
|
25616
|
+
async function loginFlow(deps, argv = [], priorJwt) {
|
|
25617
|
+
const resolved = await resolveAttribution(deps.agentDir, argv);
|
|
25618
|
+
if ("error" in resolved && resolved.error) {
|
|
25619
|
+
throw new Error(resolved.error);
|
|
25620
|
+
}
|
|
25621
|
+
const token = await runLoginAuto({
|
|
25622
|
+
baseUrl: deps.baseUrl,
|
|
25623
|
+
openBrowser: deps.openBrowser,
|
|
25624
|
+
sleep: deps.sleep,
|
|
25625
|
+
log: deps.log,
|
|
25626
|
+
jwt: priorJwt ?? await loadJwt(deps.credentialsPath),
|
|
25627
|
+
credentialsPath: deps.credentialsPath
|
|
25628
|
+
}, resolved.attribution ?? {}, { forceDevice: argv.includes("--device-login"), env: deps.env, platform: deps.platform });
|
|
25629
|
+
await saveJwt(deps.credentialsPath, token);
|
|
25630
|
+
return token;
|
|
25631
|
+
}
|
|
25632
|
+
var SIGNIN_UNFINISHED_LINE = "Sign-in didn't finish. Run npx free-pi-cli again when you're ready.";
|
|
25633
|
+
var DECLINED_CALM_LINE = "OK, free-pi won't start. Run it again if you change your mind.";
|
|
25313
25634
|
function validateSessionId(value) {
|
|
25314
25635
|
try {
|
|
25315
25636
|
SessionManager2.inMemory(process.cwd(), { id: value });
|
|
@@ -25385,6 +25706,12 @@ ${FINISH_SIGNIN_LINE}
|
|
|
25385
25706
|
updateLatest = handled.updateLatest;
|
|
25386
25707
|
versionHandled = true;
|
|
25387
25708
|
}
|
|
25709
|
+
const interactive = deps.stdinIsTTY ?? process.stdin.isTTY === true;
|
|
25710
|
+
const canPrompt = interactive && !resumeFlags.hasPrompt && !argv.some((a) => a === "-p" || a === "--print");
|
|
25711
|
+
let rejectedJwt;
|
|
25712
|
+
let rejectedKind = "unknown";
|
|
25713
|
+
let startedGuestThisRun = false;
|
|
25714
|
+
let signedInAfterExpiry = false;
|
|
25388
25715
|
let guestHandoffVariant;
|
|
25389
25716
|
let showFirstTask = false;
|
|
25390
25717
|
let consentCurrent = false;
|
|
@@ -25394,8 +25721,10 @@ ${FINISH_SIGNIN_LINE}
|
|
|
25394
25721
|
consentCurrent = serverConsent === CONSENT_VERSION;
|
|
25395
25722
|
if (serverConsent !== undefined && serverConsent !== CONSENT_VERSION) {
|
|
25396
25723
|
if (!await deps.promptConsent(REPROMPT_CONSENT_TEXT)) {
|
|
25397
|
-
|
|
25398
|
-
|
|
25724
|
+
if (!interactive)
|
|
25725
|
+
return 1;
|
|
25726
|
+
deps.log(DECLINED_CALM_LINE);
|
|
25727
|
+
return 0;
|
|
25399
25728
|
}
|
|
25400
25729
|
consentCurrent = true;
|
|
25401
25730
|
try {
|
|
@@ -25407,6 +25736,8 @@ ${FINISH_SIGNIN_LINE}
|
|
|
25407
25736
|
}
|
|
25408
25737
|
} catch (err) {
|
|
25409
25738
|
if (err instanceof UnauthorizedError) {
|
|
25739
|
+
rejectedKind = storedTokenKind(jwt2);
|
|
25740
|
+
rejectedJwt = jwt2;
|
|
25410
25741
|
await clearJwt(deps.credentialsPath);
|
|
25411
25742
|
jwt2 = null;
|
|
25412
25743
|
}
|
|
@@ -25415,9 +25746,45 @@ ${FINISH_SIGNIN_LINE}
|
|
|
25415
25746
|
if (!jwt2) {
|
|
25416
25747
|
const accepted = await deps.promptConsent();
|
|
25417
25748
|
if (!accepted) {
|
|
25418
|
-
|
|
25419
|
-
|
|
25749
|
+
if (!interactive)
|
|
25750
|
+
return 1;
|
|
25751
|
+
deps.log(DECLINED_CALM_LINE);
|
|
25752
|
+
return 0;
|
|
25753
|
+
}
|
|
25754
|
+
if (canPrompt && rejectedKind !== "unknown") {
|
|
25755
|
+
try {
|
|
25756
|
+
if (rejectedKind === "github") {
|
|
25757
|
+
deps.log(`
|
|
25758
|
+
Your free-pi sign-in has expired. Signing in with GitHub again.
|
|
25759
|
+
`);
|
|
25760
|
+
}
|
|
25761
|
+
const choice = rejectedKind === "github" ? "signin" : await (deps.promptGuestSignin ?? promptGuestSigninInteractive)();
|
|
25762
|
+
if (choice === "aborted") {
|
|
25763
|
+
deps.log("Aborted.");
|
|
25764
|
+
return 1;
|
|
25765
|
+
}
|
|
25766
|
+
if (choice === "signin") {
|
|
25767
|
+
jwt2 = await loginFlow(deps, argv, rejectedKind === "guest" ? rejectedJwt : undefined);
|
|
25768
|
+
consentCurrent = true;
|
|
25769
|
+
signedInAfterExpiry = true;
|
|
25770
|
+
}
|
|
25771
|
+
} catch (err) {
|
|
25772
|
+
if (rejectedKind === "github") {
|
|
25773
|
+
if (err instanceof LoginTimeoutError || err instanceof LoginDeniedError) {
|
|
25774
|
+
deps.log(SIGNIN_UNFINISHED_LINE);
|
|
25775
|
+
return 0;
|
|
25776
|
+
}
|
|
25777
|
+
const reason = err instanceof Error && err.message ? err.message : "unknown error";
|
|
25778
|
+
deps.log(`Couldn't finish GitHub sign-in: ${reason}. Run npx free-pi-cli again to retry.`);
|
|
25779
|
+
return 1;
|
|
25780
|
+
}
|
|
25781
|
+
deps.log(`
|
|
25782
|
+
Couldn't finish GitHub sign-in. Continuing as a guest.
|
|
25783
|
+
`);
|
|
25784
|
+
}
|
|
25420
25785
|
}
|
|
25786
|
+
}
|
|
25787
|
+
if (!jwt2) {
|
|
25421
25788
|
showIntro(deps.log);
|
|
25422
25789
|
const resolved = await resolveAttribution(deps.agentDir, argv);
|
|
25423
25790
|
if ("error" in resolved && resolved.error) {
|
|
@@ -25429,11 +25796,17 @@ ${FINISH_SIGNIN_LINE}
|
|
|
25429
25796
|
const guest = await startGuestSession(deps.baseUrl, CONSENT_VERSION, attribution);
|
|
25430
25797
|
await saveJwt(deps.credentialsPath, guest.token);
|
|
25431
25798
|
jwt2 = guest.token;
|
|
25799
|
+
startedGuestThisRun = true;
|
|
25432
25800
|
deps.log(isHandoffTreatment(guest.guestHandoffVariant) ? `
|
|
25433
25801
|
You're on a short guest trial — ads pay for it. ${LOGIN_VALUE_LINE}
|
|
25434
25802
|
` : `
|
|
25435
25803
|
You're on a short guest trial — ads pay for it. Sign in with GitHub anytime (/login) to get 15,000 free credits a day and unlock a one-time comeback bonus.
|
|
25436
25804
|
`);
|
|
25805
|
+
if (guest.claimCode && guest.githubBonusCredits) {
|
|
25806
|
+
deps.log(emphasize(formatGuestBonusBox(guest.claimCode, guest.githubBonusCredits).join(`
|
|
25807
|
+
`)) + `
|
|
25808
|
+
`);
|
|
25809
|
+
}
|
|
25437
25810
|
guestHandoffVariant = guest.guestHandoffVariant;
|
|
25438
25811
|
showFirstTask = guest.firstTaskVariant === "treatment" && !resumeFlags.hasPrompt;
|
|
25439
25812
|
} catch (err) {
|
|
@@ -25442,11 +25815,19 @@ You're on a short guest trial — ads pay for it. Sign in with GitHub anytime (/
|
|
|
25442
25815
|
deps.log(`
|
|
25443
25816
|
Guest trial is not available right now.
|
|
25444
25817
|
`);
|
|
25445
|
-
|
|
25818
|
+
try {
|
|
25819
|
+
jwt2 = await loginFlow(deps, argv);
|
|
25820
|
+
} catch (loginErr) {
|
|
25821
|
+
if (loginErr instanceof LoginTimeoutError || loginErr instanceof LoginDeniedError) {
|
|
25822
|
+
deps.log(SIGNIN_UNFINISHED_LINE);
|
|
25823
|
+
return 0;
|
|
25824
|
+
}
|
|
25825
|
+
throw loginErr;
|
|
25826
|
+
}
|
|
25446
25827
|
}
|
|
25447
25828
|
consentCurrent = true;
|
|
25448
25829
|
}
|
|
25449
|
-
if (!hadJwt)
|
|
25830
|
+
if (!hadJwt || signedInAfterExpiry)
|
|
25450
25831
|
versionCheck = await deps.checkClientVersion(deps.baseUrl, CLI_VERSION, jwt2);
|
|
25451
25832
|
if (!versionHandled && versionCheck.action !== "ok") {
|
|
25452
25833
|
const handled = await handleVersionAction(deps, versionCheck);
|
|
@@ -25459,13 +25840,37 @@ Guest trial is not available right now.
|
|
|
25459
25840
|
deps.log(whatsNew);
|
|
25460
25841
|
if (versionCheck.notice)
|
|
25461
25842
|
deps.log(versionCheck.notice);
|
|
25843
|
+
let me = await fetchMe2(deps.baseUrl, jwt2).catch(() => null);
|
|
25844
|
+
if (me?.guest && !startedGuestThisRun && canPrompt && consentCurrent) {
|
|
25845
|
+
try {
|
|
25846
|
+
const choice = await (deps.promptGuestSignin ?? promptGuestSigninInteractive)();
|
|
25847
|
+
if (choice === "aborted") {
|
|
25848
|
+
deps.log("Aborted.");
|
|
25849
|
+
return 1;
|
|
25850
|
+
}
|
|
25851
|
+
if (choice === "signin") {
|
|
25852
|
+
jwt2 = await loginFlow(deps, argv);
|
|
25853
|
+
me = await fetchMe2(deps.baseUrl, jwt2).catch(() => null);
|
|
25854
|
+
versionCheck = await deps.checkClientVersion(deps.baseUrl, CLI_VERSION, jwt2).catch(() => versionCheck);
|
|
25855
|
+
if (versionCheck.action !== "ok") {
|
|
25856
|
+
const handled = await handleVersionAction(deps, versionCheck);
|
|
25857
|
+
if (handled.code !== undefined)
|
|
25858
|
+
return handled.code;
|
|
25859
|
+
updateLatest = handled.updateLatest;
|
|
25860
|
+
}
|
|
25861
|
+
}
|
|
25862
|
+
} catch {
|
|
25863
|
+
deps.log(`
|
|
25864
|
+
Couldn't finish GitHub sign-in. Continuing as a guest.
|
|
25865
|
+
`);
|
|
25866
|
+
}
|
|
25867
|
+
}
|
|
25462
25868
|
let getGravityContext;
|
|
25463
25869
|
if (consentCurrent) {
|
|
25464
25870
|
try {
|
|
25465
25871
|
getGravityContext = deps.startGravityPixel?.(jwt2);
|
|
25466
25872
|
} catch {}
|
|
25467
25873
|
}
|
|
25468
|
-
const me = await fetchMe2(deps.baseUrl, jwt2).catch(() => null);
|
|
25469
25874
|
if (me)
|
|
25470
25875
|
updateAllowanceCache(me);
|
|
25471
25876
|
guestHandoffVariant = me?.experiments?.guest_handoff ?? guestHandoffVariant;
|
|
@@ -25493,7 +25898,6 @@ ${creditsBack}`);
|
|
|
25493
25898
|
const experiment = versionCheck.experiments?.[RESUME_ON_LAUNCH_EXPERIMENT];
|
|
25494
25899
|
const variant = experiment?.variant === "treatment" ? "treatment" : "control";
|
|
25495
25900
|
const recentHours = recentHoursFromEnv(process.env, experiment?.recent_hours);
|
|
25496
|
-
const interactive = deps.stdinIsTTY ?? process.stdin.isTTY === true;
|
|
25497
25901
|
if (!session && !resumeFlags.startNew && !resumeFlags.hasPrompt && variant === "treatment" && interactive) {
|
|
25498
25902
|
const latest = await lookupLatest(process.cwd(), deps.agentDir);
|
|
25499
25903
|
const now = deps.now?.() ?? new Date;
|