free-pi-cli 0.5.3 → 0.6.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +4 -2
- package/dist/index.js +1 -1
- package/dist/{main-zgz9as8b.js → main-zv5s2zn8.js} +803 -168
- package/package.json +1 -1
|
@@ -6,7 +6,7 @@ import {
|
|
|
6
6
|
|
|
7
7
|
// src/main.ts
|
|
8
8
|
import { realpathSync as realpathSync3, writeFileSync as writeFileSync3 } from "node:fs";
|
|
9
|
-
import { dirname as
|
|
9
|
+
import { dirname as dirname10, join as join11 } from "node:path";
|
|
10
10
|
|
|
11
11
|
// src/browser.ts
|
|
12
12
|
import { spawn } from "node:child_process";
|
|
@@ -14408,11 +14408,16 @@ var GuestStartRequestSchema = exports_external.object({
|
|
|
14408
14408
|
signup_src: exports_external.string().optional(),
|
|
14409
14409
|
referral_code: exports_external.string().optional()
|
|
14410
14410
|
});
|
|
14411
|
+
var ExperimentVariantSchema = exports_external.enum(["control", "treatment"]);
|
|
14412
|
+
var ExperimentAssignmentSchema = exports_external.object({
|
|
14413
|
+
guest_handoff: ExperimentVariantSchema.optional()
|
|
14414
|
+
});
|
|
14411
14415
|
var GuestStartResponseSchema = exports_external.object({
|
|
14412
14416
|
token: exports_external.string(),
|
|
14413
14417
|
guest: exports_external.literal(true),
|
|
14414
14418
|
allowance_usd: exports_external.string(),
|
|
14415
|
-
remaining_usd: exports_external.string()
|
|
14419
|
+
remaining_usd: exports_external.string(),
|
|
14420
|
+
experiments: ExperimentAssignmentSchema.optional()
|
|
14416
14421
|
});
|
|
14417
14422
|
var GuestUnavailableResponseSchema = exports_external.object({
|
|
14418
14423
|
code: exports_external.literal("guest_unavailable"),
|
|
@@ -14424,10 +14429,15 @@ var FunnelEventRequestSchema = exports_external.object({
|
|
|
14424
14429
|
session_id: exports_external.string().uuid().optional()
|
|
14425
14430
|
});
|
|
14426
14431
|
var ReturnBonusTelemetrySchema = exports_external.object({
|
|
14427
|
-
type: exports_external.
|
|
14432
|
+
type: exports_external.enum(["card_shown", "credits_back"])
|
|
14433
|
+
});
|
|
14434
|
+
var ExperimentExposureSchema = exports_external.object({
|
|
14435
|
+
experiment: exports_external.enum(["resume_on_launch"]),
|
|
14436
|
+
event: exports_external.enum(["offer_shown", "offer_accepted"])
|
|
14428
14437
|
});
|
|
14429
14438
|
var ReturnBonusSurfaceSchema = exports_external.object({
|
|
14430
14439
|
show_card: exports_external.boolean(),
|
|
14440
|
+
credits_back: exports_external.boolean(),
|
|
14431
14441
|
day2_granted: exports_external.boolean(),
|
|
14432
14442
|
day7_granted: exports_external.boolean()
|
|
14433
14443
|
});
|
|
@@ -14448,9 +14458,15 @@ var MeResponseSchema = exports_external.object({
|
|
|
14448
14458
|
refill_at: exports_external.string().optional(),
|
|
14449
14459
|
lane: UsageLaneSchema.optional(),
|
|
14450
14460
|
bonus_credits: exports_external.number().optional(),
|
|
14461
|
+
bonus_wallet_credits: exports_external.number().optional(),
|
|
14462
|
+
use_bonus: exports_external.string().optional(),
|
|
14451
14463
|
guest: exports_external.boolean().optional(),
|
|
14452
14464
|
guest_prompt_login: exports_external.boolean().optional(),
|
|
14453
|
-
|
|
14465
|
+
guest_requests_remaining: exports_external.number().int().optional(),
|
|
14466
|
+
guest_near_wall: exports_external.boolean().optional(),
|
|
14467
|
+
experiments: ExperimentAssignmentSchema.optional(),
|
|
14468
|
+
return_bonus: ReturnBonusSurfaceSchema.optional(),
|
|
14469
|
+
visible_allowance: exports_external.boolean().optional()
|
|
14454
14470
|
});
|
|
14455
14471
|
var MeStatsResponseSchema = exports_external.object({
|
|
14456
14472
|
user_id: exports_external.string(),
|
|
@@ -14466,6 +14482,8 @@ var MeStatsResponseSchema = exports_external.object({
|
|
|
14466
14482
|
refill_at: exports_external.string(),
|
|
14467
14483
|
lane: UsageLaneSchema,
|
|
14468
14484
|
bonus_credits: exports_external.number(),
|
|
14485
|
+
bonus_wallet_credits: exports_external.number().optional(),
|
|
14486
|
+
use_bonus: exports_external.string().optional(),
|
|
14469
14487
|
request_count_today: exports_external.number(),
|
|
14470
14488
|
prompt_tokens_today: exports_external.number(),
|
|
14471
14489
|
completion_tokens_today: exports_external.number(),
|
|
@@ -14477,6 +14495,9 @@ var MeStatsResponseSchema = exports_external.object({
|
|
|
14477
14495
|
}),
|
|
14478
14496
|
guest: exports_external.boolean().optional(),
|
|
14479
14497
|
guest_prompt_login: exports_external.boolean().optional(),
|
|
14498
|
+
guest_requests_remaining: exports_external.number().int().optional(),
|
|
14499
|
+
guest_near_wall: exports_external.boolean().optional(),
|
|
14500
|
+
experiments: ExperimentAssignmentSchema.optional(),
|
|
14480
14501
|
return_bonus: ReturnBonusSurfaceSchema.optional()
|
|
14481
14502
|
});
|
|
14482
14503
|
var AdSlotSchema = exports_external.enum(["banner", "inline"]);
|
|
@@ -14530,7 +14551,8 @@ var ClientVersionResponseSchema = exports_external.object({
|
|
|
14530
14551
|
model: exports_external.string().optional(),
|
|
14531
14552
|
notice: exports_external.string().optional(),
|
|
14532
14553
|
models: exports_external.array(exports_external.object({ id: exports_external.string(), name: exports_external.string(), section: exports_external.string().optional() })).optional(),
|
|
14533
|
-
fusion: exports_external.object({ planner: exports_external.string(), executor: exports_external.string() }).optional()
|
|
14554
|
+
fusion: exports_external.object({ planner: exports_external.string(), executor: exports_external.string() }).optional(),
|
|
14555
|
+
experiments: exports_external.record(exports_external.string(), exports_external.object({ variant: exports_external.enum(["control", "treatment"]) }).passthrough()).optional()
|
|
14534
14556
|
});
|
|
14535
14557
|
var SessionResetResponseSchema = exports_external.object({
|
|
14536
14558
|
released: exports_external.boolean()
|
|
@@ -14596,6 +14618,12 @@ var ConfigSchema = exports_external.object({
|
|
|
14596
14618
|
providerDataCollection: exports_external.enum(["allow", "deny"]).optional(),
|
|
14597
14619
|
promptCacheEnabled: exports_external.boolean().default(false),
|
|
14598
14620
|
emailCaptureEnabled: exports_external.boolean().default(false),
|
|
14621
|
+
lifecycleEmailExperimentEnabled: exports_external.boolean().default(false),
|
|
14622
|
+
lifecycleEmailEnabled: exports_external.boolean().default(false),
|
|
14623
|
+
lifecycleEmailSend: exports_external.boolean().default(false),
|
|
14624
|
+
emailProviderConfigured: exports_external.boolean().default(false),
|
|
14625
|
+
emailFrom: exports_external.string().optional(),
|
|
14626
|
+
emailProviderUrl: exports_external.string().optional(),
|
|
14599
14627
|
hiveEnabled: exports_external.boolean().default(false),
|
|
14600
14628
|
providerOrder: exports_external.array(exports_external.string()).optional(),
|
|
14601
14629
|
tierNewMaxDays: exports_external.number().int().default(30),
|
|
@@ -14607,6 +14635,7 @@ var ConfigSchema = exports_external.object({
|
|
|
14607
14635
|
fallbackLaneMinHeadroomBps: exports_external.number().int().default(1000),
|
|
14608
14636
|
queueWaitRetryAfterSeconds: exports_external.number().int().default(0),
|
|
14609
14637
|
sessionLeaseTtlMinutes: exports_external.number().int().default(30),
|
|
14638
|
+
signedInMaxCliSessions: exports_external.number().int().min(1).default(4),
|
|
14610
14639
|
minCliVersion: exports_external.string().default("0.0.0"),
|
|
14611
14640
|
latestCliVersion: exports_external.string().default("0.2.0"),
|
|
14612
14641
|
abuseWindowMinutes: exports_external.number().int().default(30),
|
|
@@ -14623,13 +14652,14 @@ var ConfigSchema = exports_external.object({
|
|
|
14623
14652
|
abuseRestrictedHourlyCap: exports_external.number().int().default(5),
|
|
14624
14653
|
abuseNormalHourlyCap: exports_external.number().int().default(30),
|
|
14625
14654
|
abuseSignedInNormalHourlyCap: exports_external.number().int().default(0),
|
|
14655
|
+
abuseDay0SkipHourly: exports_external.boolean().default(true),
|
|
14626
14656
|
abuseFlipAlertThreshold: exports_external.number().int().default(10),
|
|
14627
14657
|
abuseAdBypassMinRequests: exports_external.number().int().default(20),
|
|
14628
14658
|
abuseAdBypassRatio: exports_external.number().int().default(10),
|
|
14629
14659
|
traceMaxInflightWrites: exports_external.number().int().default(32),
|
|
14630
14660
|
catalogCacheTtlSeconds: exports_external.number().int().default(60),
|
|
14631
14661
|
eventMaxInflightWrites: exports_external.number().int().default(128),
|
|
14632
|
-
guestEnabled: exports_external.boolean().default(
|
|
14662
|
+
guestEnabled: exports_external.boolean().default(false),
|
|
14633
14663
|
guestAllowanceMicros: exports_external.bigint().default(30000n),
|
|
14634
14664
|
guestGlobalDailyCapMicros: exports_external.bigint().default(3000000n),
|
|
14635
14665
|
guestIpSessionsPerDay: exports_external.number().int().default(8),
|
|
@@ -14639,10 +14669,12 @@ var ConfigSchema = exports_external.object({
|
|
|
14639
14669
|
guestIpHashRetentionDays: exports_external.number().int().default(7),
|
|
14640
14670
|
returnBonusDay2Credits: exports_external.number().int().default(2000),
|
|
14641
14671
|
returnBonusDay7Credits: exports_external.number().int().default(1000),
|
|
14672
|
+
returnNudgeEmailEnabled: exports_external.boolean().default(false),
|
|
14642
14673
|
referralBonusCreditsPerDay: exports_external.number().int().default(5000),
|
|
14643
14674
|
referralBonusDays: exports_external.number().int().default(30),
|
|
14644
14675
|
referralMaxActivePerReferrer: exports_external.number().int().default(10),
|
|
14645
|
-
referralGithubMinAgeDays: exports_external.number().int().default(30)
|
|
14676
|
+
referralGithubMinAgeDays: exports_external.number().int().default(30),
|
|
14677
|
+
referralCreditGrantCredits: exports_external.number().int().default(0)
|
|
14646
14678
|
});
|
|
14647
14679
|
function envNumber(value) {
|
|
14648
14680
|
if (value === undefined || value === "")
|
|
@@ -14702,6 +14734,12 @@ function loadConfig(env = process.env) {
|
|
|
14702
14734
|
providerDataCollection: env.PROVIDER_DATA_COLLECTION || undefined,
|
|
14703
14735
|
promptCacheEnabled: envBoolean(env.PROMPT_CACHE_ENABLED),
|
|
14704
14736
|
emailCaptureEnabled: envBoolean(env.EMAIL_CAPTURE_ENABLED),
|
|
14737
|
+
lifecycleEmailExperimentEnabled: envBoolean(env.LIFECYCLE_EMAIL_EXPERIMENT),
|
|
14738
|
+
lifecycleEmailEnabled: envBoolean(env.LIFECYCLE_EMAIL_ENABLED),
|
|
14739
|
+
lifecycleEmailSend: envBoolean(env.LIFECYCLE_EMAIL_SEND),
|
|
14740
|
+
emailProviderConfigured: env.EMAIL_PROVIDER_KEY ? env.EMAIL_PROVIDER_KEY.trim().length > 0 : undefined,
|
|
14741
|
+
emailFrom: env.EMAIL_FROM?.trim() || undefined,
|
|
14742
|
+
emailProviderUrl: env.EMAIL_PROVIDER_URL?.trim() || undefined,
|
|
14705
14743
|
hiveEnabled: envBoolean(env.HIVE_ENABLED),
|
|
14706
14744
|
providerOrder: envList(env.PROVIDER_ORDER),
|
|
14707
14745
|
tierNewMaxDays: envNumber(env.TIER_NEW_MAX_DAYS),
|
|
@@ -14713,6 +14751,7 @@ function loadConfig(env = process.env) {
|
|
|
14713
14751
|
fallbackLaneMinHeadroomBps: envNumber(env.FALLBACK_LANE_MIN_HEADROOM_BPS),
|
|
14714
14752
|
queueWaitRetryAfterSeconds: envNumber(env.QUEUE_WAIT_RETRY_AFTER_SECONDS),
|
|
14715
14753
|
sessionLeaseTtlMinutes: envNumber(env.SESSION_LEASE_TTL_MINUTES),
|
|
14754
|
+
signedInMaxCliSessions: envNumber(env.SIGNED_IN_MAX_CLI_SESSIONS),
|
|
14716
14755
|
minCliVersion: env.MIN_CLI_VERSION || undefined,
|
|
14717
14756
|
latestCliVersion: env.LATEST_CLI_VERSION || undefined,
|
|
14718
14757
|
abuseWindowMinutes: envNumber(env.ABUSE_WINDOW_MINUTES),
|
|
@@ -14729,6 +14768,7 @@ function loadConfig(env = process.env) {
|
|
|
14729
14768
|
abuseRestrictedHourlyCap: envNumber(env.ABUSE_RESTRICTED_HOURLY_CAP),
|
|
14730
14769
|
abuseNormalHourlyCap: envNumber(env.ABUSE_NORMAL_HOURLY_CAP),
|
|
14731
14770
|
abuseSignedInNormalHourlyCap: envNumber(env.ABUSE_SIGNED_IN_NORMAL_HOURLY_CAP),
|
|
14771
|
+
abuseDay0SkipHourly: envBoolean(env.ABUSE_DAY0_SKIP_HOURLY),
|
|
14732
14772
|
abuseFlipAlertThreshold: envNumber(env.ABUSE_FLIP_ALERT_THRESHOLD),
|
|
14733
14773
|
abuseAdBypassMinRequests: envNumber(env.ABUSE_AD_BYPASS_MIN_REQUESTS),
|
|
14734
14774
|
abuseAdBypassRatio: envNumber(env.ABUSE_AD_BYPASS_RATIO),
|
|
@@ -14745,10 +14785,12 @@ function loadConfig(env = process.env) {
|
|
|
14745
14785
|
guestIpHashRetentionDays: envStrictInt("GUEST_IP_HASH_RETENTION_DAYS", env.GUEST_IP_HASH_RETENTION_DAYS),
|
|
14746
14786
|
returnBonusDay2Credits: envStrictInt("RETURN_BONUS_DAY2_CREDITS", env.RETURN_BONUS_DAY2_CREDITS),
|
|
14747
14787
|
returnBonusDay7Credits: envStrictInt("RETURN_BONUS_DAY7_CREDITS", env.RETURN_BONUS_DAY7_CREDITS),
|
|
14788
|
+
returnNudgeEmailEnabled: envBoolean(env.RETURN_NUDGE_EMAIL_ENABLED),
|
|
14748
14789
|
referralBonusCreditsPerDay: envStrictInt("REFERRAL_BONUS_CREDITS_PER_DAY", env.REFERRAL_BONUS_CREDITS_PER_DAY),
|
|
14749
14790
|
referralBonusDays: envStrictInt("REFERRAL_BONUS_DAYS", env.REFERRAL_BONUS_DAYS),
|
|
14750
14791
|
referralMaxActivePerReferrer: envStrictInt("REFERRAL_MAX_ACTIVE_PER_REFERRER", env.REFERRAL_MAX_ACTIVE_PER_REFERRER),
|
|
14751
|
-
referralGithubMinAgeDays: envStrictInt("REFERRAL_GITHUB_MIN_AGE_DAYS", env.REFERRAL_GITHUB_MIN_AGE_DAYS)
|
|
14792
|
+
referralGithubMinAgeDays: envStrictInt("REFERRAL_GITHUB_MIN_AGE_DAYS", env.REFERRAL_GITHUB_MIN_AGE_DAYS),
|
|
14793
|
+
referralCreditGrantCredits: envStrictInt("REFERRAL_CREDIT_GRANT_CREDITS", env.REFERRAL_CREDIT_GRANT_CREDITS)
|
|
14752
14794
|
});
|
|
14753
14795
|
}
|
|
14754
14796
|
// ../shared/src/credits.ts
|
|
@@ -14759,6 +14801,43 @@ function usdToCredits(usd) {
|
|
|
14759
14801
|
function nextUtcMidnight(now) {
|
|
14760
14802
|
return new Date(Date.UTC(now.getUTCFullYear(), now.getUTCMonth(), now.getUTCDate() + 1));
|
|
14761
14803
|
}
|
|
14804
|
+
function formatRefillLocal(date5, timeZone) {
|
|
14805
|
+
const opts = {
|
|
14806
|
+
hour: "numeric",
|
|
14807
|
+
minute: "2-digit",
|
|
14808
|
+
hour12: true,
|
|
14809
|
+
timeZoneName: "short"
|
|
14810
|
+
};
|
|
14811
|
+
if (timeZone)
|
|
14812
|
+
opts.timeZone = timeZone;
|
|
14813
|
+
return new Intl.DateTimeFormat("en-US", opts).format(date5);
|
|
14814
|
+
}
|
|
14815
|
+
function formatVisibleAllowanceLine(input) {
|
|
14816
|
+
const left = input.remaining.toLocaleString("en-US");
|
|
14817
|
+
if (input.guest)
|
|
14818
|
+
return `${left} credits left · sign in for 15,000 a day`;
|
|
14819
|
+
if (input.refillAt && !Number.isNaN(input.refillAt.getTime())) {
|
|
14820
|
+
return `${left} credits left today · resets ${formatRefillLocal(input.refillAt, input.timeZone)}`;
|
|
14821
|
+
}
|
|
14822
|
+
return `${left} credits left today`;
|
|
14823
|
+
}
|
|
14824
|
+
function formatVisibleAllowanceStatus(input) {
|
|
14825
|
+
const left = input.remaining.toLocaleString("en-US");
|
|
14826
|
+
const bought = typeof input.bonusCredits === "number" && input.bonusCredits > 0 ? ` + ${input.bonusCredits.toLocaleString("en-US")}` : "";
|
|
14827
|
+
if (input.guest)
|
|
14828
|
+
return `${left}${bought} credits left`;
|
|
14829
|
+
if (input.refillAt && !Number.isNaN(input.refillAt.getTime())) {
|
|
14830
|
+
return `${left}${bought} credits left today · resets ${formatRefillLocal(input.refillAt, input.timeZone)}`;
|
|
14831
|
+
}
|
|
14832
|
+
return `${left}${bought} credits left today`;
|
|
14833
|
+
}
|
|
14834
|
+
function formatLowAllowanceWarning(input) {
|
|
14835
|
+
const left = input.remaining.toLocaleString("en-US");
|
|
14836
|
+
if (input.guest)
|
|
14837
|
+
return `Low on credits: ${left} left${input.suffix ?? ""}`;
|
|
14838
|
+
const reset = input.refillAt && !Number.isNaN(input.refillAt.getTime()) ? ` · resets ${formatRefillLocal(input.refillAt, input.timeZone)}` : "";
|
|
14839
|
+
return `Low on credits: ${left} left today${reset}${input.suffix ?? ""}`;
|
|
14840
|
+
}
|
|
14762
14841
|
function formatRefillEt(date5, timeZone = "America/New_York") {
|
|
14763
14842
|
const formatted = new Intl.DateTimeFormat("en-US", {
|
|
14764
14843
|
timeZone,
|
|
@@ -14786,11 +14865,13 @@ var LIMIT_NOTICE_LABELS = {
|
|
|
14786
14865
|
};
|
|
14787
14866
|
var EXTRA_CREDITS_URL = "freepi.ai/extra-credits";
|
|
14788
14867
|
var LIMIT_NOTICE_LOCAL_TIME_AFTER_SECONDS = 90 * 60;
|
|
14868
|
+
var REFILL_CLOCK_LIMITS = new Set(["daily_cap", "global_cap", "queue_wait"]);
|
|
14789
14869
|
function formatLimitNotice(fields, opts = {}) {
|
|
14790
14870
|
const slow = fields.lane === "slow" && fields.limit === "hourly_ceiling" && fields.hourly_ceiling != null;
|
|
14791
14871
|
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, " ");
|
|
14792
14872
|
const parts = [label];
|
|
14793
14873
|
const retry = fields.retry_after_seconds;
|
|
14874
|
+
const waitBits = [];
|
|
14794
14875
|
if (retry != null && retry > 0) {
|
|
14795
14876
|
const useClock = Boolean(opts.localTime) && Boolean(fields.clears_at) && retry >= LIMIT_NOTICE_LOCAL_TIME_AFTER_SECONDS;
|
|
14796
14877
|
if (useClock) {
|
|
@@ -14799,13 +14880,21 @@ function formatLimitNotice(fields, opts = {}) {
|
|
|
14799
14880
|
hour: "numeric",
|
|
14800
14881
|
minute: "2-digit"
|
|
14801
14882
|
}).format(new Date(fields.clears_at));
|
|
14802
|
-
|
|
14883
|
+
waitBits.push(`back at ${formatted}`);
|
|
14803
14884
|
} else {
|
|
14804
|
-
|
|
14885
|
+
waitBits.push(`back in ${Math.max(1, Math.ceil(retry / 60))} min`);
|
|
14805
14886
|
}
|
|
14806
14887
|
}
|
|
14888
|
+
if (opts.copyStyle === "treatment" && REFILL_CLOCK_LIMITS.has(fields.limit) && fields.clears_at) {
|
|
14889
|
+
const clears = new Date(fields.clears_at);
|
|
14890
|
+
if (!Number.isNaN(clears.getTime())) {
|
|
14891
|
+
waitBits.push(`resets ${formatRefillEt(clears)}`);
|
|
14892
|
+
}
|
|
14893
|
+
}
|
|
14894
|
+
if (waitBits.length > 0)
|
|
14895
|
+
parts.push(waitBits.join(" · "));
|
|
14807
14896
|
if (slow) {
|
|
14808
|
-
const wait =
|
|
14897
|
+
const wait = waitBits[0] ? ` ${waitBits[0][0].toUpperCase()}${waitBits[0].slice(1)} — or get` : " Get";
|
|
14809
14898
|
return `${label}${wait} credits at ${EXTRA_CREDITS_URL} to keep going at full speed.`;
|
|
14810
14899
|
}
|
|
14811
14900
|
parts.push(`${fields.remaining_credits_today.toLocaleString("en-US")} credits left today`);
|
|
@@ -14940,7 +15029,7 @@ var HiveClassifyResponseSchema = exports_external.object({
|
|
|
14940
15029
|
// package.json
|
|
14941
15030
|
var package_default = {
|
|
14942
15031
|
name: "free-pi-cli",
|
|
14943
|
-
version: "0.
|
|
15032
|
+
version: "0.6.1",
|
|
14944
15033
|
description: "Free, ad-supported distribution of the pi coding agent. Inference paid for by ads.",
|
|
14945
15034
|
license: "MIT",
|
|
14946
15035
|
type: "module",
|
|
@@ -14981,6 +15070,8 @@ free-pi — free AI coding in your terminal; ads pay for the inference.
|
|
|
14981
15070
|
Usage:
|
|
14982
15071
|
free-pi-cli start the coding agent (first run: a short guest trial, then GitHub)
|
|
14983
15072
|
free-pi-cli --session <id> resume a previous session
|
|
15073
|
+
free-pi-cli --continue resume the last session in this folder
|
|
15074
|
+
free-pi-cli --new start a new session
|
|
14984
15075
|
free-pi-cli --ref <code> first-run referral code (from freepi.ai/?ref=)
|
|
14985
15076
|
free-pi-cli --src <channel> first-run campaign attribution (from freepi.ai/?src=)
|
|
14986
15077
|
free-pi-cli logout remove the stored sign-in token; the next run signs in again
|
|
@@ -15016,6 +15107,10 @@ function parseCliArgs(argv) {
|
|
|
15016
15107
|
let i = 0;
|
|
15017
15108
|
while (i < argv.length) {
|
|
15018
15109
|
const arg = argv[i];
|
|
15110
|
+
if (arg === "--continue" || arg === "--resume" || arg === "--new") {
|
|
15111
|
+
i += 1;
|
|
15112
|
+
continue;
|
|
15113
|
+
}
|
|
15019
15114
|
if (arg === "--session" || arg === "--ref" || arg === "--src") {
|
|
15020
15115
|
i += 1;
|
|
15021
15116
|
if (argv[i] && !String(argv[i]).startsWith("--"))
|
|
@@ -15326,7 +15421,11 @@ async function fetchMe(deps) {
|
|
|
15326
15421
|
const res = await authedFetch(deps, "/me");
|
|
15327
15422
|
if (!res.ok)
|
|
15328
15423
|
return;
|
|
15329
|
-
|
|
15424
|
+
const me = await res.json();
|
|
15425
|
+
try {
|
|
15426
|
+
deps.onMe?.(me);
|
|
15427
|
+
} catch {}
|
|
15428
|
+
return me;
|
|
15330
15429
|
} catch {
|
|
15331
15430
|
return;
|
|
15332
15431
|
}
|
|
@@ -15494,7 +15593,7 @@ function mapErrorCode(code) {
|
|
|
15494
15593
|
case "concurrent":
|
|
15495
15594
|
return "only one request at a time on the free tier — wait for the current one to finish";
|
|
15496
15595
|
case "concurrent_session":
|
|
15497
|
-
return "
|
|
15596
|
+
return "this account is already at its session limit — run /close-other-session or wait for a slot";
|
|
15498
15597
|
case "context_ceiling":
|
|
15499
15598
|
return "this conversation is too large for free-pi's context limit";
|
|
15500
15599
|
case "upstream_error":
|
|
@@ -15506,7 +15605,7 @@ function mapErrorCode(code) {
|
|
|
15506
15605
|
case "upgrade_required":
|
|
15507
15606
|
return "your free-pi-cli is out of date — run `npx free-pi-cli@latest`";
|
|
15508
15607
|
case "hourly_ceiling":
|
|
15509
|
-
return "
|
|
15608
|
+
return "daily allowance still applies — it refills at midnight UTC";
|
|
15510
15609
|
case "account_review":
|
|
15511
15610
|
return "this account is under review — contact support";
|
|
15512
15611
|
case "ads_required":
|
|
@@ -15575,9 +15674,23 @@ async function renderMeter(deps, ctx) {
|
|
|
15575
15674
|
if (!Number.isNaN(refill.getTime()) && me.lane !== "wait") {
|
|
15576
15675
|
const left = me.remaining_credits_today;
|
|
15577
15676
|
const bought = typeof me.bonus_credits === "number" && me.bonus_credits > 0 ? ` + ${me.bonus_credits.toLocaleString("en-US")}` : "";
|
|
15578
|
-
|
|
15677
|
+
const refill2 = new Date(me.refill_at);
|
|
15678
|
+
const treatment = me.visible_allowance === true;
|
|
15679
|
+
const status = treatment ? formatVisibleAllowanceStatus({
|
|
15680
|
+
remaining: left,
|
|
15681
|
+
bonusCredits: typeof me.bonus_credits === "number" ? me.bonus_credits : undefined,
|
|
15682
|
+
refillAt: refill2,
|
|
15683
|
+
guest: me.guest === true
|
|
15684
|
+
}) : `${left.toLocaleString("en-US")}${bought} credits`;
|
|
15685
|
+
ctx.ui.setStatus(METER_STATUS_KEY, theme.fg("muted", status));
|
|
15579
15686
|
const low = left > 0 && left <= me.allowance_credits * LOW_CREDIT_FRACTION;
|
|
15580
|
-
|
|
15687
|
+
const warning = treatment ? formatLowAllowanceWarning({
|
|
15688
|
+
remaining: left,
|
|
15689
|
+
refillAt: refill2,
|
|
15690
|
+
guest: me.guest === true,
|
|
15691
|
+
suffix: buyMore(me.buy_url)
|
|
15692
|
+
}) : `Low on credits: ${left.toLocaleString("en-US")} left today${buyMore(me.buy_url)}`;
|
|
15693
|
+
ctx.ui.setWidget(METER_WIDGET_ID, low ? [renderWarningLine(warning, theme)] : undefined);
|
|
15581
15694
|
return;
|
|
15582
15695
|
}
|
|
15583
15696
|
}
|
|
@@ -15598,7 +15711,8 @@ function createAdsExtension(opts) {
|
|
|
15598
15711
|
getToken: opts.getToken,
|
|
15599
15712
|
sessionId: opts.sessionId,
|
|
15600
15713
|
fetchImpl: opts.fetchImpl,
|
|
15601
|
-
getGravityContext: opts.getGravityContext
|
|
15714
|
+
getGravityContext: opts.getGravityContext,
|
|
15715
|
+
onMe: opts.onMe
|
|
15602
15716
|
};
|
|
15603
15717
|
const config3 = loadConfig();
|
|
15604
15718
|
return {
|
|
@@ -15651,7 +15765,7 @@ function createAdsExtension(opts) {
|
|
|
15651
15765
|
}
|
|
15652
15766
|
|
|
15653
15767
|
// src/pi-launch.ts
|
|
15654
|
-
import { dirname as
|
|
15768
|
+
import { dirname as dirname8, join as join10, resolve as resolve3 } from "node:path";
|
|
15655
15769
|
import { fileURLToPath as fileURLToPath4 } from "node:url";
|
|
15656
15770
|
|
|
15657
15771
|
// src/provider.ts
|
|
@@ -15679,7 +15793,8 @@ function buildProviderConfig(baseUrl, jwt2, sessionId, models, name = PROVIDER_N
|
|
|
15679
15793
|
models: models.map((m) => ({
|
|
15680
15794
|
id: m.id,
|
|
15681
15795
|
name: m.name,
|
|
15682
|
-
reasoning:
|
|
15796
|
+
reasoning: true,
|
|
15797
|
+
compat: { supportsDeveloperRole: false },
|
|
15683
15798
|
input: ["text"],
|
|
15684
15799
|
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
|
|
15685
15800
|
contextWindow: 1048576,
|
|
@@ -20318,7 +20433,7 @@ at first run, before the GitHub sign-in.
|
|
|
20318
20433
|
Each user gets a free allowance every day. The allowance can change with
|
|
20319
20434
|
supply and demand. \`/usage\` shows how much of today's allowance is spent, as
|
|
20320
20435
|
a percentage, with token counts.
|
|
20321
|
-
|
|
20436
|
+
You can run free-pi in more than one terminal. If a previous session is stuck,
|
|
20322
20437
|
\`/close-other-session\` frees it.
|
|
20323
20438
|
|
|
20324
20439
|
## Credits
|
|
@@ -20328,6 +20443,7 @@ first. Purchased credits cover use beyond it and do not expire.
|
|
|
20328
20443
|
## Commands
|
|
20329
20444
|
- \`/usage\` — spend and remaining budget today
|
|
20330
20445
|
- \`/support\` — visit today's advertiser
|
|
20446
|
+
- \`/feedback <text>\` — send feedback to the free-pi team
|
|
20331
20447
|
- \`/tos\` — open the Terms of Service
|
|
20332
20448
|
- \`/privacy-policy\` — open the Privacy Policy
|
|
20333
20449
|
- \`/buy-credits\` — get more usage
|
|
@@ -20763,14 +20879,20 @@ function parseErrorEnvelope(errorMessage) {
|
|
|
20763
20879
|
parsed.lane = "slow";
|
|
20764
20880
|
if (ceiling !== undefined)
|
|
20765
20881
|
parsed.hourly_ceiling = ceiling;
|
|
20882
|
+
if (body.prompt === "github_signin")
|
|
20883
|
+
parsed.prompt = "github_signin";
|
|
20884
|
+
if (body.copy_variant === "control" || body.copy_variant === "treatment") {
|
|
20885
|
+
parsed.copy_variant = body.copy_variant;
|
|
20886
|
+
}
|
|
20766
20887
|
return parsed;
|
|
20767
20888
|
} catch {
|
|
20768
20889
|
return;
|
|
20769
20890
|
}
|
|
20770
20891
|
}
|
|
20771
20892
|
function formatLimitEnvelope(env, opts) {
|
|
20772
|
-
if (env.code === "concurrent_session" || typeof env.remaining_credits_today !== "number")
|
|
20893
|
+
if (env.code === "concurrent_session" || env.prompt === "github_signin" || env.copy_variant === "treatment" || typeof env.remaining_credits_today !== "number") {
|
|
20773
20894
|
return env.message;
|
|
20895
|
+
}
|
|
20774
20896
|
return formatLimitNotice({
|
|
20775
20897
|
limit: env.limit ?? env.code,
|
|
20776
20898
|
retry_after_seconds: env.retry_after_seconds,
|
|
@@ -20779,7 +20901,7 @@ function formatLimitEnvelope(env, opts) {
|
|
|
20779
20901
|
allowance_credits: env.allowance_credits,
|
|
20780
20902
|
lane: env.lane,
|
|
20781
20903
|
hourly_ceiling: env.hourly_ceiling
|
|
20782
|
-
}, { localTime: true, timeZone: opts?.timeZone });
|
|
20904
|
+
}, { localTime: true, timeZone: opts?.timeZone, copyStyle: env.copy_variant });
|
|
20783
20905
|
}
|
|
20784
20906
|
function createErrorNoticeExtension(opts = {}) {
|
|
20785
20907
|
const lastAt = new Map;
|
|
@@ -20853,21 +20975,35 @@ function createToolGuardExtension(opts) {
|
|
|
20853
20975
|
var BUG_USAGE = "Usage: /bug <what went wrong>";
|
|
20854
20976
|
var BUG_DESCRIPTION = "Report a bug to free-pi";
|
|
20855
20977
|
var LOGIN_DESCRIPTION = "Sign in with GitHub to unlock daily credits and a comeback bonus.";
|
|
20978
|
+
var FEEDBACK_USAGE = "Usage: /feedback <what you think>";
|
|
20979
|
+
var FEEDBACK_DESCRIPTION = "Send feedback to the free-pi team";
|
|
20980
|
+
var FEEDBACK_PREFIX = "[feedback] ";
|
|
20981
|
+
var FEEDBACK_MAX = 4000 - FEEDBACK_PREFIX.length;
|
|
20982
|
+
function parseFeedbackArgs(args) {
|
|
20983
|
+
const text = args.trim();
|
|
20984
|
+
if (!text)
|
|
20985
|
+
return { kind: "usage" };
|
|
20986
|
+
if (text.length > FEEDBACK_MAX)
|
|
20987
|
+
return { kind: "too_long" };
|
|
20988
|
+
return { kind: "send", description: FEEDBACK_PREFIX + text };
|
|
20989
|
+
}
|
|
20856
20990
|
function buildBugRequest(description, ctx) {
|
|
20857
20991
|
return { description, session_id: ctx.sessionId, cli_version: CLI_VERSION, model: ctx.model };
|
|
20858
20992
|
}
|
|
20859
|
-
function mapBugFailure(status) {
|
|
20993
|
+
function mapBugFailure(status, kind = "bug") {
|
|
20994
|
+
const fb = kind === "feedback";
|
|
20995
|
+
const what = fb ? "feedback" : "the bug report";
|
|
20860
20996
|
if (status === 401)
|
|
20861
|
-
return
|
|
20997
|
+
return `Couldn't send ${what}: you're signed out. Restart free-pi-cli to sign in again.`;
|
|
20862
20998
|
if (status === 429)
|
|
20863
|
-
return
|
|
20999
|
+
return `Couldn't send ${what}: you've reached today's limit of 20 ${fb ? "reports and feedback messages" : "reports"}. Try again tomorrow.`;
|
|
20864
21000
|
if (status === 413)
|
|
20865
|
-
return
|
|
21001
|
+
return `Couldn't send ${what}: it is too large.`;
|
|
20866
21002
|
if (status === 400)
|
|
20867
|
-
return "Couldn't send the bug report: the description is too long (4000 characters max).";
|
|
20868
|
-
return `Couldn't send
|
|
21003
|
+
return fb ? `Couldn't send feedback: it is too long (${FEEDBACK_MAX} characters max).` : "Couldn't send the bug report: the description is too long (4000 characters max).";
|
|
21004
|
+
return `Couldn't send ${what} (the server returned ${status}). Try again in a moment.`;
|
|
20869
21005
|
}
|
|
20870
|
-
async function sendBugReport(opts, body) {
|
|
21006
|
+
async function sendBugReport(opts, body, kind = "bug") {
|
|
20871
21007
|
try {
|
|
20872
21008
|
const res = await (opts.fetchImpl ?? fetch)(`${opts.baseUrl}/bug-reports`, {
|
|
20873
21009
|
method: "POST",
|
|
@@ -20876,13 +21012,13 @@ async function sendBugReport(opts, body) {
|
|
|
20876
21012
|
signal: AbortSignal.timeout(1e4)
|
|
20877
21013
|
});
|
|
20878
21014
|
if (!res.ok)
|
|
20879
|
-
return { ok: false, message: mapBugFailure(res.status) };
|
|
21015
|
+
return { ok: false, message: mapBugFailure(res.status, kind) };
|
|
20880
21016
|
const parsed = BugReportResponseSchema.safeParse(await res.json());
|
|
20881
21017
|
if (!parsed.success)
|
|
20882
|
-
return { ok: false, message: mapBugFailure(res.status) };
|
|
21018
|
+
return { ok: false, message: mapBugFailure(res.status, kind) };
|
|
20883
21019
|
return { ok: true, id: parsed.data.id };
|
|
20884
21020
|
} catch {
|
|
20885
|
-
return { ok: false, message:
|
|
21021
|
+
return { ok: false, message: `Couldn't reach free-pi to send ${kind === "feedback" ? "feedback" : "the bug report"} — check your connection.` };
|
|
20886
21022
|
}
|
|
20887
21023
|
}
|
|
20888
21024
|
function makeBugHandler(mode, opts) {
|
|
@@ -20918,20 +21054,65 @@ function registerBugAutocomplete(pi) {
|
|
|
20918
21054
|
}
|
|
20919
21055
|
|
|
20920
21056
|
// src/footer-align.ts
|
|
20921
|
-
|
|
20922
|
-
var
|
|
21057
|
+
import { truncateToWidth as truncateToWidth4, visibleWidth as visibleWidth2 } from "@earendil-works/pi-tui";
|
|
21058
|
+
var MIN_GAP = 2;
|
|
21059
|
+
var MIN_CWD = 8;
|
|
21060
|
+
var LEADING_SGR = /^(?:\x1b\[[0-9;]*m)+/;
|
|
21061
|
+
function mergeStatusIntoCwdLine(lines, width) {
|
|
21062
|
+
if (lines.length < 3)
|
|
21063
|
+
return lines;
|
|
21064
|
+
const [cwd, stats] = lines;
|
|
21065
|
+
const status = lines[lines.length - 1];
|
|
21066
|
+
const statusW = visibleWidth2(status);
|
|
21067
|
+
let left = cwd;
|
|
21068
|
+
if (visibleWidth2(left) + MIN_GAP + statusW > width) {
|
|
21069
|
+
const room = width - MIN_GAP - statusW;
|
|
21070
|
+
if (room < MIN_CWD)
|
|
21071
|
+
return lines;
|
|
21072
|
+
const style = LEADING_SGR.exec(cwd)?.[0] ?? "";
|
|
21073
|
+
left = truncateToWidth4(cwd, room, style ? `${style}...\x1B[0m` : "...");
|
|
21074
|
+
}
|
|
21075
|
+
const gap = " ".repeat(Math.max(MIN_GAP, width - visibleWidth2(left) - statusW));
|
|
21076
|
+
return [left + gap + status, stats, ...lines.slice(2, -1)];
|
|
21077
|
+
}
|
|
20923
21078
|
function installFooterRightAlign(mode) {
|
|
20924
21079
|
const footer = mode.footer;
|
|
20925
21080
|
const render = footer.render.bind(footer);
|
|
20926
|
-
footer.render = (w) =>
|
|
20927
|
-
|
|
20928
|
-
|
|
20929
|
-
|
|
20930
|
-
|
|
20931
|
-
|
|
20932
|
-
|
|
20933
|
-
|
|
20934
|
-
|
|
21081
|
+
footer.render = (w) => mergeStatusIntoCwdLine(render(w), w);
|
|
21082
|
+
}
|
|
21083
|
+
|
|
21084
|
+
// src/guest-handoff.ts
|
|
21085
|
+
var LOGIN_VALUE_LINE = "sign in free with GitHub: full daily allowance, FREE abliterated DeepSeek, history saved";
|
|
21086
|
+
var GUEST_NEAR_WALL_LINE = `A few guest requests left. ${LOGIN_VALUE_LINE}`;
|
|
21087
|
+
var SIGNED_IN_CONTINUE_LINE = "Signed in. Your conversation continues — full daily allowance is unlocked.";
|
|
21088
|
+
var HEADLESS_FALLBACK_LINE = "No browser or clipboard here — open the URL and enter the code above.";
|
|
21089
|
+
function isHandoffTreatment(variant) {
|
|
21090
|
+
return variant === "treatment";
|
|
21091
|
+
}
|
|
21092
|
+
var GUEST_NOTES = [
|
|
21093
|
+
"Signed-in users get the Defense models in /model, for security work on code and systems you own. Sign in free with /login.",
|
|
21094
|
+
"With the Defense models you can audit your own repo for vulnerabilities and get fixes, without the usual refusals. /login to unlock.",
|
|
21095
|
+
"Write detection rules and review your server configs with the Defense models. Sign in free with /login.",
|
|
21096
|
+
"More on security work with free-pi: https://freepi.ai/cybersecurity. /login to unlock the Defense models."
|
|
21097
|
+
];
|
|
21098
|
+
var GUEST_NOTE_EVERY_TURNS = 3;
|
|
21099
|
+
function createGuestNotesExtension(opts) {
|
|
21100
|
+
let turns = 0;
|
|
21101
|
+
let shown = 0;
|
|
21102
|
+
return {
|
|
21103
|
+
name: "free-pi-guest-notes",
|
|
21104
|
+
factory(pi) {
|
|
21105
|
+
pi.on("agent_end", (event, ctx) => {
|
|
21106
|
+
if (!opts.isGuest() || shown >= GUEST_NOTES.length)
|
|
21107
|
+
return;
|
|
21108
|
+
const last = [...event.messages].reverse().find((m) => m.role === "assistant");
|
|
21109
|
+
if (last?.stopReason !== "stop")
|
|
21110
|
+
return;
|
|
21111
|
+
if (++turns % GUEST_NOTE_EVERY_TURNS !== 0)
|
|
21112
|
+
return;
|
|
21113
|
+
ctx.ui.notify(GUEST_NOTES[shown++], "info");
|
|
21114
|
+
});
|
|
21115
|
+
}
|
|
20935
21116
|
};
|
|
20936
21117
|
}
|
|
20937
21118
|
|
|
@@ -20977,6 +21158,25 @@ function mergeAttribution(a, b) {
|
|
|
20977
21158
|
|
|
20978
21159
|
// src/changelog.ts
|
|
20979
21160
|
var CHANGELOG_HIGHLIGHTS = [
|
|
21161
|
+
{
|
|
21162
|
+
version: "0.6.1",
|
|
21163
|
+
highlights: [
|
|
21164
|
+
"/model shows the Defense models by their real names.",
|
|
21165
|
+
"Five more Defense models: Abliterated Large V2, Qwen 3.6 Plus Uncensored, Gemma 4 Uncensored, GLM 4.7 Flash Heretic and Uncensored 1.2.",
|
|
21166
|
+
"The footer is two lines: credits left now sit next to the folder name.",
|
|
21167
|
+
"The welcome text no longer says you can only run one session."
|
|
21168
|
+
]
|
|
21169
|
+
},
|
|
21170
|
+
{
|
|
21171
|
+
version: "0.6.0",
|
|
21172
|
+
highlights: [
|
|
21173
|
+
"Send feedback with /feedback <what you think>.",
|
|
21174
|
+
"Turn thinking on with the thinking-level control (Shift+Tab). It starts off.",
|
|
21175
|
+
"Run free-pi in more than one terminal at once.",
|
|
21176
|
+
"Pick up your last session in this folder when you start free-pi (some users).",
|
|
21177
|
+
"The Defense models now show their real model names."
|
|
21178
|
+
]
|
|
21179
|
+
},
|
|
20980
21180
|
{
|
|
20981
21181
|
version: "0.5.3",
|
|
20982
21182
|
highlights: [
|
|
@@ -20986,7 +21186,7 @@ var CHANGELOG_HIGHLIGHTS = [
|
|
|
20986
21186
|
},
|
|
20987
21187
|
{
|
|
20988
21188
|
version: "0.5.2",
|
|
20989
|
-
highlights: ["/model now lists Defense
|
|
21189
|
+
highlights: ["/model now lists the Defense models right under Fusion."]
|
|
20990
21190
|
},
|
|
20991
21191
|
{
|
|
20992
21192
|
version: "0.5.1",
|
|
@@ -21267,7 +21467,8 @@ async function startGuestSession(baseUrl, consentVersion, attribution = {}) {
|
|
|
21267
21467
|
return {
|
|
21268
21468
|
token: parsed.token,
|
|
21269
21469
|
allowance_usd: parsed.allowance_usd,
|
|
21270
|
-
remaining_usd: parsed.remaining_usd
|
|
21470
|
+
remaining_usd: parsed.remaining_usd,
|
|
21471
|
+
guestHandoffVariant: parsed.experiments?.guest_handoff
|
|
21271
21472
|
};
|
|
21272
21473
|
}
|
|
21273
21474
|
async function reportFunnelEvent(baseUrl, event, opts = {}) {
|
|
@@ -21298,7 +21499,15 @@ async function pollForToken(baseUrl, sessionId, interval, sleep, opts = {}) {
|
|
|
21298
21499
|
}
|
|
21299
21500
|
let res;
|
|
21300
21501
|
try {
|
|
21301
|
-
|
|
21502
|
+
const headers = { "content-type": "application/json" };
|
|
21503
|
+
if (opts.jwt)
|
|
21504
|
+
headers.authorization = `Bearer ${opts.jwt}`;
|
|
21505
|
+
const doFetch = opts.fetchImpl ?? fetch;
|
|
21506
|
+
res = await doFetch(new URL("/auth/token", baseUrl), {
|
|
21507
|
+
method: "POST",
|
|
21508
|
+
headers,
|
|
21509
|
+
body: JSON.stringify({ session_id: sessionId })
|
|
21510
|
+
});
|
|
21302
21511
|
consecutiveNetworkFailures = 0;
|
|
21303
21512
|
} catch (err) {
|
|
21304
21513
|
consecutiveNetworkFailures++;
|
|
@@ -21369,8 +21578,11 @@ async function submitUserConsent(baseUrl, jwt2, consentVersion) {
|
|
|
21369
21578
|
|
|
21370
21579
|
// src/clipboard.ts
|
|
21371
21580
|
import { spawn as spawn2 } from "node:child_process";
|
|
21372
|
-
|
|
21581
|
+
import { existsSync as existsSync2 } from "node:fs";
|
|
21582
|
+
function copyToClipboard(text, platform = process.platform, spawnImpl = spawn2, env = process.env) {
|
|
21373
21583
|
try {
|
|
21584
|
+
if (!clipboardAvailable(platform, env))
|
|
21585
|
+
return false;
|
|
21374
21586
|
const cmd = clipboardCommand(platform);
|
|
21375
21587
|
if (!cmd)
|
|
21376
21588
|
return false;
|
|
@@ -21383,6 +21595,24 @@ function copyToClipboard(text, platform = process.platform, spawnImpl = spawn2)
|
|
|
21383
21595
|
return false;
|
|
21384
21596
|
}
|
|
21385
21597
|
}
|
|
21598
|
+
function clipboardAvailable(platform = process.platform, env = process.env) {
|
|
21599
|
+
if (platform === "darwin" || platform === "win32")
|
|
21600
|
+
return commandOnPath(clipboardCommand(platform)?.command, env);
|
|
21601
|
+
if (platform !== "linux")
|
|
21602
|
+
return false;
|
|
21603
|
+
if (!env.DISPLAY && !env.WAYLAND_DISPLAY)
|
|
21604
|
+
return false;
|
|
21605
|
+
const cmd = clipboardCommand(platform);
|
|
21606
|
+
return commandOnPath(cmd?.command, env);
|
|
21607
|
+
}
|
|
21608
|
+
function commandOnPath(command, env) {
|
|
21609
|
+
if (!command)
|
|
21610
|
+
return false;
|
|
21611
|
+
if (command.includes("/"))
|
|
21612
|
+
return existsSync2(command);
|
|
21613
|
+
const path = env.PATH ?? "";
|
|
21614
|
+
return path.split(":").some((dir) => dir.length > 0 && existsSync2(`${dir}/${command}`));
|
|
21615
|
+
}
|
|
21386
21616
|
function clipboardCommand(platform) {
|
|
21387
21617
|
if (platform === "darwin")
|
|
21388
21618
|
return { command: "pbcopy", args: [] };
|
|
@@ -21444,7 +21674,7 @@ function allowanceIsLow(remaining, allowance) {
|
|
|
21444
21674
|
return false;
|
|
21445
21675
|
return remaining * LOW_ALLOWANCE_DENOMINATOR <= allowance * LOW_ALLOWANCE_NUMERATOR;
|
|
21446
21676
|
}
|
|
21447
|
-
function
|
|
21677
|
+
function formatRefillLocal2(refillAt, timeZone) {
|
|
21448
21678
|
const opts = {
|
|
21449
21679
|
hour: "numeric",
|
|
21450
21680
|
minute: "2-digit",
|
|
@@ -21464,7 +21694,7 @@ function refillAtFromIso(iso, now = new Date) {
|
|
|
21464
21694
|
return nextUtcMidnight(now);
|
|
21465
21695
|
}
|
|
21466
21696
|
function formatRefillCard(input) {
|
|
21467
|
-
const refill =
|
|
21697
|
+
const refill = formatRefillLocal2(input.refillAt, input.timeZone);
|
|
21468
21698
|
const lines = [
|
|
21469
21699
|
`${input.allowance.toLocaleString("en-US")} free credits refill at ${refill}.`
|
|
21470
21700
|
];
|
|
@@ -21493,6 +21723,7 @@ async function fetchRefillStats(baseUrl, jwt2, fetchImpl = fetch) {
|
|
|
21493
21723
|
allowanceCredits: body.allowance_credits,
|
|
21494
21724
|
refillAt: typeof body.refill_at === "string" ? body.refill_at : undefined,
|
|
21495
21725
|
showCard: body.return_bonus?.show_card === true,
|
|
21726
|
+
creditsBack: body.return_bonus?.credits_back === true,
|
|
21496
21727
|
guest: body.guest === true
|
|
21497
21728
|
};
|
|
21498
21729
|
} catch {
|
|
@@ -21523,6 +21754,12 @@ function sessionFromStats(stats) {
|
|
|
21523
21754
|
shown: false
|
|
21524
21755
|
};
|
|
21525
21756
|
}
|
|
21757
|
+
function formatCreditsBackNudge(stats, timeZone) {
|
|
21758
|
+
if (stats.guest || !stats.creditsBack)
|
|
21759
|
+
return;
|
|
21760
|
+
const refill = formatRefillLocal2(refillAtFromIso(stats.refillAt), timeZone);
|
|
21761
|
+
return `Your credits are back. ${stats.allowanceCredits.toLocaleString("en-US")} free credits refill at ${refill}.`;
|
|
21762
|
+
}
|
|
21526
21763
|
function formatLowAllowanceCard(session, remaining, timeZone) {
|
|
21527
21764
|
if (!session.show || session.shown || session.allowance === undefined)
|
|
21528
21765
|
return;
|
|
@@ -21551,14 +21788,48 @@ function formatExitRefillCard(session, end, timeZone) {
|
|
|
21551
21788
|
});
|
|
21552
21789
|
}
|
|
21553
21790
|
|
|
21791
|
+
// src/pending-login.ts
|
|
21792
|
+
import { mkdir as mkdir4, readFile as readFile3, rm as rm4, writeFile as writeFile3 } from "node:fs/promises";
|
|
21793
|
+
import { dirname as dirname5, join as join6 } from "node:path";
|
|
21794
|
+
var FINISH_SIGNIN_LINE = "one step to finish signing in";
|
|
21795
|
+
function pendingLoginPath(credentialsPath) {
|
|
21796
|
+
return join6(dirname5(credentialsPath), "pending-login.json");
|
|
21797
|
+
}
|
|
21798
|
+
async function loadPendingLogin(credentialsPath) {
|
|
21799
|
+
try {
|
|
21800
|
+
const raw = await readFile3(pendingLoginPath(credentialsPath), "utf8");
|
|
21801
|
+
const parsed = JSON.parse(raw);
|
|
21802
|
+
if (typeof parsed.session_id !== "string" || typeof parsed.user_code !== "string" || typeof parsed.verification_uri !== "string" || typeof parsed.expires_at !== "number") {
|
|
21803
|
+
return null;
|
|
21804
|
+
}
|
|
21805
|
+
return {
|
|
21806
|
+
session_id: parsed.session_id,
|
|
21807
|
+
user_code: parsed.user_code,
|
|
21808
|
+
verification_uri: parsed.verification_uri,
|
|
21809
|
+
expires_at: parsed.expires_at
|
|
21810
|
+
};
|
|
21811
|
+
} catch {
|
|
21812
|
+
return null;
|
|
21813
|
+
}
|
|
21814
|
+
}
|
|
21815
|
+
async function savePendingLogin(credentialsPath, pending) {
|
|
21816
|
+
const path = pendingLoginPath(credentialsPath);
|
|
21817
|
+
await mkdir4(dirname5(path), { recursive: true });
|
|
21818
|
+
await writeFile3(path, JSON.stringify(pending), { mode: 384 });
|
|
21819
|
+
}
|
|
21820
|
+
async function clearPendingLogin(credentialsPath) {
|
|
21821
|
+
await rm4(pendingLoginPath(credentialsPath), { force: true });
|
|
21822
|
+
}
|
|
21823
|
+
|
|
21554
21824
|
// src/login-ux.ts
|
|
21555
21825
|
var LOGIN_REWARD_LINE = "Sign in with GitHub to get 15,000 free credits a day";
|
|
21556
21826
|
var LOGIN_WAITING_LINE = "Waiting for GitHub…";
|
|
21557
21827
|
var LOGIN_TIMEOUT_LINE = "GitHub sign-in timed out. Run /login to try again.";
|
|
21558
|
-
function formatDeviceCodePrompt(device) {
|
|
21828
|
+
function formatDeviceCodePrompt(device, variant) {
|
|
21829
|
+
const value = isHandoffTreatment(variant) ? LOGIN_VALUE_LINE : LOGIN_REWARD_LINE;
|
|
21559
21830
|
return [
|
|
21560
21831
|
"",
|
|
21561
|
-
|
|
21832
|
+
value,
|
|
21562
21833
|
LOGIN_BONUS_UNLOCK_LINE,
|
|
21563
21834
|
"",
|
|
21564
21835
|
` Open ${device.verification_uri}`,
|
|
@@ -21569,7 +21840,15 @@ function formatDeviceCodePrompt(device) {
|
|
|
21569
21840
|
}
|
|
21570
21841
|
async function runDeviceLogin(deps, attribution = {}) {
|
|
21571
21842
|
const device = await startDeviceFlow(deps.baseUrl);
|
|
21572
|
-
deps.log(formatDeviceCodePrompt(device));
|
|
21843
|
+
deps.log(formatDeviceCodePrompt(device, deps.variant));
|
|
21844
|
+
if (deps.credentialsPath) {
|
|
21845
|
+
await savePendingLogin(deps.credentialsPath, {
|
|
21846
|
+
session_id: device.session_id,
|
|
21847
|
+
user_code: device.user_code,
|
|
21848
|
+
verification_uri: device.verification_uri,
|
|
21849
|
+
expires_at: (deps.now ?? Date.now)() + (device.expires_in ?? 900) * 1000
|
|
21850
|
+
});
|
|
21851
|
+
}
|
|
21573
21852
|
const copied = (deps.copy ?? copyToClipboard)(device.user_code);
|
|
21574
21853
|
if (copied)
|
|
21575
21854
|
deps.log(` (code copied to clipboard)
|
|
@@ -21587,12 +21866,66 @@ async function runDeviceLogin(deps, attribution = {}) {
|
|
|
21587
21866
|
jwt: deps.jwt,
|
|
21588
21867
|
fetchImpl: deps.fetchImpl
|
|
21589
21868
|
});
|
|
21869
|
+
} else if (!copied) {
|
|
21870
|
+
deps.log(HEADLESS_FALLBACK_LINE);
|
|
21590
21871
|
}
|
|
21591
21872
|
await submitConsent(deps.baseUrl, device.session_id, CONSENT_VERSION, attribution);
|
|
21592
21873
|
const expiresInMs = (device.expires_in ?? 900) * 1000;
|
|
21593
21874
|
const deadline = (deps.now ?? Date.now)() + expiresInMs;
|
|
21594
|
-
|
|
21595
|
-
|
|
21875
|
+
try {
|
|
21876
|
+
const token = await pollForToken(deps.baseUrl, device.session_id, device.interval, deps.sleep, { deadline, now: deps.now, jwt: deps.jwt, fetchImpl: deps.fetchImpl });
|
|
21877
|
+
if (deps.credentialsPath)
|
|
21878
|
+
await clearPendingLogin(deps.credentialsPath);
|
|
21879
|
+
return token;
|
|
21880
|
+
} catch (err) {
|
|
21881
|
+
throw err;
|
|
21882
|
+
}
|
|
21883
|
+
}
|
|
21884
|
+
|
|
21885
|
+
// src/allowance-counter.ts
|
|
21886
|
+
var cached2;
|
|
21887
|
+
function updateAllowanceCache(input) {
|
|
21888
|
+
if (typeof input.remaining_credits_today !== "number" || typeof input.allowance_credits !== "number") {
|
|
21889
|
+
return cached2;
|
|
21890
|
+
}
|
|
21891
|
+
const refill = typeof input.refill_at === "string" && !Number.isNaN(new Date(input.refill_at).getTime()) ? new Date(input.refill_at) : undefined;
|
|
21892
|
+
cached2 = {
|
|
21893
|
+
remaining: input.remaining_credits_today,
|
|
21894
|
+
allowance: input.allowance_credits,
|
|
21895
|
+
refillAt: refill,
|
|
21896
|
+
bonusCredits: typeof input.bonus_credits === "number" ? input.bonus_credits : undefined,
|
|
21897
|
+
guest: input.guest === true,
|
|
21898
|
+
visible: input.visible_allowance === true
|
|
21899
|
+
};
|
|
21900
|
+
return cached2;
|
|
21901
|
+
}
|
|
21902
|
+
function allowanceLine(snapshot = cached2, timeZone) {
|
|
21903
|
+
if (!snapshot?.visible)
|
|
21904
|
+
return;
|
|
21905
|
+
return formatVisibleAllowanceLine({
|
|
21906
|
+
remaining: snapshot.remaining,
|
|
21907
|
+
refillAt: snapshot.refillAt,
|
|
21908
|
+
guest: snapshot.guest,
|
|
21909
|
+
timeZone
|
|
21910
|
+
});
|
|
21911
|
+
}
|
|
21912
|
+
function installModelAllowanceHandler(mode, getLine = () => allowanceLine()) {
|
|
21913
|
+
const m = mode;
|
|
21914
|
+
if (typeof m.handleModelCommand !== "function" || typeof m.showStatus !== "function")
|
|
21915
|
+
return false;
|
|
21916
|
+
const original = m.handleModelCommand.bind(mode);
|
|
21917
|
+
m.handleModelCommand = () => {
|
|
21918
|
+
const line = getLine();
|
|
21919
|
+
if (line)
|
|
21920
|
+
m.showStatus(line);
|
|
21921
|
+
return original();
|
|
21922
|
+
};
|
|
21923
|
+
return true;
|
|
21924
|
+
}
|
|
21925
|
+
function notifyAllowance(ctx) {
|
|
21926
|
+
const line = allowanceLine();
|
|
21927
|
+
if (line)
|
|
21928
|
+
ctx.ui.notify(line, "info");
|
|
21596
21929
|
}
|
|
21597
21930
|
|
|
21598
21931
|
// src/commands.ts
|
|
@@ -21615,6 +21948,18 @@ async function closeOtherSession(opts, ctx) {
|
|
|
21615
21948
|
ctx.ui.notify("Couldn't reach free-pi to reset your session — check your connection.", "error");
|
|
21616
21949
|
}
|
|
21617
21950
|
}
|
|
21951
|
+
async function sendFeedback(opts, args, ctx) {
|
|
21952
|
+
const parsed = parseFeedbackArgs(args);
|
|
21953
|
+
if (parsed.kind === "usage")
|
|
21954
|
+
return ctx.ui.notify(FEEDBACK_USAGE, "info");
|
|
21955
|
+
if (parsed.kind === "too_long")
|
|
21956
|
+
return ctx.ui.notify(`Couldn't send feedback: it is too long (${FEEDBACK_MAX} characters max).`, "error");
|
|
21957
|
+
const r = await sendBugReport({ baseUrl: opts.baseUrl, getToken: opts.getToken, fetchImpl: opts.fetchImpl }, buildBugRequest(parsed.description, { sessionId: ctx.sessionManager?.getSessionId(), model: ctx.model?.id }), "feedback");
|
|
21958
|
+
if (r.ok)
|
|
21959
|
+
ctx.ui.notify(`Thanks for the feedback (#${r.id}).`, "info");
|
|
21960
|
+
else
|
|
21961
|
+
ctx.ui.notify(r.message, "error");
|
|
21962
|
+
}
|
|
21618
21963
|
function showWhatsNew(ctx) {
|
|
21619
21964
|
const lines = CHANGELOG_HIGHLIGHTS.slice(0, 3).flatMap((e) => [
|
|
21620
21965
|
`free-pi ${e.version}:`,
|
|
@@ -21679,21 +22024,27 @@ async function openPrivacyPolicy(opts, ctx) {
|
|
|
21679
22024
|
(opts.openBrowserImpl ?? openBrowser)(PRIVACY_URL);
|
|
21680
22025
|
ctx.ui.notify(`Opening the Privacy Policy in your browser: ${PRIVACY_URL}`, "info");
|
|
21681
22026
|
}
|
|
22027
|
+
function resolveHandoffVariant(opts) {
|
|
22028
|
+
return typeof opts.variant === "function" ? opts.variant() : opts.variant;
|
|
22029
|
+
}
|
|
21682
22030
|
async function runLogin(opts, ctx) {
|
|
21683
22031
|
try {
|
|
21684
22032
|
const attribution = opts.agentDir ? await loadAttribution(getAttributionPath(opts.agentDir)) : {};
|
|
22033
|
+
const variant = resolveHandoffVariant(opts);
|
|
21685
22034
|
const token = await runDeviceLogin({
|
|
21686
22035
|
baseUrl: opts.baseUrl,
|
|
21687
22036
|
openBrowser: opts.openBrowserImpl ?? openBrowser,
|
|
21688
22037
|
sleep: (ms) => new Promise((r) => setTimeout(r, ms)),
|
|
21689
22038
|
log: (message) => ctx.ui.notify(message.trim(), "info"),
|
|
21690
22039
|
jwt: await opts.getToken(),
|
|
21691
|
-
fetchImpl: opts.fetchImpl
|
|
22040
|
+
fetchImpl: opts.fetchImpl,
|
|
22041
|
+
variant,
|
|
22042
|
+
credentialsPath: opts.credentialsPath
|
|
21692
22043
|
}, attribution);
|
|
21693
22044
|
if (opts.credentialsPath)
|
|
21694
22045
|
await saveJwt(opts.credentialsPath, token);
|
|
21695
22046
|
opts.onLogin?.(token);
|
|
21696
|
-
ctx.ui.notify("Signed in. Restart free-pi-cli to unlock 15,000 free credits a day and a one-time comeback bonus.", "info");
|
|
22047
|
+
ctx.ui.notify(isHandoffTreatment(variant) ? SIGNED_IN_CONTINUE_LINE : "Signed in. Restart free-pi-cli to unlock 15,000 free credits a day and a one-time comeback bonus.", "info");
|
|
21697
22048
|
} catch (err) {
|
|
21698
22049
|
if (err instanceof LoginTimeoutError) {
|
|
21699
22050
|
ctx.ui.notify(LOGIN_TIMEOUT_LINE, "warning");
|
|
@@ -21807,23 +22158,34 @@ function loadRea(pi, opts, ctx) {
|
|
|
21807
22158
|
}
|
|
21808
22159
|
function createGuestLoginExtension(opts) {
|
|
21809
22160
|
let started = false;
|
|
22161
|
+
let nearWallWarned = false;
|
|
21810
22162
|
const start = (ctx) => {
|
|
21811
22163
|
started = true;
|
|
21812
|
-
|
|
22164
|
+
const variant = resolveHandoffVariant(opts);
|
|
22165
|
+
ctx.ui.notify(isHandoffTreatment(variant) ? LOGIN_VALUE_LINE : LOGIN_REWARD_LINE, "info");
|
|
21813
22166
|
runLogin(opts, ctx);
|
|
21814
22167
|
};
|
|
21815
22168
|
return {
|
|
21816
22169
|
name: "free-pi-guest-login",
|
|
21817
22170
|
factory(pi) {
|
|
21818
22171
|
pi.on("turn_end", (event, ctx) => {
|
|
21819
|
-
if (!opts.isGuest?.()
|
|
22172
|
+
if (!opts.isGuest?.())
|
|
21820
22173
|
return;
|
|
21821
22174
|
const msg = event.message;
|
|
21822
|
-
if (msg?.stopReason
|
|
22175
|
+
if (!started && msg?.stopReason === "error" && parseErrorEnvelope(msg.errorMessage)?.code === "guest_allowance") {
|
|
22176
|
+
start(ctx);
|
|
21823
22177
|
return;
|
|
21824
|
-
|
|
22178
|
+
}
|
|
22179
|
+
if (started || nearWallWarned || !isHandoffTreatment(resolveHandoffVariant(opts)))
|
|
21825
22180
|
return;
|
|
21826
|
-
|
|
22181
|
+
if (msg?.stopReason === "error")
|
|
22182
|
+
return;
|
|
22183
|
+
Promise.resolve(opts.getToken()).then((jwt2) => fetchMe2(opts.baseUrl, jwt2)).then((me) => {
|
|
22184
|
+
if (nearWallWarned || started || !me?.guest_near_wall)
|
|
22185
|
+
return;
|
|
22186
|
+
nearWallWarned = true;
|
|
22187
|
+
ctx.ui.notify(GUEST_NEAR_WALL_LINE, "warning");
|
|
22188
|
+
}).catch(() => {});
|
|
21827
22189
|
});
|
|
21828
22190
|
pi.on("agent_end", (event, ctx) => {
|
|
21829
22191
|
if (!opts.isGuest?.() || started)
|
|
@@ -21857,10 +22219,17 @@ function createFreePiCommandsExtension(opts) {
|
|
|
21857
22219
|
description: "Show credits left today, refill time, and purchased credit.",
|
|
21858
22220
|
handler: (_args, ctx) => showUsage(opts, ctx)
|
|
21859
22221
|
});
|
|
22222
|
+
pi.on("model_select", (_event, ctx) => {
|
|
22223
|
+
notifyAllowance(ctx);
|
|
22224
|
+
});
|
|
21860
22225
|
pi.registerCommand("buy-credits", {
|
|
21861
22226
|
description: "Buy more usage ($5 or $10 packs, card or USDC) — opens the buy page.",
|
|
21862
22227
|
handler: (_args, ctx) => buyCredits(opts, ctx)
|
|
21863
22228
|
});
|
|
22229
|
+
pi.registerCommand("feedback", {
|
|
22230
|
+
description: FEEDBACK_DESCRIPTION,
|
|
22231
|
+
handler: (args, ctx) => sendFeedback(opts, args, ctx)
|
|
22232
|
+
});
|
|
21864
22233
|
pi.registerCommand("support", {
|
|
21865
22234
|
description: "Open today's advertiser in your browser to support free-pi.",
|
|
21866
22235
|
handler: (_args, ctx) => openAdvertiser(opts, ctx)
|
|
@@ -21936,7 +22305,7 @@ function truncate2(text, maxWidth) {
|
|
|
21936
22305
|
return text.slice(0, maxWidth);
|
|
21937
22306
|
return `${text.slice(0, maxWidth - 1)}…`;
|
|
21938
22307
|
}
|
|
21939
|
-
function headerLines(theme, modelName, expanded, width = Number.POSITIVE_INFINITY, updateLatest, guest) {
|
|
22308
|
+
function headerLines(theme, modelName, expanded, width = Number.POSITIVE_INFINITY, updateLatest, guest, allowanceLine2) {
|
|
21940
22309
|
const inner = width - 1;
|
|
21941
22310
|
const fit2 = (plain, styled) => plain.length <= inner ? styled() : theme.fg("dim", truncate2(plain, inner));
|
|
21942
22311
|
const titleRest = ` · ${modelName} · ctrl+o for help`;
|
|
@@ -21952,6 +22321,7 @@ function headerLines(theme, modelName, expanded, width = Number.POSITIVE_INFINIT
|
|
|
21952
22321
|
"",
|
|
21953
22322
|
dim(WELCOME_LINE),
|
|
21954
22323
|
...guest ? [dim(GUEST_LINE)] : [],
|
|
22324
|
+
...allowanceLine2 ? [dim(allowanceLine2)] : [],
|
|
21955
22325
|
"",
|
|
21956
22326
|
dim(CONSENT_LINE_1),
|
|
21957
22327
|
dim(CONSENT_LINE_2),
|
|
@@ -21975,7 +22345,7 @@ function createHeaderExtension(opts) {
|
|
|
21975
22345
|
let expanded = false;
|
|
21976
22346
|
return {
|
|
21977
22347
|
render(width) {
|
|
21978
|
-
return headerLines(theme, opts.modelName, expanded, width, opts.updateLatest, opts.guest);
|
|
22348
|
+
return headerLines(theme, opts.modelName, expanded, width, opts.updateLatest, opts.guest, opts.allowanceLine);
|
|
21979
22349
|
},
|
|
21980
22350
|
invalidate() {},
|
|
21981
22351
|
setExpanded(next) {
|
|
@@ -22199,12 +22569,12 @@ function resolveFreePiScope(modelRuntime, providerName, modelIds, env = process.
|
|
|
22199
22569
|
import { createHash as createHash3 } from "node:crypto";
|
|
22200
22570
|
import { execFileSync } from "node:child_process";
|
|
22201
22571
|
import { lstatSync as lstatSync2, readFileSync as readFileSync3, readlinkSync as readlinkSync2 } from "node:fs";
|
|
22202
|
-
import { join as
|
|
22572
|
+
import { join as join8 } from "node:path";
|
|
22203
22573
|
|
|
22204
22574
|
// src/hive-guard.ts
|
|
22205
|
-
import { existsSync as
|
|
22575
|
+
import { existsSync as existsSync3, lstatSync, readdirSync, readlinkSync, realpathSync } from "node:fs";
|
|
22206
22576
|
import { homedir as homedir3 } from "node:os";
|
|
22207
|
-
import { basename, dirname as
|
|
22577
|
+
import { basename, dirname as dirname6, isAbsolute, join as join7, relative, resolve, sep } from "node:path";
|
|
22208
22578
|
import { fileURLToPath as fileURLToPath3 } from "node:url";
|
|
22209
22579
|
var GUARD_INPUT_LIMIT = 1500;
|
|
22210
22580
|
var CLASSIFY_TIMEOUT_MS = 2500;
|
|
@@ -22216,7 +22586,7 @@ var SAFE_READ_TOOLS = ["read", "grep", "find", "ls"];
|
|
|
22216
22586
|
var ALWAYS_ASK = ["codemode", "tool_search", FUSION_PLAN_TOOL, WEB_SEARCH_TOOL_NAME, DOCS_LOOKUP_TOOL_NAME];
|
|
22217
22587
|
var CLASSIFIED = ["bash", "write", "edit"];
|
|
22218
22588
|
var SECRET_BASENAME = [/^\.env(\..*)?$/, /\.pem$/, /^id_(rsa|dsa|ecdsa|ed25519)/];
|
|
22219
|
-
var SECRET_HOME_DIRS = [".ssh", ".aws",
|
|
22589
|
+
var SECRET_HOME_DIRS = [".ssh", ".aws", join7(".config", "freepi"), ".free-pi"];
|
|
22220
22590
|
var SECRET_IN_TEXT = /(?:^|[\s"'=:/])\.env(?:\.[\w.-]+)?(?=$|[\s"';|&)])|\.pem\b|\bid_(?:rsa|dsa|ecdsa|ed25519)|\.ssh\b|\.aws\b|\.config\/freepi|\.free-pi\b/;
|
|
22221
22591
|
var BASH_DENY = [
|
|
22222
22592
|
[/\brm\s+(?:-\S+\s+)*(?:\/|~|\$HOME)\*?(?:\s|$)/, "destructive command"],
|
|
@@ -22239,7 +22609,7 @@ function protectedLinkTargets(root, limit) {
|
|
|
22239
22609
|
const note = (link) => {
|
|
22240
22610
|
let target2;
|
|
22241
22611
|
try {
|
|
22242
|
-
target2 = resolve(
|
|
22612
|
+
target2 = resolve(dirname6(link), readlinkSync(link));
|
|
22243
22613
|
} catch {
|
|
22244
22614
|
return;
|
|
22245
22615
|
}
|
|
@@ -22249,7 +22619,7 @@ function protectedLinkTargets(root, limit) {
|
|
|
22249
22619
|
out.push(real);
|
|
22250
22620
|
};
|
|
22251
22621
|
for (const entry of [".git/hooks", ".git/config"]) {
|
|
22252
|
-
const link =
|
|
22622
|
+
const link = join7(root, entry);
|
|
22253
22623
|
try {
|
|
22254
22624
|
if (lstatSync(link).isSymbolicLink())
|
|
22255
22625
|
note(link);
|
|
@@ -22268,7 +22638,7 @@ function protectedLinkTargets(root, limit) {
|
|
|
22268
22638
|
for (const entry of entries) {
|
|
22269
22639
|
if (++seen > limit)
|
|
22270
22640
|
return null;
|
|
22271
|
-
const path =
|
|
22641
|
+
const path = join7(dir, entry.name);
|
|
22272
22642
|
if (entry.isSymbolicLink()) {
|
|
22273
22643
|
if (protectedByName(relative(root, path)))
|
|
22274
22644
|
note(path);
|
|
@@ -22318,7 +22688,7 @@ function resolveLikePi(raw, cwd, home, platform) {
|
|
|
22318
22688
|
if (p === "~")
|
|
22319
22689
|
p = home;
|
|
22320
22690
|
else if (p.startsWith("~/") || platform === "win32" && p.startsWith("~\\"))
|
|
22321
|
-
p =
|
|
22691
|
+
p = join7(home, p.slice(2));
|
|
22322
22692
|
if (/^file:\/\//.test(p))
|
|
22323
22693
|
p = fileURLToPath3(p);
|
|
22324
22694
|
return resolve(cwd, p);
|
|
@@ -22328,14 +22698,14 @@ function realpathDeepest(p) {
|
|
|
22328
22698
|
const rest3 = [];
|
|
22329
22699
|
for (;; ) {
|
|
22330
22700
|
try {
|
|
22331
|
-
return
|
|
22701
|
+
return join7(realpathSync(current), ...rest3.reverse());
|
|
22332
22702
|
} catch {
|
|
22333
22703
|
try {
|
|
22334
22704
|
lstatSync(current);
|
|
22335
22705
|
return null;
|
|
22336
22706
|
} catch {}
|
|
22337
22707
|
}
|
|
22338
|
-
const parent =
|
|
22708
|
+
const parent = dirname6(current);
|
|
22339
22709
|
if (parent === current)
|
|
22340
22710
|
return null;
|
|
22341
22711
|
rest3.push(basename(current));
|
|
@@ -22350,7 +22720,7 @@ function isSecret(target2, home) {
|
|
|
22350
22720
|
if (SECRET_BASENAME.some((re) => re.test(basename(target2))))
|
|
22351
22721
|
return true;
|
|
22352
22722
|
return SECRET_HOME_DIRS.some((dir) => {
|
|
22353
|
-
const lexical =
|
|
22723
|
+
const lexical = join7(home, dir);
|
|
22354
22724
|
return isInside(lexical, target2) || isInside(realpathDeepest(lexical) ?? lexical, target2);
|
|
22355
22725
|
});
|
|
22356
22726
|
}
|
|
@@ -22371,7 +22741,7 @@ function scanForSecret(dir) {
|
|
|
22371
22741
|
if (SECRET_BASENAME.some((re) => re.test(entry.name)))
|
|
22372
22742
|
return "yes";
|
|
22373
22743
|
if (entry.isDirectory())
|
|
22374
|
-
queue.push(
|
|
22744
|
+
queue.push(join7(current, entry.name));
|
|
22375
22745
|
}
|
|
22376
22746
|
}
|
|
22377
22747
|
return "no";
|
|
@@ -22411,7 +22781,7 @@ function pathVerdict(tool, input, opts, home) {
|
|
|
22411
22781
|
const verdict = checkPath(raw, search, opts, home);
|
|
22412
22782
|
if (verdict)
|
|
22413
22783
|
return verdict;
|
|
22414
|
-
if (tool === "read" && !
|
|
22784
|
+
if (tool === "read" && !existsSync3(resolveLikePi(raw, opts.cwd, home, opts.platform ?? process.platform))) {
|
|
22415
22785
|
return { ask: "file not found; pi may open a similarly named file" };
|
|
22416
22786
|
}
|
|
22417
22787
|
return;
|
|
@@ -22546,7 +22916,7 @@ function worktreeSnapshot(g) {
|
|
|
22546
22916
|
const status = entry.slice(0, 2);
|
|
22547
22917
|
if (status[0] === "R" || status[0] === "C")
|
|
22548
22918
|
i++;
|
|
22549
|
-
files.set(entry.slice(3), `${status}:${fingerprint(
|
|
22919
|
+
files.set(entry.slice(3), `${status}:${fingerprint(join8(root, entry.slice(3)))}`);
|
|
22550
22920
|
}
|
|
22551
22921
|
return { files, head: runGit(g, "rev-parse", "HEAD")?.trim() };
|
|
22552
22922
|
} catch (e) {
|
|
@@ -22999,7 +23369,7 @@ ${decision.reason}`, ACTION_DIALOG_MS);
|
|
|
22999
23369
|
import { spawnSync } from "node:child_process";
|
|
23000
23370
|
import {
|
|
23001
23371
|
chmodSync,
|
|
23002
|
-
existsSync as
|
|
23372
|
+
existsSync as existsSync4,
|
|
23003
23373
|
lstatSync as lstatSync3,
|
|
23004
23374
|
mkdirSync,
|
|
23005
23375
|
mkdtempSync,
|
|
@@ -23010,7 +23380,7 @@ import {
|
|
|
23010
23380
|
writeFileSync
|
|
23011
23381
|
} from "node:fs";
|
|
23012
23382
|
import { homedir as homedir4, tmpdir } from "node:os";
|
|
23013
|
-
import { dirname as
|
|
23383
|
+
import { dirname as dirname7, join as join9 } from "node:path";
|
|
23014
23384
|
var SECRET_NAME_PATTERNS = [
|
|
23015
23385
|
"\\.env(\\..*)?",
|
|
23016
23386
|
"[^/]*\\.pem",
|
|
@@ -23045,8 +23415,8 @@ var TOOLCHAIN_ROOTS = [
|
|
|
23045
23415
|
".local/share/pnpm",
|
|
23046
23416
|
"Library/pnpm"
|
|
23047
23417
|
];
|
|
23048
|
-
function toolchainRoots(home, exists =
|
|
23049
|
-
return TOOLCHAIN_ROOTS.map((r) =>
|
|
23418
|
+
function toolchainRoots(home, exists = existsSync4) {
|
|
23419
|
+
return TOOLCHAIN_ROOTS.map((r) => join9(home, r)).filter((p) => exists(p));
|
|
23050
23420
|
}
|
|
23051
23421
|
var ENV_ALLOW = ["PATH", "LANG", "LC_ALL", "LC_CTYPE", "TERM", "COLORTERM", "TZ", "USER", "LOGNAME", "SHELL", "PI_PROVIDER", "PI_MODEL"];
|
|
23052
23422
|
function under(path, dir) {
|
|
@@ -23064,10 +23434,10 @@ function sandboxEnv(base, o) {
|
|
|
23064
23434
|
for (const k of ENV_ALLOW)
|
|
23065
23435
|
if (base[k] !== undefined)
|
|
23066
23436
|
env[k] = base[k];
|
|
23067
|
-
env.PATH = [...o.developerDir ? [
|
|
23437
|
+
env.PATH = [...o.developerDir ? [join9(o.developerDir, "usr/bin")] : [], ...o.pathPrefix ?? [], ...(base.PATH ?? "/usr/bin:/bin").split(":")].filter((p) => p.startsWith("/") && !under(p, o.project) && !under(realOrSelf(p), o.project)).join(":");
|
|
23068
23438
|
if (o.developerDir)
|
|
23069
23439
|
env.DEVELOPER_DIR = o.developerDir;
|
|
23070
|
-
env.HOME =
|
|
23440
|
+
env.HOME = join9(o.scratch, "home");
|
|
23071
23441
|
env.TMP = env.TEMP = o.scratch;
|
|
23072
23442
|
env.TMPDIR = `${o.scratch}/`;
|
|
23073
23443
|
env.BUN_OPTIONS = "--no-env-file";
|
|
@@ -23077,7 +23447,7 @@ function sandboxEnv(base, o) {
|
|
|
23077
23447
|
env.GIT_CONFIG_COUNT = "1";
|
|
23078
23448
|
env.GIT_CONFIG_KEY_0 = "credential.helper";
|
|
23079
23449
|
env.GIT_CONFIG_VALUE_0 = "";
|
|
23080
|
-
env.npm_config_cache =
|
|
23450
|
+
env.npm_config_cache = join9(o.scratch, "npm-cache");
|
|
23081
23451
|
env.FREEPI_HIVE_CALL = o.callId;
|
|
23082
23452
|
return env;
|
|
23083
23453
|
}
|
|
@@ -23123,7 +23493,7 @@ function scanProject(project, limit = SCAN_LIMIT2) {
|
|
|
23123
23493
|
scan.overflow = true;
|
|
23124
23494
|
break;
|
|
23125
23495
|
}
|
|
23126
|
-
const path =
|
|
23496
|
+
const path = join9(dir, e.name);
|
|
23127
23497
|
if (e.isSymbolicLink())
|
|
23128
23498
|
continue;
|
|
23129
23499
|
if (e.isDirectory()) {
|
|
@@ -23206,7 +23576,7 @@ var MAC_MACH_SERVICES = [
|
|
|
23206
23576
|
["com.apple.system.opendirectoryd.libinfo", "user and group names: id, ls -l, python pwd, git author"]
|
|
23207
23577
|
];
|
|
23208
23578
|
function developerDir(o = {}) {
|
|
23209
|
-
const exists = o.exists ??
|
|
23579
|
+
const exists = o.exists ?? existsSync4;
|
|
23210
23580
|
const realpath = o.realpath ?? realpathSync2;
|
|
23211
23581
|
const run = o.exec ?? defaultExec;
|
|
23212
23582
|
const candidates2 = [];
|
|
@@ -23223,14 +23593,14 @@ function developerDir(o = {}) {
|
|
|
23223
23593
|
continue;
|
|
23224
23594
|
try {
|
|
23225
23595
|
const real = realpath(c);
|
|
23226
|
-
if (exists(
|
|
23596
|
+
if (exists(join9(real, "usr/bin/git")))
|
|
23227
23597
|
return real;
|
|
23228
23598
|
} catch {}
|
|
23229
23599
|
}
|
|
23230
23600
|
return null;
|
|
23231
23601
|
}
|
|
23232
23602
|
function opensslFiles(o = {}) {
|
|
23233
|
-
const exists = o.exists ??
|
|
23603
|
+
const exists = o.exists ?? existsSync4;
|
|
23234
23604
|
const list = o.list ?? ((d) => {
|
|
23235
23605
|
try {
|
|
23236
23606
|
return readdirSync2(d);
|
|
@@ -23241,18 +23611,18 @@ function opensslFiles(o = {}) {
|
|
|
23241
23611
|
const files = [];
|
|
23242
23612
|
const dirs = [];
|
|
23243
23613
|
for (const prefix of o.prefixes ?? ["/opt/homebrew", "/usr/local"]) {
|
|
23244
|
-
const etc =
|
|
23614
|
+
const etc = join9(prefix, "etc");
|
|
23245
23615
|
for (const name of list(etc)) {
|
|
23246
23616
|
if (!/^openssl(@[0-9.]+)?$/.test(name))
|
|
23247
23617
|
continue;
|
|
23248
23618
|
for (const f of ["openssl.cnf", "ct_log_list.cnf", "cert.pem"])
|
|
23249
|
-
if (exists(
|
|
23250
|
-
files.push(
|
|
23251
|
-
if (exists(
|
|
23252
|
-
dirs.push(
|
|
23619
|
+
if (exists(join9(etc, name, f)))
|
|
23620
|
+
files.push(join9(etc, name, f));
|
|
23621
|
+
if (exists(join9(etc, name, "certs")))
|
|
23622
|
+
dirs.push(join9(etc, name, "certs"));
|
|
23253
23623
|
}
|
|
23254
|
-
if (exists(
|
|
23255
|
-
files.push(
|
|
23624
|
+
if (exists(join9(etc, "ca-certificates/cert.pem")))
|
|
23625
|
+
files.push(join9(etc, "ca-certificates/cert.pem"));
|
|
23256
23626
|
}
|
|
23257
23627
|
return { files, dirs };
|
|
23258
23628
|
}
|
|
@@ -23260,7 +23630,7 @@ var MAC_DEV_READ = ["/dev/null", "/dev/zero", "/dev/random", "/dev/urandom", "/d
|
|
|
23260
23630
|
var MAC_DEV_WRITE = ["/dev/null", "/dev/tty", "/dev/zero", "/dev/dtracehelper"];
|
|
23261
23631
|
function ancestors(p) {
|
|
23262
23632
|
const out = [];
|
|
23263
|
-
for (let d =
|
|
23633
|
+
for (let d = dirname7(p);; d = dirname7(d)) {
|
|
23264
23634
|
out.push(d);
|
|
23265
23635
|
if (d === "/")
|
|
23266
23636
|
break;
|
|
@@ -23296,9 +23666,9 @@ function buildMacProfile(i) {
|
|
|
23296
23666
|
L.push(`(deny file-read* file-write* (literal ${sbString(s)}))`);
|
|
23297
23667
|
for (const f of i.links)
|
|
23298
23668
|
L.push(`(deny file-write* (literal ${sbString(f)}))`);
|
|
23299
|
-
const guarded = new Set([i.project,
|
|
23300
|
-
for (const f of [...i.sockets, ...i.links, ...i.nodeModules ?? [],
|
|
23301
|
-
for (let d =
|
|
23669
|
+
const guarded = new Set([i.project, join9(i.project, ".git"), ...i.nodeModules ?? []]);
|
|
23670
|
+
for (const f of [...i.sockets, ...i.links, ...i.nodeModules ?? [], join9(i.project, ".git")]) {
|
|
23671
|
+
for (let d = dirname7(f);d.length >= i.project.length && (d === i.project || d.startsWith(i.project + "/")); d = dirname7(d))
|
|
23302
23672
|
guarded.add(d);
|
|
23303
23673
|
}
|
|
23304
23674
|
for (const d of [...guarded].sort())
|
|
@@ -23351,7 +23721,7 @@ var BWRAP_ETC = [
|
|
|
23351
23721
|
"resolv.conf"
|
|
23352
23722
|
].map((e) => `/etc/${e}`);
|
|
23353
23723
|
function buildBwrapArgs(i) {
|
|
23354
|
-
const exists = i.exists ??
|
|
23724
|
+
const exists = i.exists ?? existsSync4;
|
|
23355
23725
|
const a = ["--unshare-all", "--die-with-parent", "--new-session"];
|
|
23356
23726
|
const ro = (p) => a.push("--ro-bind", p, p);
|
|
23357
23727
|
for (const d of BWRAP_SYSTEM_DIRS)
|
|
@@ -23367,10 +23737,10 @@ function buildBwrapArgs(i) {
|
|
|
23367
23737
|
a.push("--bind", i.scratch, i.scratch);
|
|
23368
23738
|
for (const d of i.nodeModules)
|
|
23369
23739
|
ro(d);
|
|
23370
|
-
if (exists(
|
|
23371
|
-
ro(
|
|
23372
|
-
if (exists(
|
|
23373
|
-
ro(
|
|
23740
|
+
if (exists(join9(i.project, ".git/hooks")))
|
|
23741
|
+
ro(join9(i.project, ".git/hooks"));
|
|
23742
|
+
if (exists(join9(i.project, ".git/config")))
|
|
23743
|
+
ro(join9(i.project, ".git/config"));
|
|
23374
23744
|
for (const f of i.links)
|
|
23375
23745
|
ro(f);
|
|
23376
23746
|
for (const s of i.sockets)
|
|
@@ -23400,7 +23770,7 @@ function removeTree(path) {
|
|
|
23400
23770
|
return;
|
|
23401
23771
|
chmodSync(p, 448);
|
|
23402
23772
|
for (const e of readdirSync2(p))
|
|
23403
|
-
walk(
|
|
23773
|
+
walk(join9(p, e));
|
|
23404
23774
|
} catch {}
|
|
23405
23775
|
};
|
|
23406
23776
|
walk(path);
|
|
@@ -23438,16 +23808,16 @@ function createShellSandbox(deps = {}) {
|
|
|
23438
23808
|
if (deps.python !== undefined)
|
|
23439
23809
|
return deps.python;
|
|
23440
23810
|
const dev = platform === "darwin" ? macHost().developerDir : null;
|
|
23441
|
-
return dev &&
|
|
23811
|
+
return dev && existsSync4(join9(dev, "usr/bin/python3")) ? join9(dev, "usr/bin/python3") : null;
|
|
23442
23812
|
};
|
|
23443
|
-
let
|
|
23813
|
+
let cached3;
|
|
23444
23814
|
function probe() {
|
|
23445
23815
|
if (platform === "win32")
|
|
23446
23816
|
return { supported: false, reason: "Windows has no sandbox" };
|
|
23447
23817
|
if (platform !== "darwin" && platform !== "linux")
|
|
23448
23818
|
return { supported: false, reason: `No shell sandbox for ${platform}` };
|
|
23449
23819
|
const mac3 = platform === "darwin";
|
|
23450
|
-
if (!
|
|
23820
|
+
if (!existsSync4(launcher.sandbox))
|
|
23451
23821
|
return { supported: false, reason: mac3 ? "sandbox-exec is unavailable" : "bubblewrap is not installed" };
|
|
23452
23822
|
if (checkLauncher(launcher.sandbox, undefined, stat) !== null)
|
|
23453
23823
|
return { supported: false, reason: NOT_TRUSTWORTHY };
|
|
@@ -23457,7 +23827,7 @@ function createShellSandbox(deps = {}) {
|
|
|
23457
23827
|
}
|
|
23458
23828
|
const args = ["--unshare-all"];
|
|
23459
23829
|
for (const d of ["/usr", "/bin", "/lib", "/lib64"])
|
|
23460
|
-
if (
|
|
23830
|
+
if (existsSync4(d))
|
|
23461
23831
|
args.push("--ro-bind", d, d);
|
|
23462
23832
|
args.push("--proc", "/proc", "--dev", "/dev", "/bin/true");
|
|
23463
23833
|
const r = exec(launcher.sandbox, args, { timeout: 5000 });
|
|
@@ -23532,12 +23902,12 @@ function createShellSandbox(deps = {}) {
|
|
|
23532
23902
|
return {
|
|
23533
23903
|
enabled: false,
|
|
23534
23904
|
capability() {
|
|
23535
|
-
return
|
|
23905
|
+
return cached3 ??= probe();
|
|
23536
23906
|
},
|
|
23537
23907
|
prepare(toolCallId, cwd) {
|
|
23538
23908
|
dispose(toolCallId);
|
|
23539
23909
|
decisions.delete(toolCallId);
|
|
23540
|
-
const cap =
|
|
23910
|
+
const cap = cached3 ??= probe();
|
|
23541
23911
|
if (!cap.supported)
|
|
23542
23912
|
return { ready: false, reason: cap.reason };
|
|
23543
23913
|
let project;
|
|
@@ -23556,12 +23926,12 @@ function createShellSandbox(deps = {}) {
|
|
|
23556
23926
|
return { ready: false, reason: "a folder in the project cannot be read, so it cannot be checked for sockets and hard links" };
|
|
23557
23927
|
let scratch;
|
|
23558
23928
|
try {
|
|
23559
|
-
scratch = mkdtempSync(
|
|
23929
|
+
scratch = mkdtempSync(join9(realpathSync2(tmpdir()), "freepi-hive-"));
|
|
23560
23930
|
const dir = scratch;
|
|
23561
|
-
mkdirSync(
|
|
23562
|
-
mkdirSync(
|
|
23563
|
-
const emptyFile =
|
|
23564
|
-
const marker =
|
|
23931
|
+
mkdirSync(join9(dir, "home"));
|
|
23932
|
+
mkdirSync(join9(dir, "npm-cache"));
|
|
23933
|
+
const emptyFile = join9(dir, "empty");
|
|
23934
|
+
const marker = join9(dir, "call.fd");
|
|
23565
23935
|
writeFileSync(emptyFile, "", { mode: 292 });
|
|
23566
23936
|
const roots = toolchainRoots(home);
|
|
23567
23937
|
const mac3 = platform === "darwin";
|
|
@@ -23722,6 +24092,20 @@ function projectTrustForCwd(cwd, launch, agentDir, hasTrustRequiringResources =
|
|
|
23722
24092
|
return new ProjectTrustStore(agentDir).get(cwd) === true;
|
|
23723
24093
|
}
|
|
23724
24094
|
|
|
24095
|
+
// src/session-auth.ts
|
|
24096
|
+
function createSessionAuth(initial) {
|
|
24097
|
+
let jwt2 = initial.jwt;
|
|
24098
|
+
let guest = initial.guest;
|
|
24099
|
+
return {
|
|
24100
|
+
getJwt: () => jwt2,
|
|
24101
|
+
isGuest: () => guest,
|
|
24102
|
+
becomeSignedIn(token) {
|
|
24103
|
+
jwt2 = token;
|
|
24104
|
+
guest = false;
|
|
24105
|
+
}
|
|
24106
|
+
};
|
|
24107
|
+
}
|
|
24108
|
+
|
|
23725
24109
|
// src/pi-launch.ts
|
|
23726
24110
|
function catalogModelsFor(opts) {
|
|
23727
24111
|
const main = splitCatalogModels(opts.models ?? []).main;
|
|
@@ -23753,7 +24137,7 @@ var SDK_TOOLS = [
|
|
|
23753
24137
|
"list_mcp_resource_templates",
|
|
23754
24138
|
"read_mcp_resource"
|
|
23755
24139
|
];
|
|
23756
|
-
var SKILLS_DIR = resolve3(
|
|
24140
|
+
var SKILLS_DIR = resolve3(dirname8(fileURLToPath4(import.meta.url)), "..", "skills");
|
|
23757
24141
|
function resolveModelName(opts) {
|
|
23758
24142
|
return opts.models?.[0]?.name || opts.model || MODEL_ID;
|
|
23759
24143
|
}
|
|
@@ -23764,6 +24148,7 @@ function buildSettingsManager(opts, models, projectTrusted) {
|
|
|
23764
24148
|
defaultTools: [...SAFE_TOOLS, TODO_TOOL_NAME],
|
|
23765
24149
|
packages: [],
|
|
23766
24150
|
quietStartup: true,
|
|
24151
|
+
defaultThinkingLevel: "off",
|
|
23767
24152
|
hideThinkingBlock: true,
|
|
23768
24153
|
tuiMode: "regular",
|
|
23769
24154
|
theme: "light/dark",
|
|
@@ -23776,42 +24161,50 @@ function settingsForRuntime(opts, rtCwd, launch) {
|
|
|
23776
24161
|
function buildRuntimeOptions(opts, sessionId) {
|
|
23777
24162
|
process.env.PI_CODING_AGENT_DIR = opts.agentDir;
|
|
23778
24163
|
const models = catalogModelsFor(opts);
|
|
24164
|
+
const auth = opts.sessionAuth ?? createSessionAuth({ jwt: opts.jwt, guest: Boolean(opts.guest) });
|
|
24165
|
+
opts.sessionAuth = auth;
|
|
23779
24166
|
const providerExtension = {
|
|
23780
24167
|
name: "free-pi-provider",
|
|
23781
24168
|
factory: (pi) => {
|
|
23782
|
-
pi.registerProvider(PROVIDER_NAME, buildProviderConfig(opts.baseUrl,
|
|
24169
|
+
pi.registerProvider(PROVIDER_NAME, buildProviderConfig(opts.baseUrl, auth.getJwt(), sessionId, models));
|
|
23783
24170
|
const defense = defenseModelsFor(opts);
|
|
23784
24171
|
if (defense.length > 0) {
|
|
23785
|
-
pi.registerProvider(DEFENSE_PROVIDER_NAME, buildProviderConfig(opts.baseUrl,
|
|
24172
|
+
pi.registerProvider(DEFENSE_PROVIDER_NAME, buildProviderConfig(opts.baseUrl, auth.getJwt(), sessionId, defense, DEFENSE_PROVIDER_NAME));
|
|
23786
24173
|
}
|
|
24174
|
+
pi.on("before_provider_headers", (event) => {
|
|
24175
|
+
event.headers.Authorization = `Bearer ${auth.getJwt()}`;
|
|
24176
|
+
});
|
|
23787
24177
|
}
|
|
23788
24178
|
};
|
|
23789
24179
|
let advertiserUrl;
|
|
23790
24180
|
const adsExtension = createAdsExtension({
|
|
23791
24181
|
baseUrl: opts.baseUrl,
|
|
23792
|
-
getToken: () =>
|
|
24182
|
+
getToken: () => auth.getJwt(),
|
|
23793
24183
|
sessionId,
|
|
23794
24184
|
getGravityContext: opts.getGravityContext,
|
|
23795
24185
|
onBannerAd: (u) => {
|
|
23796
24186
|
advertiserUrl = u;
|
|
24187
|
+
},
|
|
24188
|
+
onMe: (me) => {
|
|
24189
|
+
updateAllowanceCache(me);
|
|
23797
24190
|
}
|
|
23798
24191
|
});
|
|
23799
24192
|
const usageToolExtension = createUsageToolExtension({
|
|
23800
24193
|
baseUrl: opts.baseUrl,
|
|
23801
|
-
getToken: () =>
|
|
24194
|
+
getToken: () => auth.getJwt()
|
|
23802
24195
|
});
|
|
23803
24196
|
const buyToolExtension = createBuyToolExtension({
|
|
23804
24197
|
baseUrl: opts.baseUrl,
|
|
23805
|
-
getToken: () =>
|
|
24198
|
+
getToken: () => auth.getJwt()
|
|
23806
24199
|
});
|
|
23807
24200
|
const docsToolExtension = createDocsToolExtension();
|
|
23808
24201
|
const webToolsExtension = createWebToolsExtension({
|
|
23809
24202
|
baseUrl: opts.baseUrl,
|
|
23810
|
-
getToken: () =>
|
|
24203
|
+
getToken: () => auth.getJwt()
|
|
23811
24204
|
});
|
|
23812
24205
|
const imageToolExtension = createImageToolExtension({
|
|
23813
24206
|
baseUrl: opts.baseUrl,
|
|
23814
|
-
getToken: () =>
|
|
24207
|
+
getToken: () => auth.getJwt()
|
|
23815
24208
|
});
|
|
23816
24209
|
const errorNoticeExtension = createErrorNoticeExtension({
|
|
23817
24210
|
extraNotice: (env) => {
|
|
@@ -23822,34 +24215,39 @@ function buildRuntimeOptions(opts, sessionId) {
|
|
|
23822
24215
|
if (!card)
|
|
23823
24216
|
return;
|
|
23824
24217
|
session.shown = true;
|
|
23825
|
-
reportRefillCardShown({ baseUrl: opts.baseUrl, jwt:
|
|
24218
|
+
reportRefillCardShown({ baseUrl: opts.baseUrl, jwt: auth.getJwt() });
|
|
23826
24219
|
return card;
|
|
23827
24220
|
}
|
|
23828
24221
|
});
|
|
23829
24222
|
const toolGuardExtension = createToolGuardExtension({
|
|
23830
24223
|
baseUrl: opts.baseUrl,
|
|
23831
|
-
getToken: () =>
|
|
24224
|
+
getToken: () => auth.getJwt(),
|
|
23832
24225
|
allowedTools: ALLOWED_TOOL_NAMES
|
|
23833
24226
|
});
|
|
23834
24227
|
const commandsExtension = createFreePiCommandsExtension({
|
|
23835
24228
|
baseUrl: opts.baseUrl,
|
|
23836
|
-
getToken: () =>
|
|
24229
|
+
getToken: () => auth.getJwt(),
|
|
23837
24230
|
getAdvertiserUrl: () => advertiserUrl,
|
|
23838
24231
|
credentialsPath: opts.credentialsPath,
|
|
23839
24232
|
agentDir: opts.agentDir,
|
|
23840
|
-
isGuest: () =>
|
|
24233
|
+
isGuest: () => auth.isGuest(),
|
|
24234
|
+
onLogin: (token) => auth.becomeSignedIn(token),
|
|
24235
|
+
variant: () => opts.guestHandoffVariant
|
|
23841
24236
|
});
|
|
23842
24237
|
const guestLoginExtension = opts.guest ? createGuestLoginExtension({
|
|
23843
24238
|
baseUrl: opts.baseUrl,
|
|
23844
|
-
getToken: () =>
|
|
24239
|
+
getToken: () => auth.getJwt(),
|
|
23845
24240
|
credentialsPath: opts.credentialsPath,
|
|
23846
24241
|
agentDir: opts.agentDir,
|
|
23847
|
-
isGuest: () =>
|
|
24242
|
+
isGuest: () => auth.isGuest(),
|
|
24243
|
+
onLogin: (token) => auth.becomeSignedIn(token),
|
|
24244
|
+
variant: () => opts.guestHandoffVariant
|
|
23848
24245
|
}) : undefined;
|
|
23849
24246
|
const headerExtension = createHeaderExtension({
|
|
23850
24247
|
modelName: resolveModelName(opts),
|
|
23851
24248
|
updateLatest: opts.updateLatest,
|
|
23852
|
-
guest: opts.guest
|
|
24249
|
+
guest: opts.guest,
|
|
24250
|
+
allowanceLine: opts.allowanceLine
|
|
23853
24251
|
});
|
|
23854
24252
|
const todoToolExtension = createTodoToolExtension();
|
|
23855
24253
|
const shellSandbox = opts.shellSandbox ?? createShellSandbox();
|
|
@@ -23859,7 +24257,7 @@ function buildRuntimeOptions(opts, sessionId) {
|
|
|
23859
24257
|
const toolSearchExtension = { name: "pi-tool-search", factory: createToolSearchExtension() };
|
|
23860
24258
|
const hiveExtension = createHiveExtension({
|
|
23861
24259
|
baseUrl: opts.baseUrl,
|
|
23862
|
-
getToken: () =>
|
|
24260
|
+
getToken: () => auth.getJwt(),
|
|
23863
24261
|
knownTools: ALLOWED_TOOL_NAMES,
|
|
23864
24262
|
sandbox: shellSandbox
|
|
23865
24263
|
});
|
|
@@ -23882,7 +24280,8 @@ function buildRuntimeOptions(opts, sessionId) {
|
|
|
23882
24280
|
codemodeExtension,
|
|
23883
24281
|
toolSearchExtension,
|
|
23884
24282
|
hiveExtension,
|
|
23885
|
-
...guestLoginExtension ? [guestLoginExtension] : []
|
|
24283
|
+
...guestLoginExtension ? [guestLoginExtension] : [],
|
|
24284
|
+
...opts.guest ? [createGuestNotesExtension({ isGuest: () => auth.isGuest() })] : []
|
|
23886
24285
|
];
|
|
23887
24286
|
const settingsManager = buildSettingsManager(opts, models, opts.projectTrusted === true);
|
|
23888
24287
|
return {
|
|
@@ -23899,7 +24298,20 @@ function buildRuntimeOptions(opts, sessionId) {
|
|
|
23899
24298
|
function getSessionDir(cwd, agentDir) {
|
|
23900
24299
|
const resolvedCwd = resolve3(cwd);
|
|
23901
24300
|
const safePath = `--${resolvedCwd.replace(/^[/\\]/, "").replace(/[/\\:]/g, "-")}--`;
|
|
23902
|
-
return
|
|
24301
|
+
return join10(resolve3(agentDir), "sessions", safePath);
|
|
24302
|
+
}
|
|
24303
|
+
async function findLatestSession(cwd, sessionDir, api3 = SessionManager) {
|
|
24304
|
+
const infos = await api3.list(cwd, sessionDir);
|
|
24305
|
+
if (infos.length === 0)
|
|
24306
|
+
return;
|
|
24307
|
+
const latest = [...infos].sort((a, b) => +new Date(b.modified) - +new Date(a.modified))[0];
|
|
24308
|
+
if (!latest)
|
|
24309
|
+
return;
|
|
24310
|
+
return {
|
|
24311
|
+
id: latest.id,
|
|
24312
|
+
modified: latest.modified instanceof Date ? latest.modified : new Date(latest.modified),
|
|
24313
|
+
firstMessage: latest.firstMessage ?? ""
|
|
24314
|
+
};
|
|
23903
24315
|
}
|
|
23904
24316
|
async function createSessionManager(cwd, sessionDir, sessionId, api3 = SessionManager) {
|
|
23905
24317
|
if (sessionId) {
|
|
@@ -23925,9 +24337,11 @@ async function launchPi(opts) {
|
|
|
23925
24337
|
const cwd = process.cwd();
|
|
23926
24338
|
const sessionDir = getSessionDir(cwd, opts.agentDir);
|
|
23927
24339
|
const sm = await createSessionManager(cwd, sessionDir, opts.session);
|
|
24340
|
+
const auth = opts.sessionAuth ?? createSessionAuth({ jwt: opts.jwt, guest: Boolean(opts.guest) });
|
|
24341
|
+
const launchOpts = { ...opts, sessionAuth: auth };
|
|
23928
24342
|
const sessionId = sm.getSessionId();
|
|
23929
24343
|
const projectTrusted = await decideProjectTrustInteractive(cwd, opts.agentDir);
|
|
23930
|
-
const { resourceLoaderOptions, tools } = buildRuntimeOptions({ ...
|
|
24344
|
+
const { resourceLoaderOptions, tools } = buildRuntimeOptions({ ...launchOpts, projectTrusted }, sessionId);
|
|
23931
24345
|
const launchTrust = { cwd, trusted: projectTrusted };
|
|
23932
24346
|
const createRuntime = async ({
|
|
23933
24347
|
cwd: rtCwd,
|
|
@@ -23962,13 +24376,17 @@ async function launchPi(opts) {
|
|
|
23962
24376
|
initialImages: [],
|
|
23963
24377
|
initialMessages: []
|
|
23964
24378
|
});
|
|
23965
|
-
installBugHandler(mode, { baseUrl: opts.baseUrl, getToken: () =>
|
|
24379
|
+
installBugHandler(mode, { baseUrl: opts.baseUrl, getToken: () => auth.getJwt() });
|
|
23966
24380
|
installLoginHandler(mode, {
|
|
23967
24381
|
baseUrl: opts.baseUrl,
|
|
23968
|
-
getToken: () =>
|
|
24382
|
+
getToken: () => auth.getJwt(),
|
|
23969
24383
|
credentialsPath: opts.credentialsPath,
|
|
23970
|
-
agentDir: opts.agentDir
|
|
24384
|
+
agentDir: opts.agentDir,
|
|
24385
|
+
isGuest: () => auth.isGuest(),
|
|
24386
|
+
onLogin: (token) => auth.becomeSignedIn(token),
|
|
24387
|
+
variant: () => opts.guestHandoffVariant
|
|
23971
24388
|
});
|
|
24389
|
+
installModelAllowanceHandler(mode);
|
|
23972
24390
|
installFooterRightAlign(mode);
|
|
23973
24391
|
await mode.run();
|
|
23974
24392
|
return sm.getSessionId();
|
|
@@ -24018,6 +24436,154 @@ function createRealSpawn(io = { platform: process.platform, spawnSyncImpl: spawn
|
|
|
24018
24436
|
// src/run.ts
|
|
24019
24437
|
import { SessionManager as SessionManager2 } from "@earendil-works/pi-coding-agent";
|
|
24020
24438
|
|
|
24439
|
+
// src/resume-on-launch.ts
|
|
24440
|
+
var RESUME_ON_LAUNCH_EXPERIMENT = "resume_on_launch";
|
|
24441
|
+
var DEFAULT_RESUME_RECENT_HOURS = 48;
|
|
24442
|
+
var RESUME_PROMPT_SUFFIX = " [Y/n] ";
|
|
24443
|
+
function userArgv(argv) {
|
|
24444
|
+
if (/(^|[\\/])(node|bun)(\.exe)?$/i.test(argv[0] ?? ""))
|
|
24445
|
+
return argv.slice(2);
|
|
24446
|
+
return argv;
|
|
24447
|
+
}
|
|
24448
|
+
function parseResumeFlags(argv) {
|
|
24449
|
+
const args = userArgv(argv);
|
|
24450
|
+
let session;
|
|
24451
|
+
let sessionError;
|
|
24452
|
+
let continueLast = false;
|
|
24453
|
+
let startNew = false;
|
|
24454
|
+
let hasPrompt = false;
|
|
24455
|
+
for (let i = 0;i < args.length; i++) {
|
|
24456
|
+
const arg = args[i];
|
|
24457
|
+
if (arg === "--session") {
|
|
24458
|
+
const value = args[i + 1];
|
|
24459
|
+
if (!value || value.startsWith("--")) {
|
|
24460
|
+
sessionError = "Missing value for --session";
|
|
24461
|
+
} else {
|
|
24462
|
+
session = value;
|
|
24463
|
+
i += 1;
|
|
24464
|
+
}
|
|
24465
|
+
continue;
|
|
24466
|
+
}
|
|
24467
|
+
if (arg.startsWith("--session=")) {
|
|
24468
|
+
const value = arg.slice("--session=".length);
|
|
24469
|
+
if (!value)
|
|
24470
|
+
sessionError = "Missing value for --session";
|
|
24471
|
+
else
|
|
24472
|
+
session = value;
|
|
24473
|
+
continue;
|
|
24474
|
+
}
|
|
24475
|
+
if (arg === "--continue" || arg === "--resume") {
|
|
24476
|
+
continueLast = true;
|
|
24477
|
+
continue;
|
|
24478
|
+
}
|
|
24479
|
+
if (arg === "--new") {
|
|
24480
|
+
startNew = true;
|
|
24481
|
+
continue;
|
|
24482
|
+
}
|
|
24483
|
+
if (arg === "--ref" || arg === "--src") {
|
|
24484
|
+
if (args[i + 1] && !String(args[i + 1]).startsWith("--"))
|
|
24485
|
+
i += 1;
|
|
24486
|
+
continue;
|
|
24487
|
+
}
|
|
24488
|
+
if (arg.startsWith("--ref=") || arg.startsWith("--src="))
|
|
24489
|
+
continue;
|
|
24490
|
+
if (!arg.startsWith("-"))
|
|
24491
|
+
hasPrompt = true;
|
|
24492
|
+
}
|
|
24493
|
+
return { session, sessionError, continueLast, startNew, hasPrompt };
|
|
24494
|
+
}
|
|
24495
|
+
function isRecentSession(modified, recentHours, now) {
|
|
24496
|
+
const ageMs = now.getTime() - modified.getTime();
|
|
24497
|
+
return ageMs >= 0 && ageMs <= recentHours * 3600000;
|
|
24498
|
+
}
|
|
24499
|
+
function recentHoursFromEnv(env = process.env, serverHours) {
|
|
24500
|
+
const raw = env.FREEPI_RESUME_RECENT_HOURS;
|
|
24501
|
+
if (raw !== undefined && raw !== "") {
|
|
24502
|
+
const n = Number(raw);
|
|
24503
|
+
if (Number.isInteger(n) && n >= 1)
|
|
24504
|
+
return n;
|
|
24505
|
+
}
|
|
24506
|
+
if (typeof serverHours === "number" && Number.isInteger(serverHours) && serverHours >= 1) {
|
|
24507
|
+
return serverHours;
|
|
24508
|
+
}
|
|
24509
|
+
return DEFAULT_RESUME_RECENT_HOURS;
|
|
24510
|
+
}
|
|
24511
|
+
function formatResumeAge(modified, now) {
|
|
24512
|
+
const deltaSec = Math.max(0, Math.floor((now.getTime() - modified.getTime()) / 1000));
|
|
24513
|
+
if (deltaSec < 60)
|
|
24514
|
+
return "just now";
|
|
24515
|
+
const minutes = Math.floor(deltaSec / 60);
|
|
24516
|
+
if (minutes < 60)
|
|
24517
|
+
return minutes === 1 ? "1 minute ago" : `${minutes} minutes ago`;
|
|
24518
|
+
const hours = Math.floor(minutes / 60);
|
|
24519
|
+
if (hours < 24)
|
|
24520
|
+
return hours === 1 ? "1 hour ago" : `${hours} hours ago`;
|
|
24521
|
+
const days = Math.floor(hours / 24);
|
|
24522
|
+
if (days === 1)
|
|
24523
|
+
return "yesterday";
|
|
24524
|
+
return `${days} days ago`;
|
|
24525
|
+
}
|
|
24526
|
+
function previewText(firstMessage) {
|
|
24527
|
+
if (!firstMessage)
|
|
24528
|
+
return;
|
|
24529
|
+
const oneLine = firstMessage.replace(/\s+/g, " ").trim();
|
|
24530
|
+
if (!oneLine)
|
|
24531
|
+
return;
|
|
24532
|
+
return oneLine.length > 60 ? `${oneLine.slice(0, 57)}...` : oneLine;
|
|
24533
|
+
}
|
|
24534
|
+
function formatResumePrompt(session, now = new Date) {
|
|
24535
|
+
const age = formatResumeAge(session.modified, now);
|
|
24536
|
+
const preview = previewText(session.firstMessage);
|
|
24537
|
+
const body = preview ? `Continue last session from ${age}: ${preview}?` : `Continue last session from ${age}?`;
|
|
24538
|
+
return `${body}${RESUME_PROMPT_SUFFIX}`;
|
|
24539
|
+
}
|
|
24540
|
+
function parseResumeKeypress(raw) {
|
|
24541
|
+
if (raw === "\x03")
|
|
24542
|
+
return null;
|
|
24543
|
+
const ch = raw.slice(0, 1);
|
|
24544
|
+
if (ch === "n" || ch === "N")
|
|
24545
|
+
return false;
|
|
24546
|
+
return true;
|
|
24547
|
+
}
|
|
24548
|
+
async function promptResume(session, deps) {
|
|
24549
|
+
if (!deps.stdinIsTTY)
|
|
24550
|
+
return false;
|
|
24551
|
+
const prompt = formatResumePrompt(session, deps.now ?? new Date);
|
|
24552
|
+
const raw = await deps.askKey(prompt);
|
|
24553
|
+
const parsed = parseResumeKeypress(raw);
|
|
24554
|
+
if (parsed === null)
|
|
24555
|
+
return "aborted";
|
|
24556
|
+
deps.write(`
|
|
24557
|
+
`);
|
|
24558
|
+
return parsed;
|
|
24559
|
+
}
|
|
24560
|
+
async function readOneKeypress(prompt, stdin = process.stdin, stdout = process.stdout) {
|
|
24561
|
+
stdout.write(prompt);
|
|
24562
|
+
const wasRaw = stdin.isRaw;
|
|
24563
|
+
if (typeof stdin.setRawMode === "function")
|
|
24564
|
+
stdin.setRawMode(true);
|
|
24565
|
+
stdin.resume();
|
|
24566
|
+
return await new Promise((resolve4) => {
|
|
24567
|
+
const onData = (chunk) => {
|
|
24568
|
+
stdin.off("data", onData);
|
|
24569
|
+
if (typeof stdin.setRawMode === "function")
|
|
24570
|
+
stdin.setRawMode(wasRaw ?? false);
|
|
24571
|
+
stdin.pause();
|
|
24572
|
+
resolve4(typeof chunk === "string" ? chunk : chunk.toString("utf8"));
|
|
24573
|
+
};
|
|
24574
|
+
stdin.once("data", onData);
|
|
24575
|
+
});
|
|
24576
|
+
}
|
|
24577
|
+
async function promptResumeInteractive(session) {
|
|
24578
|
+
return promptResume(session, {
|
|
24579
|
+
stdinIsTTY: process.stdin.isTTY === true,
|
|
24580
|
+
write: (text) => {
|
|
24581
|
+
process.stdout.write(text);
|
|
24582
|
+
},
|
|
24583
|
+
askKey: (prompt) => readOneKeypress(prompt)
|
|
24584
|
+
});
|
|
24585
|
+
}
|
|
24586
|
+
|
|
24021
24587
|
// src/onboarding.ts
|
|
24022
24588
|
var INTRO_ENV_SKIP = "FREEPI_NO_INTRO";
|
|
24023
24589
|
var INTRO_TEXT = `
|
|
@@ -24026,8 +24592,8 @@ Welcome to free-pi — a free coding agent in your terminal.
|
|
|
24026
24592
|
• It's the pi coding agent, free to use. Ads in the terminal pay for the
|
|
24027
24593
|
inference — ad content never reaches the model.
|
|
24028
24594
|
• Tell it what to build, fix, or explain, like any coding assistant.
|
|
24029
|
-
•
|
|
24030
|
-
with "npx free-pi-cli" — or resume where you left off with
|
|
24595
|
+
• You can run free-pi in more than one terminal. If you close the terminal,
|
|
24596
|
+
relaunch with "npx free-pi-cli" — or resume where you left off with
|
|
24031
24597
|
"npx free-pi-cli --session <id>" (the id prints when you exit).
|
|
24032
24598
|
|
|
24033
24599
|
Set ${INTRO_ENV_SKIP}=1 to skip this next time.
|
|
@@ -24045,12 +24611,12 @@ ${INTRO_TEXT}
|
|
|
24045
24611
|
}
|
|
24046
24612
|
|
|
24047
24613
|
// src/whats-new.ts
|
|
24048
|
-
import { existsSync as
|
|
24049
|
-
import { dirname as
|
|
24614
|
+
import { existsSync as existsSync5, mkdirSync as mkdirSync2, readFileSync as readFileSync4, writeFileSync as writeFileSync2 } from "node:fs";
|
|
24615
|
+
import { dirname as dirname9 } from "node:path";
|
|
24050
24616
|
function maybeWhatsNew(cliVersion, agentDir) {
|
|
24051
24617
|
try {
|
|
24052
24618
|
const path = getLastSeenVersionPath(agentDir);
|
|
24053
|
-
const lastSeen =
|
|
24619
|
+
const lastSeen = existsSync5(path) ? readFileSync4(path, "utf-8").trim() : undefined;
|
|
24054
24620
|
writeLastSeen(path, cliVersion);
|
|
24055
24621
|
if (lastSeen === undefined)
|
|
24056
24622
|
return;
|
|
@@ -24066,7 +24632,7 @@ function maybeWhatsNew(cliVersion, agentDir) {
|
|
|
24066
24632
|
}
|
|
24067
24633
|
}
|
|
24068
24634
|
function writeLastSeen(path, version3) {
|
|
24069
|
-
mkdirSync2(
|
|
24635
|
+
mkdirSync2(dirname9(path), { recursive: true });
|
|
24070
24636
|
writeFileSync2(path, version3, "utf-8");
|
|
24071
24637
|
}
|
|
24072
24638
|
|
|
@@ -24092,7 +24658,8 @@ async function loginFlow(deps, argv = []) {
|
|
|
24092
24658
|
openBrowser: deps.openBrowser,
|
|
24093
24659
|
sleep: deps.sleep,
|
|
24094
24660
|
log: deps.log,
|
|
24095
|
-
jwt: await loadJwt(deps.credentialsPath)
|
|
24661
|
+
jwt: await loadJwt(deps.credentialsPath),
|
|
24662
|
+
credentialsPath: deps.credentialsPath
|
|
24096
24663
|
}, resolved.attribution ?? {});
|
|
24097
24664
|
await saveJwt(deps.credentialsPath, token);
|
|
24098
24665
|
return token;
|
|
@@ -24103,28 +24670,52 @@ async function loginFlow(deps, argv = []) {
|
|
|
24103
24670
|
throw err;
|
|
24104
24671
|
}
|
|
24105
24672
|
}
|
|
24106
|
-
function
|
|
24107
|
-
const flagIndex = argv.indexOf("--session");
|
|
24108
|
-
if (flagIndex === -1)
|
|
24109
|
-
return {};
|
|
24110
|
-
const value = argv[flagIndex + 1];
|
|
24111
|
-
if (!value || value.startsWith("--")) {
|
|
24112
|
-
return { error: "Missing value for --session" };
|
|
24113
|
-
}
|
|
24673
|
+
function validateSessionId(value) {
|
|
24114
24674
|
try {
|
|
24115
24675
|
SessionManager2.inMemory(process.cwd(), { id: value });
|
|
24676
|
+
return;
|
|
24116
24677
|
} catch (err) {
|
|
24117
|
-
return
|
|
24678
|
+
return `Invalid --session value: ${err instanceof Error ? err.message : String(err)}`;
|
|
24118
24679
|
}
|
|
24119
|
-
|
|
24680
|
+
}
|
|
24681
|
+
function reportResumeExposure(deps, jwt2, event) {
|
|
24682
|
+
if (deps.reportResumeExposure) {
|
|
24683
|
+
deps.reportResumeExposure(event);
|
|
24684
|
+
return;
|
|
24685
|
+
}
|
|
24686
|
+
fetch(new URL("/telemetry/experiment", deps.baseUrl), {
|
|
24687
|
+
method: "POST",
|
|
24688
|
+
headers: {
|
|
24689
|
+
authorization: `Bearer ${jwt2}`,
|
|
24690
|
+
"content-type": "application/json"
|
|
24691
|
+
},
|
|
24692
|
+
body: JSON.stringify({ experiment: RESUME_ON_LAUNCH_EXPERIMENT, event }),
|
|
24693
|
+
signal: AbortSignal.timeout(3000)
|
|
24694
|
+
}).catch(() => {});
|
|
24695
|
+
}
|
|
24696
|
+
async function defaultFindLatestSession(cwd, agentDir) {
|
|
24697
|
+
return findLatestSession(cwd, getSessionDir(cwd, agentDir));
|
|
24120
24698
|
}
|
|
24121
24699
|
async function run(deps, argv = process.argv) {
|
|
24122
|
-
const
|
|
24123
|
-
if (
|
|
24124
|
-
deps.log(
|
|
24700
|
+
const resumeFlags = parseResumeFlags(argv);
|
|
24701
|
+
if (resumeFlags.sessionError) {
|
|
24702
|
+
deps.log(resumeFlags.sessionError);
|
|
24125
24703
|
return 1;
|
|
24126
24704
|
}
|
|
24705
|
+
if (resumeFlags.session) {
|
|
24706
|
+
const invalid = validateSessionId(resumeFlags.session);
|
|
24707
|
+
if (invalid) {
|
|
24708
|
+
deps.log(invalid);
|
|
24709
|
+
return 1;
|
|
24710
|
+
}
|
|
24711
|
+
}
|
|
24712
|
+
const pending = await loadPendingLogin(deps.credentialsPath);
|
|
24713
|
+
if (pending)
|
|
24714
|
+
deps.log(`
|
|
24715
|
+
${FINISH_SIGNIN_LINE}
|
|
24716
|
+
`);
|
|
24127
24717
|
let jwt2 = await loadJwt(deps.credentialsPath);
|
|
24718
|
+
let guestHandoffVariant;
|
|
24128
24719
|
let consentCurrent = false;
|
|
24129
24720
|
if (jwt2) {
|
|
24130
24721
|
try {
|
|
@@ -24167,9 +24758,12 @@ async function run(deps, argv = process.argv) {
|
|
|
24167
24758
|
const guest = await startGuestSession(deps.baseUrl, CONSENT_VERSION, attribution);
|
|
24168
24759
|
await saveJwt(deps.credentialsPath, guest.token);
|
|
24169
24760
|
jwt2 = guest.token;
|
|
24170
|
-
deps.log(`
|
|
24761
|
+
deps.log(isHandoffTreatment(guest.guestHandoffVariant) ? `
|
|
24762
|
+
You're on a short guest trial — ads pay for it. ${LOGIN_VALUE_LINE}
|
|
24763
|
+
` : `
|
|
24171
24764
|
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.
|
|
24172
24765
|
`);
|
|
24766
|
+
guestHandoffVariant = guest.guestHandoffVariant;
|
|
24173
24767
|
} catch (err) {
|
|
24174
24768
|
if (!(err instanceof GuestUnavailableError))
|
|
24175
24769
|
throw err;
|
|
@@ -24180,7 +24774,7 @@ Guest trial is not available right now.
|
|
|
24180
24774
|
}
|
|
24181
24775
|
consentCurrent = true;
|
|
24182
24776
|
}
|
|
24183
|
-
const versionCheck = await deps.checkClientVersion(deps.baseUrl, CLI_VERSION);
|
|
24777
|
+
const versionCheck = await deps.checkClientVersion(deps.baseUrl, CLI_VERSION, jwt2);
|
|
24184
24778
|
let updateLatest;
|
|
24185
24779
|
if (versionCheck.action !== "ok") {
|
|
24186
24780
|
const outcome = await deps.maybeSelfUpdate(versionCheck.action, versionCheck.latest);
|
|
@@ -24206,23 +24800,61 @@ Guest trial is not available right now.
|
|
|
24206
24800
|
} catch {}
|
|
24207
24801
|
}
|
|
24208
24802
|
const me = await fetchMe2(deps.baseUrl, jwt2).catch(() => null);
|
|
24803
|
+
if (me)
|
|
24804
|
+
updateAllowanceCache(me);
|
|
24805
|
+
guestHandoffVariant = me?.experiments?.guest_handoff ?? guestHandoffVariant;
|
|
24209
24806
|
const startRefill = await fetchRefillStats(deps.baseUrl, jwt2).catch(() => {
|
|
24210
24807
|
return;
|
|
24211
24808
|
});
|
|
24212
24809
|
const refillSession = sessionFromStats(startRefill);
|
|
24810
|
+
const creditsBack = startRefill ? formatCreditsBackNudge(startRefill) : undefined;
|
|
24811
|
+
if (creditsBack) {
|
|
24812
|
+
deps.log(`
|
|
24813
|
+
${creditsBack}`);
|
|
24814
|
+
reportRefillCardShown({ baseUrl: deps.baseUrl, jwt: jwt2 });
|
|
24815
|
+
}
|
|
24816
|
+
let session = resumeFlags.session;
|
|
24817
|
+
const lookupLatest = deps.findLatestSession ?? defaultFindLatestSession;
|
|
24818
|
+
if (!session && resumeFlags.continueLast && !resumeFlags.startNew) {
|
|
24819
|
+
session = (await lookupLatest(process.cwd(), deps.agentDir))?.id;
|
|
24820
|
+
if (!session)
|
|
24821
|
+
deps.log("No earlier session in this folder; starting a new one.");
|
|
24822
|
+
}
|
|
24823
|
+
const experiment = versionCheck.experiments?.[RESUME_ON_LAUNCH_EXPERIMENT];
|
|
24824
|
+
const variant = experiment?.variant === "treatment" ? "treatment" : "control";
|
|
24825
|
+
const recentHours = recentHoursFromEnv(process.env, experiment?.recent_hours);
|
|
24826
|
+
const interactive = deps.stdinIsTTY ?? process.stdin.isTTY === true;
|
|
24827
|
+
if (!session && !resumeFlags.startNew && !resumeFlags.hasPrompt && variant === "treatment" && interactive) {
|
|
24828
|
+
const latest = await lookupLatest(process.cwd(), deps.agentDir);
|
|
24829
|
+
const now = deps.now?.() ?? new Date;
|
|
24830
|
+
if (latest && isRecentSession(latest.modified, recentHours, now)) {
|
|
24831
|
+
reportResumeExposure(deps, jwt2, "offer_shown");
|
|
24832
|
+
const answer = await (deps.promptResume ?? promptResumeInteractive)(latest);
|
|
24833
|
+
if (answer === "aborted") {
|
|
24834
|
+
deps.log("Aborted.");
|
|
24835
|
+
return 1;
|
|
24836
|
+
}
|
|
24837
|
+
if (answer) {
|
|
24838
|
+
reportResumeExposure(deps, jwt2, "offer_accepted");
|
|
24839
|
+
session = latest.id;
|
|
24840
|
+
}
|
|
24841
|
+
}
|
|
24842
|
+
}
|
|
24213
24843
|
const finalSessionId = await deps.launchPi({
|
|
24214
24844
|
baseUrl: deps.baseUrl,
|
|
24215
24845
|
jwt: jwt2,
|
|
24216
24846
|
agentDir: deps.agentDir,
|
|
24217
24847
|
credentialsPath: deps.credentialsPath,
|
|
24218
|
-
session
|
|
24848
|
+
session,
|
|
24219
24849
|
model: versionCheck.model,
|
|
24220
24850
|
models: versionCheck.models,
|
|
24221
24851
|
fusion: versionCheck.fusion,
|
|
24222
24852
|
updateLatest,
|
|
24223
24853
|
getGravityContext,
|
|
24224
24854
|
guest: Boolean(me?.guest),
|
|
24225
|
-
refillCard: refillSession
|
|
24855
|
+
refillCard: refillSession,
|
|
24856
|
+
allowanceLine: allowanceLine(),
|
|
24857
|
+
guestHandoffVariant
|
|
24226
24858
|
});
|
|
24227
24859
|
const endRefill = await fetchRefillStats(deps.baseUrl, jwt2).catch(() => {
|
|
24228
24860
|
return;
|
|
@@ -24277,17 +24909,18 @@ async function maybeSelfUpdate(action2, latest, deps) {
|
|
|
24277
24909
|
}
|
|
24278
24910
|
|
|
24279
24911
|
// src/update-check.ts
|
|
24280
|
-
async function checkClientVersion(baseUrl, cliVersion, fetchImpl = fetch) {
|
|
24912
|
+
async function checkClientVersion(baseUrl, cliVersion, fetchImpl = fetch, jwt2) {
|
|
24281
24913
|
try {
|
|
24282
24914
|
const res = await fetchImpl(new URL("/client-version", baseUrl), {
|
|
24283
|
-
signal: AbortSignal.timeout(3000)
|
|
24915
|
+
signal: AbortSignal.timeout(3000),
|
|
24916
|
+
headers: jwt2 ? { authorization: `Bearer ${jwt2}` } : undefined
|
|
24284
24917
|
});
|
|
24285
24918
|
if (!res.ok)
|
|
24286
24919
|
return { action: "ok" };
|
|
24287
24920
|
const parsed = ClientVersionResponseSchema.safeParse(await res.json());
|
|
24288
24921
|
if (!parsed.success)
|
|
24289
24922
|
return { action: "ok" };
|
|
24290
|
-
const { min, latest, model, models, notice, fusion } = parsed.data;
|
|
24923
|
+
const { min, latest, model, models, notice, fusion, experiments } = parsed.data;
|
|
24291
24924
|
const extra = {};
|
|
24292
24925
|
if (model !== undefined)
|
|
24293
24926
|
extra.model = model;
|
|
@@ -24297,6 +24930,8 @@ async function checkClientVersion(baseUrl, cliVersion, fetchImpl = fetch) {
|
|
|
24297
24930
|
extra.notice = notice;
|
|
24298
24931
|
if (fusion !== undefined)
|
|
24299
24932
|
extra.fusion = fusion;
|
|
24933
|
+
if (experiments !== undefined)
|
|
24934
|
+
extra.experiments = experiments;
|
|
24300
24935
|
if (compareVersions(cliVersion, min) === -1)
|
|
24301
24936
|
return { action: "block", latest, ...extra };
|
|
24302
24937
|
if (compareVersions(cliVersion, latest) === -1)
|
|
@@ -24364,8 +24999,8 @@ ${HELP_TEXT}`);
|
|
|
24364
24999
|
sleep: (ms) => new Promise((resolve4) => setTimeout(resolve4, ms)),
|
|
24365
25000
|
launchPi,
|
|
24366
25001
|
log: (message) => console.log(message),
|
|
24367
|
-
checkClientVersion,
|
|
24368
|
-
startGravityPixel: (jwt2) => startGravityPixel(jwt2,
|
|
25002
|
+
checkClientVersion: (baseUrl, cliVersion, jwt2) => checkClientVersion(baseUrl, cliVersion, fetch, jwt2),
|
|
25003
|
+
startGravityPixel: (jwt2) => startGravityPixel(jwt2, join11(dirname10(agentDir), "gravity")),
|
|
24369
25004
|
maybeSelfUpdate: (action2, latest) => maybeSelfUpdate(action2, latest, {
|
|
24370
25005
|
env: process.env,
|
|
24371
25006
|
autoUpdateEnabled: () => readAutoUpdate(getConfigPath()),
|