@idlekit/cli 0.4.0 → 0.5.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/dist/main.js +762 -386
- package/dist/main.js.map +21 -16
- package/dist/runtime/workers/entry.js +365 -7
- package/dist/runtime/workers/entry.js.map +12 -9
- package/package.json +2 -2
package/dist/main.js
CHANGED
|
@@ -8397,7 +8397,7 @@ var init_validate = __esm(() => {
|
|
|
8397
8397
|
OFFLINE_ACTORS = new Set(["player", "automation"]);
|
|
8398
8398
|
});
|
|
8399
8399
|
|
|
8400
|
-
// ../../node_modules/.bun/typia@
|
|
8400
|
+
// ../../node_modules/.bun/typia@15.1.0+367f7514c87f1df1/node_modules/typia/lib/internal/_validateReport.mjs
|
|
8401
8401
|
var _validateReport = (array) => {
|
|
8402
8402
|
const isAncestor = (ancestor, descendant) => descendant === ancestor || descendant.startsWith(`${ancestor}.`) || descendant.startsWith(`${ancestor}[`);
|
|
8403
8403
|
const reportable = (path) => {
|
|
@@ -8422,7 +8422,7 @@ var _validateReport = (array) => {
|
|
|
8422
8422
|
};
|
|
8423
8423
|
var init__validateReport = () => {};
|
|
8424
8424
|
|
|
8425
|
-
// ../../node_modules/.bun/typia@
|
|
8425
|
+
// ../../node_modules/.bun/typia@15.1.0+367f7514c87f1df1/node_modules/typia/lib/internal/_createStandardSchema.mjs
|
|
8426
8426
|
var _createStandardSchema = (fn) => Object.assign(fn, { "~standard": {
|
|
8427
8427
|
version: 1,
|
|
8428
8428
|
vendor: "typia",
|
|
@@ -18414,7 +18414,7 @@ var init_checks3 = __esm(() => {
|
|
|
18414
18414
|
FAIL_ON = ["error", "warn", "inconclusive", "unsupported"];
|
|
18415
18415
|
});
|
|
18416
18416
|
|
|
18417
|
-
// ../../node_modules/.bun/typia@
|
|
18417
|
+
// ../../node_modules/.bun/typia@15.1.0+367f7514c87f1df1/node_modules/typia/lib/internal/_accessExpressionAsString.mjs
|
|
18418
18418
|
var _accessExpressionAsString = (str) => variable(str) ? `.${str}` : `[${JSON.stringify(str)}]`, variable = (str) => reserved(str) === false && /^[a-zA-Z_$][a-zA-Z_$0-9]*$/g.test(str), reserved = (str) => RESERVED.has(str), RESERVED;
|
|
18419
18419
|
var init__accessExpressionAsString = __esm(() => {
|
|
18420
18420
|
RESERVED = /* @__PURE__ */ new Set([
|
|
@@ -18961,7 +18961,7 @@ var init_judge = __esm(() => {
|
|
|
18961
18961
|
// ../core/dist/sim/analysis/flow.js
|
|
18962
18962
|
var init_flow = () => {};
|
|
18963
18963
|
|
|
18964
|
-
// ../../node_modules/.bun/typia@
|
|
18964
|
+
// ../../node_modules/.bun/typia@15.1.0+367f7514c87f1df1/node_modules/typia/lib/internal/_isTypeUint32.mjs
|
|
18965
18965
|
var _isTypeUint32 = (value) => Math.floor(value) === value && MINIMUM <= value && value <= MAXIMUM, MINIMUM = 0, MAXIMUM;
|
|
18966
18966
|
var init__isTypeUint32 = __esm(() => {
|
|
18967
18967
|
MAXIMUM = 2 ** 32 - 1;
|
|
@@ -19440,6 +19440,363 @@ function compareScenarios(args) {
|
|
|
19440
19440
|
};
|
|
19441
19441
|
}
|
|
19442
19442
|
|
|
19443
|
+
// ../core/dist/ltv/projection.js
|
|
19444
|
+
function clamp012(v) {
|
|
19445
|
+
if (v < 0)
|
|
19446
|
+
return 0;
|
|
19447
|
+
if (v > 1)
|
|
19448
|
+
return 1;
|
|
19449
|
+
return v;
|
|
19450
|
+
}
|
|
19451
|
+
function deriveMonetizationConfig(scenario) {
|
|
19452
|
+
const m = scenario.monetization;
|
|
19453
|
+
const q = (m?.uncertainty?.quantiles ?? [0.5, 0.9]).filter((x) => typeof x === "number");
|
|
19454
|
+
const quantiles = q.length > 0 ? [...new Set(q)].sort((a, b) => a - b) : [0.5, 0.9];
|
|
19455
|
+
return {
|
|
19456
|
+
cohorts: {
|
|
19457
|
+
baseUsers: m?.cohorts?.baseUsers ?? 1
|
|
19458
|
+
},
|
|
19459
|
+
retention: {
|
|
19460
|
+
d1: m?.retention?.d1 ?? 0.42,
|
|
19461
|
+
d7: m?.retention?.d7 ?? 0.2,
|
|
19462
|
+
d30: m?.retention?.d30 ?? 0.09,
|
|
19463
|
+
d90: m?.retention?.d90 ?? 0.04,
|
|
19464
|
+
longTailDailyDecay: m?.retention?.longTailDailyDecay ?? 0.02
|
|
19465
|
+
},
|
|
19466
|
+
revenue: {
|
|
19467
|
+
payerConversion: m?.revenue?.payerConversion ?? 0.03,
|
|
19468
|
+
arppuDaily: m?.revenue?.arppuDaily ?? 0.7,
|
|
19469
|
+
adArpDau: m?.revenue?.adArpDau ?? 0.02,
|
|
19470
|
+
platformFeeRate: m?.revenue?.platformFeeRate ?? 0.3,
|
|
19471
|
+
grossMarginRate: m?.revenue?.grossMarginRate ?? 0.92,
|
|
19472
|
+
progressionRevenueLift: m?.revenue?.progressionRevenueLift ?? 0.4,
|
|
19473
|
+
progressionLogSpan: m?.revenue?.progressionLogSpan ?? 6
|
|
19474
|
+
},
|
|
19475
|
+
acquisition: {
|
|
19476
|
+
cpi: m?.acquisition?.cpi ?? 0
|
|
19477
|
+
},
|
|
19478
|
+
uncertainty: {
|
|
19479
|
+
enabled: m?.uncertainty?.enabled ?? false,
|
|
19480
|
+
draws: m?.uncertainty?.draws ?? 300,
|
|
19481
|
+
quantiles,
|
|
19482
|
+
seed: m?.uncertainty?.seed,
|
|
19483
|
+
sigma: {
|
|
19484
|
+
retention: m?.uncertainty?.sigma?.retention ?? 0.08,
|
|
19485
|
+
conversion: m?.uncertainty?.sigma?.conversion ?? 0.12,
|
|
19486
|
+
arppu: m?.uncertainty?.sigma?.arppu ?? 0.2,
|
|
19487
|
+
ad: m?.uncertainty?.sigma?.ad ?? 0.15
|
|
19488
|
+
},
|
|
19489
|
+
correlation: {
|
|
19490
|
+
retentionConversion: m?.uncertainty?.correlation?.retentionConversion ?? 0,
|
|
19491
|
+
retentionArppu: m?.uncertainty?.correlation?.retentionArppu ?? 0,
|
|
19492
|
+
retentionAd: m?.uncertainty?.correlation?.retentionAd ?? 0,
|
|
19493
|
+
conversionArppu: m?.uncertainty?.correlation?.conversionArppu ?? 0,
|
|
19494
|
+
conversionAd: m?.uncertainty?.correlation?.conversionAd ?? 0,
|
|
19495
|
+
arppuAd: m?.uncertainty?.correlation?.arppuAd ?? 0
|
|
19496
|
+
}
|
|
19497
|
+
}
|
|
19498
|
+
};
|
|
19499
|
+
}
|
|
19500
|
+
function retentionAtDay(ret, day) {
|
|
19501
|
+
if (day <= 0)
|
|
19502
|
+
return 1;
|
|
19503
|
+
if (day <= 1)
|
|
19504
|
+
return ret.d1;
|
|
19505
|
+
if (day <= 7)
|
|
19506
|
+
return ret.d1 + (ret.d7 - ret.d1) * (day - 1) / 6;
|
|
19507
|
+
if (day <= 30)
|
|
19508
|
+
return ret.d7 + (ret.d30 - ret.d7) * (day - 7) / 23;
|
|
19509
|
+
if (day <= 90)
|
|
19510
|
+
return ret.d30 + (ret.d90 - ret.d30) * (day - 30) / 60;
|
|
19511
|
+
const extra = day - 90;
|
|
19512
|
+
return clamp012(ret.d90 * Math.exp(-ret.longTailDailyDecay * extra));
|
|
19513
|
+
}
|
|
19514
|
+
function progressionFactor(startWorthLog10, endWorthLog10, span) {
|
|
19515
|
+
if (!Number.isFinite(startWorthLog10) || !Number.isFinite(endWorthLog10) || !Number.isFinite(span) || span <= 0) {
|
|
19516
|
+
return 0;
|
|
19517
|
+
}
|
|
19518
|
+
return clamp012((endWorthLog10 - startWorthLog10) / span);
|
|
19519
|
+
}
|
|
19520
|
+
function estimateLtvPerUser(args) {
|
|
19521
|
+
const { config, horizonDays } = args;
|
|
19522
|
+
const progression = clamp012(args.progression);
|
|
19523
|
+
const arppuDaily = config.revenue.arppuDaily * (1 + progression * config.revenue.progressionRevenueLift);
|
|
19524
|
+
const adArpDau = config.revenue.adArpDau * (1 + progression * config.revenue.progressionRevenueLift * 0.5);
|
|
19525
|
+
const payerConversion = config.revenue.payerConversion;
|
|
19526
|
+
const fullDays = Math.floor(Math.max(0, horizonDays));
|
|
19527
|
+
const remainder = Math.max(0, horizonDays - fullDays);
|
|
19528
|
+
let gross = 0;
|
|
19529
|
+
for (let day = 1;day <= fullDays; day++) {
|
|
19530
|
+
const active = retentionAtDay(config.retention, day);
|
|
19531
|
+
gross += active * (payerConversion * arppuDaily + adArpDau);
|
|
19532
|
+
}
|
|
19533
|
+
if (remainder > 0) {
|
|
19534
|
+
const active = retentionAtDay(config.retention, fullDays + 1);
|
|
19535
|
+
gross += remainder * active * (payerConversion * arppuDaily + adArpDau);
|
|
19536
|
+
}
|
|
19537
|
+
const net = gross * (1 - config.revenue.platformFeeRate) * config.revenue.grossMarginRate;
|
|
19538
|
+
const ltv = net - config.acquisition.cpi;
|
|
19539
|
+
return {
|
|
19540
|
+
cumulativeGrossRevenuePerUser: gross,
|
|
19541
|
+
cumulativeNetRevenuePerUser: net,
|
|
19542
|
+
cumulativeLtvPerUser: ltv
|
|
19543
|
+
};
|
|
19544
|
+
}
|
|
19545
|
+
|
|
19546
|
+
// ../core/dist/ltv/uncertainty.js
|
|
19547
|
+
function clamp013(v) {
|
|
19548
|
+
if (v < 0)
|
|
19549
|
+
return 0;
|
|
19550
|
+
if (v > 1)
|
|
19551
|
+
return 1;
|
|
19552
|
+
return v;
|
|
19553
|
+
}
|
|
19554
|
+
function clampCorrelation(v) {
|
|
19555
|
+
if (!Number.isFinite(v))
|
|
19556
|
+
return 0;
|
|
19557
|
+
if (v < -1)
|
|
19558
|
+
return -1;
|
|
19559
|
+
if (v > 1)
|
|
19560
|
+
return 1;
|
|
19561
|
+
return v;
|
|
19562
|
+
}
|
|
19563
|
+
function randNormal(rng) {
|
|
19564
|
+
const u1 = Math.max(0.000000000001, rng());
|
|
19565
|
+
const u2 = Math.max(0.000000000001, rng());
|
|
19566
|
+
return Math.sqrt(-2 * Math.log(u1)) * Math.cos(2 * Math.PI * u2);
|
|
19567
|
+
}
|
|
19568
|
+
function buildCorrelationMatrix(base) {
|
|
19569
|
+
const rc = clampCorrelation(base.retentionConversion);
|
|
19570
|
+
const ra = clampCorrelation(base.retentionArppu);
|
|
19571
|
+
const rd = clampCorrelation(base.retentionAd);
|
|
19572
|
+
const ca = clampCorrelation(base.conversionArppu);
|
|
19573
|
+
const cd = clampCorrelation(base.conversionAd);
|
|
19574
|
+
const ad = clampCorrelation(base.arppuAd);
|
|
19575
|
+
return [
|
|
19576
|
+
[1, rc, ra, rd],
|
|
19577
|
+
[rc, 1, ca, cd],
|
|
19578
|
+
[ra, ca, 1, ad],
|
|
19579
|
+
[rd, cd, ad, 1]
|
|
19580
|
+
];
|
|
19581
|
+
}
|
|
19582
|
+
function withOffDiagonalScale(matrix, alpha) {
|
|
19583
|
+
return matrix.map((row, i) => row.map((v, j) => {
|
|
19584
|
+
if (i === j)
|
|
19585
|
+
return 1;
|
|
19586
|
+
return v * alpha;
|
|
19587
|
+
}));
|
|
19588
|
+
}
|
|
19589
|
+
function cholesky4(matrix) {
|
|
19590
|
+
const n = 4;
|
|
19591
|
+
const l = Array.from({ length: n }, () => Array.from({ length: n }, () => 0));
|
|
19592
|
+
for (let i = 0;i < n; i++) {
|
|
19593
|
+
for (let j = 0;j <= i; j++) {
|
|
19594
|
+
let sum = 0;
|
|
19595
|
+
for (let k = 0;k < j; k++) {
|
|
19596
|
+
sum += (l[i]?.[k] ?? 0) * (l[j]?.[k] ?? 0);
|
|
19597
|
+
}
|
|
19598
|
+
if (i === j) {
|
|
19599
|
+
const val = (matrix[i]?.[i] ?? 0) - sum;
|
|
19600
|
+
if (val <= 0.000000001)
|
|
19601
|
+
return null;
|
|
19602
|
+
l[i][j] = Math.sqrt(val);
|
|
19603
|
+
} else {
|
|
19604
|
+
const denom = l[j]?.[j] ?? 0;
|
|
19605
|
+
if (Math.abs(denom) < 0.000000001)
|
|
19606
|
+
return null;
|
|
19607
|
+
l[i][j] = ((matrix[i]?.[j] ?? 0) - sum) / denom;
|
|
19608
|
+
}
|
|
19609
|
+
}
|
|
19610
|
+
}
|
|
19611
|
+
return l;
|
|
19612
|
+
}
|
|
19613
|
+
function multiplyLowerTriangular(l, z) {
|
|
19614
|
+
const out = [0, 0, 0, 0];
|
|
19615
|
+
for (let i = 0;i < 4; i++) {
|
|
19616
|
+
let sum = 0;
|
|
19617
|
+
for (let j = 0;j <= i; j++) {
|
|
19618
|
+
sum += (l[i]?.[j] ?? 0) * (z[j] ?? 0);
|
|
19619
|
+
}
|
|
19620
|
+
out[i] = sum;
|
|
19621
|
+
}
|
|
19622
|
+
return out;
|
|
19623
|
+
}
|
|
19624
|
+
function sampleCorrelatedNormals(rng, corr) {
|
|
19625
|
+
const z = [randNormal(rng), randNormal(rng), randNormal(rng), randNormal(rng)];
|
|
19626
|
+
const matrix = buildCorrelationMatrix(corr);
|
|
19627
|
+
let l = cholesky4(matrix);
|
|
19628
|
+
let alpha = 0.99;
|
|
19629
|
+
while (!l && alpha > 0.000001) {
|
|
19630
|
+
l = cholesky4(withOffDiagonalScale(matrix, alpha));
|
|
19631
|
+
alpha *= 0.8;
|
|
19632
|
+
}
|
|
19633
|
+
if (!l) {
|
|
19634
|
+
return {
|
|
19635
|
+
retention: z[0] ?? 0,
|
|
19636
|
+
conversion: z[1] ?? 0,
|
|
19637
|
+
arppu: z[2] ?? 0,
|
|
19638
|
+
ad: z[3] ?? 0
|
|
19639
|
+
};
|
|
19640
|
+
}
|
|
19641
|
+
const c = multiplyLowerTriangular(l, z);
|
|
19642
|
+
return {
|
|
19643
|
+
retention: c[0] ?? 0,
|
|
19644
|
+
conversion: c[1] ?? 0,
|
|
19645
|
+
arppu: c[2] ?? 0,
|
|
19646
|
+
ad: c[3] ?? 0
|
|
19647
|
+
};
|
|
19648
|
+
}
|
|
19649
|
+
function applyLogNormalShock(mean, sigma, z) {
|
|
19650
|
+
if (sigma <= 0)
|
|
19651
|
+
return mean;
|
|
19652
|
+
return mean * Math.exp(sigma * z - 0.5 * sigma * sigma);
|
|
19653
|
+
}
|
|
19654
|
+
function makeUncertainConfig(base, rng) {
|
|
19655
|
+
const shock = sampleCorrelatedNormals(rng, base.uncertainty.correlation);
|
|
19656
|
+
const d1 = clamp013(applyLogNormalShock(base.retention.d1, base.uncertainty.sigma.retention, shock.retention));
|
|
19657
|
+
const d7 = Math.min(d1, clamp013(applyLogNormalShock(base.retention.d7, base.uncertainty.sigma.retention, shock.retention)));
|
|
19658
|
+
const d30 = Math.min(d7, clamp013(applyLogNormalShock(base.retention.d30, base.uncertainty.sigma.retention, shock.retention)));
|
|
19659
|
+
const d90 = Math.min(d30, clamp013(applyLogNormalShock(base.retention.d90, base.uncertainty.sigma.retention, shock.retention)));
|
|
19660
|
+
return {
|
|
19661
|
+
...base,
|
|
19662
|
+
retention: {
|
|
19663
|
+
...base.retention,
|
|
19664
|
+
d1,
|
|
19665
|
+
d7,
|
|
19666
|
+
d30,
|
|
19667
|
+
d90
|
|
19668
|
+
},
|
|
19669
|
+
revenue: {
|
|
19670
|
+
...base.revenue,
|
|
19671
|
+
payerConversion: clamp013(applyLogNormalShock(base.revenue.payerConversion, base.uncertainty.sigma.conversion, shock.conversion)),
|
|
19672
|
+
arppuDaily: Math.max(0, applyLogNormalShock(base.revenue.arppuDaily, base.uncertainty.sigma.arppu, shock.arppu)),
|
|
19673
|
+
adArpDau: Math.max(0, applyLogNormalShock(base.revenue.adArpDau, base.uncertainty.sigma.ad, shock.ad))
|
|
19674
|
+
}
|
|
19675
|
+
};
|
|
19676
|
+
}
|
|
19677
|
+
function quantileFromSorted(sorted, q) {
|
|
19678
|
+
if (sorted.length === 0)
|
|
19679
|
+
return 0;
|
|
19680
|
+
const idx = Math.max(0, Math.min(sorted.length - 1, Math.floor((sorted.length - 1) * q)));
|
|
19681
|
+
return sorted[idx] ?? 0;
|
|
19682
|
+
}
|
|
19683
|
+
function estimateLtvDistribution(args) {
|
|
19684
|
+
const rng = mulberry32(args.seed);
|
|
19685
|
+
const grossSamples = [];
|
|
19686
|
+
const netSamples = [];
|
|
19687
|
+
const ltvSamples = [];
|
|
19688
|
+
const draws = Math.max(1, args.draws);
|
|
19689
|
+
for (let i = 0;i < draws; i++) {
|
|
19690
|
+
const sampled = makeUncertainConfig(args.config, rng);
|
|
19691
|
+
const point = estimateLtvPerUser({
|
|
19692
|
+
config: sampled,
|
|
19693
|
+
horizonDays: args.horizonDays,
|
|
19694
|
+
progression: args.progression
|
|
19695
|
+
});
|
|
19696
|
+
grossSamples.push(point.cumulativeGrossRevenuePerUser);
|
|
19697
|
+
netSamples.push(point.cumulativeNetRevenuePerUser);
|
|
19698
|
+
ltvSamples.push(point.cumulativeLtvPerUser);
|
|
19699
|
+
}
|
|
19700
|
+
const avg = (arr) => arr.reduce((a, b) => a + b, 0) / Math.max(1, arr.length);
|
|
19701
|
+
const sortedLtv = [...ltvSamples].sort((a, b) => a - b);
|
|
19702
|
+
const byQuantile = {};
|
|
19703
|
+
for (const q of args.quantiles) {
|
|
19704
|
+
byQuantile[`q${Math.round(q * 100)}`] = quantileFromSorted(sortedLtv, q);
|
|
19705
|
+
}
|
|
19706
|
+
return {
|
|
19707
|
+
mean: {
|
|
19708
|
+
cumulativeGrossRevenuePerUser: avg(grossSamples),
|
|
19709
|
+
cumulativeNetRevenuePerUser: avg(netSamples),
|
|
19710
|
+
cumulativeLtvPerUser: avg(ltvSamples)
|
|
19711
|
+
},
|
|
19712
|
+
quantiles: byQuantile
|
|
19713
|
+
};
|
|
19714
|
+
}
|
|
19715
|
+
var init_uncertainty = () => {};
|
|
19716
|
+
|
|
19717
|
+
// ../core/dist/ltv/public.js
|
|
19718
|
+
function assertMonetization(monetization) {
|
|
19719
|
+
const valid = validateScenarioV1({
|
|
19720
|
+
schemaVersion: 1,
|
|
19721
|
+
unit: { code: "LTV" },
|
|
19722
|
+
policy: { mode: "drop" },
|
|
19723
|
+
model: { id: "ltv-input", version: 1 },
|
|
19724
|
+
initial: { wallet: { unit: "LTV", amount: "0" } },
|
|
19725
|
+
clock: { stepSec: 1, durationSec: 1 },
|
|
19726
|
+
monetization
|
|
19727
|
+
});
|
|
19728
|
+
if (!valid.ok)
|
|
19729
|
+
throw new RangeError(valid.issues.map((issue) => issue.message).join("; "));
|
|
19730
|
+
}
|
|
19731
|
+
function assertQuantileValues(quantiles) {
|
|
19732
|
+
if (quantiles.length === 0)
|
|
19733
|
+
throw new RangeError("quantiles must be nonempty and in 0..1");
|
|
19734
|
+
for (const q of quantiles) {
|
|
19735
|
+
if (!Number.isFinite(q) || q < 0 || q > 1)
|
|
19736
|
+
throw new RangeError("quantiles must be nonempty and in 0..1");
|
|
19737
|
+
}
|
|
19738
|
+
}
|
|
19739
|
+
function assertConfig(config) {
|
|
19740
|
+
const { retention, ...other } = config;
|
|
19741
|
+
for (const day of ["d1", "d7", "d30", "d90"]) {
|
|
19742
|
+
if (!Number.isFinite(retention[day]) || retention[day] < 0 || retention[day] > 1) {
|
|
19743
|
+
throw new RangeError(`retention.${day} must be finite and in 0..1`);
|
|
19744
|
+
}
|
|
19745
|
+
}
|
|
19746
|
+
if (!Number.isFinite(retention.longTailDailyDecay) || retention.longTailDailyDecay < 0) {
|
|
19747
|
+
throw new RangeError("retention.longTailDailyDecay must be finite and >= 0");
|
|
19748
|
+
}
|
|
19749
|
+
const { quantiles, ...uncertainty } = other.uncertainty;
|
|
19750
|
+
assertQuantileValues(quantiles);
|
|
19751
|
+
assertMonetization({ ...other, uncertainty });
|
|
19752
|
+
}
|
|
19753
|
+
function deriveMonetizationConfig2(scenario) {
|
|
19754
|
+
assertMonetization(scenario.monetization ?? {});
|
|
19755
|
+
const config = deriveMonetizationConfig(scenario);
|
|
19756
|
+
assertConfig(config);
|
|
19757
|
+
return config;
|
|
19758
|
+
}
|
|
19759
|
+
function assertPoint(args) {
|
|
19760
|
+
assertConfig(args.config);
|
|
19761
|
+
if (!Number.isFinite(args.horizonDays) || args.horizonDays < 0 || args.horizonDays > 36500) {
|
|
19762
|
+
throw new RangeError("horizonDays must be finite and in 0..36500");
|
|
19763
|
+
}
|
|
19764
|
+
if (!Number.isFinite(args.progression))
|
|
19765
|
+
throw new RangeError("progression must be finite");
|
|
19766
|
+
}
|
|
19767
|
+
function finiteResult(result) {
|
|
19768
|
+
if (!Object.values(result).every((value) => typeof value === "number" && Number.isFinite(value))) {
|
|
19769
|
+
throw new RangeError("LTV calculation overflowed; reduce the input magnitudes");
|
|
19770
|
+
}
|
|
19771
|
+
return result;
|
|
19772
|
+
}
|
|
19773
|
+
function estimateLtvPerUser2(args) {
|
|
19774
|
+
assertPoint(args);
|
|
19775
|
+
return finiteResult(estimateLtvPerUser(args));
|
|
19776
|
+
}
|
|
19777
|
+
function estimateLtvDistribution2(args) {
|
|
19778
|
+
assertPoint(args);
|
|
19779
|
+
if (!Number.isSafeInteger(args.draws) || args.draws < 1 || args.draws > 1e5 || args.draws * Math.max(1, Math.ceil(args.horizonDays)) > 1e7) {
|
|
19780
|
+
throw new RangeError("draws must be 1..100000 and draws \xD7 ceil(horizonDays) at most 10000000");
|
|
19781
|
+
}
|
|
19782
|
+
if (!Number.isFinite(args.seed))
|
|
19783
|
+
throw new RangeError("seed must be finite");
|
|
19784
|
+
if (args.quantiles.length > 101)
|
|
19785
|
+
throw new RangeError("quantiles exceed the 101 distinct percent keys");
|
|
19786
|
+
assertQuantileValues(args.quantiles);
|
|
19787
|
+
if (new Set(args.quantiles.map((q) => Math.round(q * 100))).size !== args.quantiles.length) {
|
|
19788
|
+
throw new RangeError("quantiles must be nonempty, in 0..1 and have distinct rounded percent keys");
|
|
19789
|
+
}
|
|
19790
|
+
const result = estimateLtvDistribution(args);
|
|
19791
|
+
finiteResult(result.mean);
|
|
19792
|
+
finiteResult(result.quantiles);
|
|
19793
|
+
return result;
|
|
19794
|
+
}
|
|
19795
|
+
var init_public = __esm(() => {
|
|
19796
|
+
init_validate();
|
|
19797
|
+
init_uncertainty();
|
|
19798
|
+
});
|
|
19799
|
+
|
|
19443
19800
|
// ../core/dist/index.js
|
|
19444
19801
|
var init_dist2 = __esm(() => {
|
|
19445
19802
|
init_types();
|
|
@@ -19505,6 +19862,7 @@ var init_dist2 = __esm(() => {
|
|
|
19505
19862
|
init_judge();
|
|
19506
19863
|
init_flow();
|
|
19507
19864
|
init_jevAdvisory();
|
|
19865
|
+
init_public();
|
|
19508
19866
|
});
|
|
19509
19867
|
|
|
19510
19868
|
// src/runtime/bun.ts
|
|
@@ -19570,7 +19928,7 @@ var init_bun = __esm(() => {
|
|
|
19570
19928
|
var require_package = __commonJS(function(exports, module) {
|
|
19571
19929
|
module.exports = {
|
|
19572
19930
|
name: "@idlekit/cli",
|
|
19573
|
-
version: "0.
|
|
19931
|
+
version: "0.5.1",
|
|
19574
19932
|
description: "Bun-first idk CLI for idle game scenario validation, simulation, tuning, and reporting.",
|
|
19575
19933
|
license: "MIT",
|
|
19576
19934
|
type: "module",
|
|
@@ -19623,7 +19981,7 @@ var require_package = __commonJS(function(exports, module) {
|
|
|
19623
19981
|
"@clack/prompts": "1.8.1",
|
|
19624
19982
|
"@gunshi/plugin-completion": "0.37.3",
|
|
19625
19983
|
"@gunshi/plugin-suggestion": "0.37.3",
|
|
19626
|
-
"@idlekit/core": "0.
|
|
19984
|
+
"@idlekit/core": "0.5.0",
|
|
19627
19985
|
ajv: "^8.20.0",
|
|
19628
19986
|
gunshi: "0.37.3",
|
|
19629
19987
|
"proper-lockfile": "4.1.2",
|
|
@@ -35201,8 +35559,10 @@ async function readScenarioFileWithMeta(path) {
|
|
|
35201
35559
|
}
|
|
35202
35560
|
|
|
35203
35561
|
// src/io/writeOutput.ts
|
|
35562
|
+
init_errors3();
|
|
35204
35563
|
init_bun();
|
|
35205
35564
|
import { resolve as resolve5 } from "path";
|
|
35565
|
+
import { realpath, stat } from "fs/promises";
|
|
35206
35566
|
function toCsv(data) {
|
|
35207
35567
|
if (!Array.isArray(data)) {
|
|
35208
35568
|
return `value
|
|
@@ -35276,6 +35636,27 @@ async function writeOutput(args) {
|
|
|
35276
35636
|
return;
|
|
35277
35637
|
}
|
|
35278
35638
|
const abs = resolve5(args.outPath);
|
|
35639
|
+
for (const input of args.protectedInputs ?? []) {
|
|
35640
|
+
const source = resolve5(input);
|
|
35641
|
+
let same = source === abs;
|
|
35642
|
+
if (!same) {
|
|
35643
|
+
try {
|
|
35644
|
+
const [outputReal, inputReal, outputStat, inputStat] = await Promise.all([
|
|
35645
|
+
realpath(abs),
|
|
35646
|
+
realpath(source),
|
|
35647
|
+
stat(abs, { bigint: true }),
|
|
35648
|
+
stat(source, { bigint: true })
|
|
35649
|
+
]);
|
|
35650
|
+
same = outputReal === inputReal || outputStat.ino !== 0n && outputStat.dev === inputStat.dev && outputStat.ino === inputStat.ino;
|
|
35651
|
+
} catch (error) {
|
|
35652
|
+
const code = error.code;
|
|
35653
|
+
if (code !== "ENOENT" && code !== "ENOTDIR")
|
|
35654
|
+
throw error;
|
|
35655
|
+
}
|
|
35656
|
+
}
|
|
35657
|
+
if (same)
|
|
35658
|
+
throw usageError(`Output path must not overwrite an input file: ${input}`);
|
|
35659
|
+
}
|
|
35279
35660
|
await writeTextFile(abs, body);
|
|
35280
35661
|
console.log(`Wrote ${args.format} output to ${abs}`);
|
|
35281
35662
|
}
|
|
@@ -36449,14 +36830,23 @@ init_outputMeta();
|
|
|
36449
36830
|
import { extname as extname3, resolve as resolve8 } from "path";
|
|
36450
36831
|
|
|
36451
36832
|
// src/lib/ltvCalibration.ts
|
|
36452
|
-
function
|
|
36833
|
+
function assertFiniteNumbers(value, path) {
|
|
36834
|
+
if (typeof value === "number" && !Number.isFinite(value)) {
|
|
36835
|
+
throw new RangeError(`Calibration requires finite numbers; numeric overflow at ${path}`);
|
|
36836
|
+
}
|
|
36837
|
+
if (typeof value === "object" && value !== null) {
|
|
36838
|
+
for (const [key, child] of Object.entries(value))
|
|
36839
|
+
assertFiniteNumbers(child, `${path}.${key}`);
|
|
36840
|
+
}
|
|
36841
|
+
}
|
|
36842
|
+
function clamp014(v) {
|
|
36453
36843
|
if (v < 0)
|
|
36454
36844
|
return 0;
|
|
36455
36845
|
if (v > 1)
|
|
36456
36846
|
return 1;
|
|
36457
36847
|
return v;
|
|
36458
36848
|
}
|
|
36459
|
-
function
|
|
36849
|
+
function clampCorrelation2(v) {
|
|
36460
36850
|
if (!Number.isFinite(v))
|
|
36461
36851
|
return 0;
|
|
36462
36852
|
if (v < -1)
|
|
@@ -36521,13 +36911,13 @@ function estimateCorrelation(x, y) {
|
|
|
36521
36911
|
varianceY
|
|
36522
36912
|
};
|
|
36523
36913
|
}
|
|
36524
|
-
const raw =
|
|
36914
|
+
const raw = clampCorrelation2(cov / Math.sqrt(vx) / Math.sqrt(vy));
|
|
36525
36915
|
const ci95 = 1.96 / Math.sqrt(Math.max(1, n - 3));
|
|
36526
|
-
const sampleConfidence =
|
|
36916
|
+
const sampleConfidence = clamp014(1 - ci95);
|
|
36527
36917
|
const varianceConfidenceX = varianceX / (varianceX + 0.01);
|
|
36528
36918
|
const varianceConfidenceY = varianceY / (varianceY + 0.01);
|
|
36529
36919
|
const varianceConfidence = Math.sqrt(varianceConfidenceX * varianceConfidenceY);
|
|
36530
|
-
const confidence =
|
|
36920
|
+
const confidence = clamp014(sampleConfidence * varianceConfidence);
|
|
36531
36921
|
const shrinkage = confidence;
|
|
36532
36922
|
const value = raw * shrinkage;
|
|
36533
36923
|
return {
|
|
@@ -36601,7 +36991,7 @@ function calibrateMonetization(rows) {
|
|
|
36601
36991
|
const userAdArpDau = [];
|
|
36602
36992
|
for (const user of users.values()) {
|
|
36603
36993
|
const activeDays = Math.max(1, user.days.size);
|
|
36604
|
-
const retentionScore =
|
|
36994
|
+
const retentionScore = clamp014(activeDays / 30);
|
|
36605
36995
|
const conversionScore = user.iap > 0 ? 1 : 0;
|
|
36606
36996
|
const arppuScore = user.iap > 0 ? user.iap / activeDays : 0;
|
|
36607
36997
|
const adScore = user.ad / activeDays;
|
|
@@ -36642,20 +37032,20 @@ function calibrateMonetization(rows) {
|
|
|
36642
37032
|
conversionAd: estimatedCorrelationPair.conversionAd.confidence,
|
|
36643
37033
|
arppuAd: estimatedCorrelationPair.arppuAd.confidence
|
|
36644
37034
|
};
|
|
36645
|
-
|
|
37035
|
+
const result = {
|
|
36646
37036
|
monetization: {
|
|
36647
37037
|
cohorts: {
|
|
36648
37038
|
baseUsers: cohortSize
|
|
36649
37039
|
},
|
|
36650
37040
|
retention: {
|
|
36651
|
-
d1:
|
|
36652
|
-
d7:
|
|
36653
|
-
d30:
|
|
36654
|
-
d90:
|
|
37041
|
+
d1: clamp014(d1),
|
|
37042
|
+
d7: clamp014(Math.min(d1, d7)),
|
|
37043
|
+
d30: clamp014(Math.min(d1, d7, d30)),
|
|
37044
|
+
d90: clamp014(Math.min(d1, d7, d30, d90)),
|
|
36655
37045
|
longTailDailyDecay: 0.02
|
|
36656
37046
|
},
|
|
36657
37047
|
revenue: {
|
|
36658
|
-
payerConversion:
|
|
37048
|
+
payerConversion: clamp014(payerConversion),
|
|
36659
37049
|
arppuDaily: Math.max(0, arppuDaily),
|
|
36660
37050
|
adArpDau: Math.max(0, adArpDau),
|
|
36661
37051
|
platformFeeRate: 0.3,
|
|
@@ -36710,288 +37100,142 @@ function calibrateMonetization(rows) {
|
|
|
36710
37100
|
correlationDiagnostics: estimatedCorrelationPair
|
|
36711
37101
|
}
|
|
36712
37102
|
};
|
|
37103
|
+
assertFiniteNumbers(result, "calibration");
|
|
37104
|
+
return result;
|
|
36713
37105
|
}
|
|
36714
37106
|
// src/lib/ltvProjection.ts
|
|
36715
|
-
|
|
36716
|
-
if (v < 0)
|
|
36717
|
-
return 0;
|
|
36718
|
-
if (v > 1)
|
|
36719
|
-
return 1;
|
|
36720
|
-
return v;
|
|
36721
|
-
}
|
|
36722
|
-
function deriveMonetizationConfig(scenario) {
|
|
36723
|
-
const m = scenario.monetization;
|
|
36724
|
-
const q = (m?.uncertainty?.quantiles ?? [0.5, 0.9]).filter((x) => typeof x === "number");
|
|
36725
|
-
const quantiles = q.length > 0 ? [...new Set(q)].sort((a, b) => a - b) : [0.5, 0.9];
|
|
36726
|
-
return {
|
|
36727
|
-
cohorts: {
|
|
36728
|
-
baseUsers: m?.cohorts?.baseUsers ?? 1
|
|
36729
|
-
},
|
|
36730
|
-
retention: {
|
|
36731
|
-
d1: m?.retention?.d1 ?? 0.42,
|
|
36732
|
-
d7: m?.retention?.d7 ?? 0.2,
|
|
36733
|
-
d30: m?.retention?.d30 ?? 0.09,
|
|
36734
|
-
d90: m?.retention?.d90 ?? 0.04,
|
|
36735
|
-
longTailDailyDecay: m?.retention?.longTailDailyDecay ?? 0.02
|
|
36736
|
-
},
|
|
36737
|
-
revenue: {
|
|
36738
|
-
payerConversion: m?.revenue?.payerConversion ?? 0.03,
|
|
36739
|
-
arppuDaily: m?.revenue?.arppuDaily ?? 0.7,
|
|
36740
|
-
adArpDau: m?.revenue?.adArpDau ?? 0.02,
|
|
36741
|
-
platformFeeRate: m?.revenue?.platformFeeRate ?? 0.3,
|
|
36742
|
-
grossMarginRate: m?.revenue?.grossMarginRate ?? 0.92,
|
|
36743
|
-
progressionRevenueLift: m?.revenue?.progressionRevenueLift ?? 0.4,
|
|
36744
|
-
progressionLogSpan: m?.revenue?.progressionLogSpan ?? 6
|
|
36745
|
-
},
|
|
36746
|
-
acquisition: {
|
|
36747
|
-
cpi: m?.acquisition?.cpi ?? 0
|
|
36748
|
-
},
|
|
36749
|
-
uncertainty: {
|
|
36750
|
-
enabled: m?.uncertainty?.enabled ?? false,
|
|
36751
|
-
draws: m?.uncertainty?.draws ?? 300,
|
|
36752
|
-
quantiles,
|
|
36753
|
-
seed: m?.uncertainty?.seed,
|
|
36754
|
-
sigma: {
|
|
36755
|
-
retention: m?.uncertainty?.sigma?.retention ?? 0.08,
|
|
36756
|
-
conversion: m?.uncertainty?.sigma?.conversion ?? 0.12,
|
|
36757
|
-
arppu: m?.uncertainty?.sigma?.arppu ?? 0.2,
|
|
36758
|
-
ad: m?.uncertainty?.sigma?.ad ?? 0.15
|
|
36759
|
-
},
|
|
36760
|
-
correlation: {
|
|
36761
|
-
retentionConversion: m?.uncertainty?.correlation?.retentionConversion ?? 0,
|
|
36762
|
-
retentionArppu: m?.uncertainty?.correlation?.retentionArppu ?? 0,
|
|
36763
|
-
retentionAd: m?.uncertainty?.correlation?.retentionAd ?? 0,
|
|
36764
|
-
conversionArppu: m?.uncertainty?.correlation?.conversionArppu ?? 0,
|
|
36765
|
-
conversionAd: m?.uncertainty?.correlation?.conversionAd ?? 0,
|
|
36766
|
-
arppuAd: m?.uncertainty?.correlation?.arppuAd ?? 0
|
|
36767
|
-
}
|
|
36768
|
-
}
|
|
36769
|
-
};
|
|
36770
|
-
}
|
|
36771
|
-
function retentionAtDay(ret, day) {
|
|
36772
|
-
if (day <= 0)
|
|
36773
|
-
return 1;
|
|
36774
|
-
if (day <= 1)
|
|
36775
|
-
return ret.d1;
|
|
36776
|
-
if (day <= 7)
|
|
36777
|
-
return ret.d1 + (ret.d7 - ret.d1) * (day - 1) / 6;
|
|
36778
|
-
if (day <= 30)
|
|
36779
|
-
return ret.d7 + (ret.d30 - ret.d7) * (day - 7) / 23;
|
|
36780
|
-
if (day <= 90)
|
|
36781
|
-
return ret.d30 + (ret.d90 - ret.d30) * (day - 30) / 60;
|
|
36782
|
-
const extra = day - 90;
|
|
36783
|
-
return clamp013(ret.d90 * Math.exp(-ret.longTailDailyDecay * extra));
|
|
36784
|
-
}
|
|
36785
|
-
function progressionFactor(startWorthLog10, endWorthLog10, span) {
|
|
36786
|
-
if (!Number.isFinite(startWorthLog10) || !Number.isFinite(endWorthLog10) || !Number.isFinite(span) || span <= 0) {
|
|
36787
|
-
return 0;
|
|
36788
|
-
}
|
|
36789
|
-
return clamp013((endWorthLog10 - startWorthLog10) / span);
|
|
36790
|
-
}
|
|
36791
|
-
function estimateLtvPerUser(args) {
|
|
36792
|
-
const { config, horizonDays } = args;
|
|
36793
|
-
const progression = clamp013(args.progression);
|
|
36794
|
-
const arppuDaily = config.revenue.arppuDaily * (1 + progression * config.revenue.progressionRevenueLift);
|
|
36795
|
-
const adArpDau = config.revenue.adArpDau * (1 + progression * config.revenue.progressionRevenueLift * 0.5);
|
|
36796
|
-
const payerConversion = config.revenue.payerConversion;
|
|
36797
|
-
const fullDays = Math.floor(Math.max(0, horizonDays));
|
|
36798
|
-
const remainder = Math.max(0, horizonDays - fullDays);
|
|
36799
|
-
let gross = 0;
|
|
36800
|
-
for (let day = 1;day <= fullDays; day++) {
|
|
36801
|
-
const active = retentionAtDay(config.retention, day);
|
|
36802
|
-
gross += active * (payerConversion * arppuDaily + adArpDau);
|
|
36803
|
-
}
|
|
36804
|
-
if (remainder > 0) {
|
|
36805
|
-
const active = retentionAtDay(config.retention, fullDays + 1);
|
|
36806
|
-
gross += remainder * active * (payerConversion * arppuDaily + adArpDau);
|
|
36807
|
-
}
|
|
36808
|
-
const net = gross * (1 - config.revenue.platformFeeRate) * config.revenue.grossMarginRate;
|
|
36809
|
-
const ltv = net - config.acquisition.cpi;
|
|
36810
|
-
return {
|
|
36811
|
-
cumulativeGrossRevenuePerUser: gross,
|
|
36812
|
-
cumulativeNetRevenuePerUser: net,
|
|
36813
|
-
cumulativeLtvPerUser: ltv
|
|
36814
|
-
};
|
|
36815
|
-
}
|
|
37107
|
+
init_dist2();
|
|
36816
37108
|
// src/lib/ltvUncertainty.ts
|
|
36817
|
-
|
|
36818
|
-
|
|
36819
|
-
|
|
36820
|
-
|
|
36821
|
-
|
|
36822
|
-
|
|
36823
|
-
|
|
36824
|
-
|
|
36825
|
-
|
|
36826
|
-
|
|
36827
|
-
|
|
36828
|
-
|
|
36829
|
-
|
|
36830
|
-
|
|
36831
|
-
|
|
36832
|
-
}
|
|
36833
|
-
|
|
36834
|
-
|
|
36835
|
-
|
|
36836
|
-
|
|
36837
|
-
|
|
36838
|
-
|
|
36839
|
-
|
|
36840
|
-
|
|
37109
|
+
init_dist2();
|
|
37110
|
+
// src/lib/cohortObservation.ts
|
|
37111
|
+
var RETENTION_DAYS = [1, 7, 30, 90];
|
|
37112
|
+
function observeCohort(rows, observedThroughDay) {
|
|
37113
|
+
if (!Number.isSafeInteger(observedThroughDay) || observedThroughDay < 0)
|
|
37114
|
+
throw new Error("observed-through-day must be a nonnegative safe integer");
|
|
37115
|
+
if (rows.length === 0)
|
|
37116
|
+
throw new Error("Observed cohort requires at least one install record");
|
|
37117
|
+
const users = new Map;
|
|
37118
|
+
for (const row of rows) {
|
|
37119
|
+
if (!row.userId.trim() || !Number.isSafeInteger(row.day) || row.day < 0 || row.day > observedThroughDay) {
|
|
37120
|
+
throw new Error("Observed cohort requires a user ID and integer days within observed-through-day");
|
|
37121
|
+
}
|
|
37122
|
+
if (![row.iapRevenue, row.adRevenue, ...row.acquisitionCost === undefined ? [] : [row.acquisitionCost]].every((v) => Number.isFinite(v) && v >= 0)) {
|
|
37123
|
+
throw new Error("Observed cohort revenue and acquisition costs must be finite and nonnegative");
|
|
37124
|
+
}
|
|
37125
|
+
if (row.active !== undefined && typeof row.active !== "boolean")
|
|
37126
|
+
throw new Error("Observed cohort active must be boolean");
|
|
37127
|
+
const user = users.get(row.userId) ?? { days: new Set, active: new Set };
|
|
37128
|
+
if (user.days.has(row.day))
|
|
37129
|
+
throw new Error("Observed cohort requires one aggregated row per user and day");
|
|
37130
|
+
user.days.add(row.day);
|
|
37131
|
+
if (row.active ?? true)
|
|
37132
|
+
user.active.add(row.day);
|
|
37133
|
+
users.set(row.userId, user);
|
|
37134
|
+
}
|
|
37135
|
+
for (const user of users.values()) {
|
|
37136
|
+
if (!user.days.has(0))
|
|
37137
|
+
throw new Error("Observed cohort requires a day 0 install record for every user, including inactive users");
|
|
37138
|
+
}
|
|
37139
|
+
const n = users.size;
|
|
37140
|
+
const observation = {
|
|
37141
|
+
observedThroughDay,
|
|
37142
|
+
users: n,
|
|
37143
|
+
retention: RETENTION_DAYS.map((day) => {
|
|
37144
|
+
if (day > observedThroughDay)
|
|
37145
|
+
return { day, status: "not-observed", users: n, activeUsers: null, rate: null, interval95: null };
|
|
37146
|
+
const activeUsers = [...users.values()].filter((user) => user.active.has(day)).length;
|
|
37147
|
+
const rate = activeUsers / n;
|
|
37148
|
+
const z2 = 1.959963984540054 ** 2;
|
|
37149
|
+
const denominator = 1 + z2 / n;
|
|
37150
|
+
const center = (rate + z2 / (2 * n)) / denominator;
|
|
37151
|
+
const radius = Math.sqrt(z2 * (rate * (1 - rate) / n + z2 / (4 * n * n))) / denominator;
|
|
37152
|
+
return { day, status: "observed", users: n, activeUsers, rate, interval95: { min: activeUsers === 0 ? 0 : Math.max(0, center - radius), max: activeUsers === n ? 1 : Math.min(1, center + radius) } };
|
|
37153
|
+
})
|
|
36841
37154
|
};
|
|
36842
|
-
|
|
36843
|
-
|
|
36844
|
-
|
|
36845
|
-
|
|
36846
|
-
|
|
36847
|
-
|
|
36848
|
-
|
|
36849
|
-
|
|
36850
|
-
|
|
36851
|
-
const
|
|
36852
|
-
|
|
36853
|
-
|
|
36854
|
-
|
|
36855
|
-
|
|
36856
|
-
|
|
36857
|
-
|
|
36858
|
-
|
|
36859
|
-
|
|
36860
|
-
|
|
36861
|
-
}
|
|
36862
|
-
function withOffDiagonalScale(matrix, alpha) {
|
|
36863
|
-
return matrix.map((row, i) => row.map((v, j) => {
|
|
36864
|
-
if (i === j)
|
|
36865
|
-
return 1;
|
|
36866
|
-
return v * alpha;
|
|
36867
|
-
}));
|
|
36868
|
-
}
|
|
36869
|
-
function cholesky4(matrix) {
|
|
36870
|
-
const n = 4;
|
|
36871
|
-
const l = Array.from({ length: n }, () => Array.from({ length: n }, () => 0));
|
|
36872
|
-
for (let i = 0;i < n; i++) {
|
|
36873
|
-
for (let j = 0;j <= i; j++) {
|
|
36874
|
-
let sum = 0;
|
|
36875
|
-
for (let k = 0;k < j; k++) {
|
|
36876
|
-
sum += (l[i]?.[k] ?? 0) * (l[j]?.[k] ?? 0);
|
|
36877
|
-
}
|
|
36878
|
-
if (i === j) {
|
|
36879
|
-
const val = (matrix[i]?.[i] ?? 0) - sum;
|
|
36880
|
-
if (val <= 0.000000001)
|
|
36881
|
-
return null;
|
|
36882
|
-
l[i][j] = Math.sqrt(val);
|
|
36883
|
-
} else {
|
|
36884
|
-
const denom = l[j]?.[j] ?? 0;
|
|
36885
|
-
if (Math.abs(denom) < 0.000000001)
|
|
36886
|
-
return null;
|
|
36887
|
-
l[i][j] = ((matrix[i]?.[j] ?? 0) - sum) / denom;
|
|
36888
|
-
}
|
|
37155
|
+
assertCohortObservation(observation);
|
|
37156
|
+
return observation;
|
|
37157
|
+
}
|
|
37158
|
+
function assertCohortObservation(observation) {
|
|
37159
|
+
if (!Number.isSafeInteger(observation.users) || observation.users < 1 || !Number.isSafeInteger(observation.observedThroughDay) || observation.observedThroughDay < 0) {
|
|
37160
|
+
throw new Error("Invalid cohort population or observation cutoff");
|
|
37161
|
+
}
|
|
37162
|
+
if (observation.retention.length !== RETENTION_DAYS.length)
|
|
37163
|
+
throw new Error("Cohort retention must contain D1, D7, D30 and D90");
|
|
37164
|
+
for (const [index, point] of observation.retention.entries()) {
|
|
37165
|
+
if (point.day !== RETENTION_DAYS[index] || point.users !== observation.users)
|
|
37166
|
+
throw new Error("Cohort retention days and population must match the observation");
|
|
37167
|
+
const observed = point.day <= observation.observedThroughDay;
|
|
37168
|
+
if (point.status !== (observed ? "observed" : "not-observed"))
|
|
37169
|
+
throw new Error("Cohort retention status must match the observation cutoff");
|
|
37170
|
+
if (!observed) {
|
|
37171
|
+
if (point.activeUsers !== null || point.rate !== null || point.interval95 !== null)
|
|
37172
|
+
throw new Error("Future retention must remain unobserved");
|
|
37173
|
+
continue;
|
|
36889
37174
|
}
|
|
36890
|
-
|
|
36891
|
-
|
|
36892
|
-
}
|
|
36893
|
-
|
|
36894
|
-
|
|
36895
|
-
|
|
36896
|
-
let sum = 0;
|
|
36897
|
-
for (let j = 0;j <= i; j++) {
|
|
36898
|
-
sum += (l[i]?.[j] ?? 0) * (z[j] ?? 0);
|
|
37175
|
+
if (point.activeUsers === null || !Number.isSafeInteger(point.activeUsers) || point.activeUsers < 0 || point.activeUsers > point.users || point.rate !== point.activeUsers / point.users) {
|
|
37176
|
+
throw new Error("Retained users and retention rate must match the installed population");
|
|
37177
|
+
}
|
|
37178
|
+
const interval = point.interval95;
|
|
37179
|
+
if (point.rate === null || !interval || !Number.isFinite(interval.min) || !Number.isFinite(interval.max) || interval.min < 0 || interval.max > 1 || interval.min > point.rate || interval.max < point.rate) {
|
|
37180
|
+
throw new Error("Retention confidence interval must be finite and contain the observed rate");
|
|
36899
37181
|
}
|
|
36900
|
-
out[i] = sum;
|
|
36901
37182
|
}
|
|
36902
|
-
return out;
|
|
36903
37183
|
}
|
|
36904
|
-
function
|
|
36905
|
-
|
|
36906
|
-
|
|
36907
|
-
|
|
36908
|
-
|
|
36909
|
-
|
|
36910
|
-
|
|
36911
|
-
|
|
37184
|
+
function observedRetentionRates(observation) {
|
|
37185
|
+
return Object.fromEntries(observation.retention.filter((row) => row.rate !== null).map((row) => [`d${row.day}`, row.rate]));
|
|
37186
|
+
}
|
|
37187
|
+
function planRetentionPatch(observation, assumptions = {}) {
|
|
37188
|
+
const rates = observedRetentionRates(observation);
|
|
37189
|
+
if (Object.keys(rates).length === 0)
|
|
37190
|
+
return { status: "unavailable", retention: {}, warnings: ["No retention day has been fully observed."] };
|
|
37191
|
+
const merged = { ...assumptions, ...rates };
|
|
37192
|
+
const warnings = [];
|
|
37193
|
+
for (const day of RETENTION_DAYS) {
|
|
37194
|
+
if (merged[`d${day}`] === undefined)
|
|
37195
|
+
warnings.push(`Unobserved D${day} has no explicit retained assumption. An LTV default could conflict with the observations; supply this assumption in --scenario or observe this day before applying a patch.`);
|
|
36912
37196
|
}
|
|
36913
|
-
|
|
36914
|
-
|
|
36915
|
-
|
|
36916
|
-
|
|
36917
|
-
|
|
36918
|
-
|
|
36919
|
-
|
|
37197
|
+
let previous;
|
|
37198
|
+
for (const day of RETENTION_DAYS) {
|
|
37199
|
+
const rate = merged[`d${day}`];
|
|
37200
|
+
if (rate === undefined)
|
|
37201
|
+
continue;
|
|
37202
|
+
if (previous && previous.rate < rate)
|
|
37203
|
+
warnings.push(`D${previous.day}=${previous.rate} is below D${day}=${rate}. LTV requires d1 >= d7 >= d30 >= d90; review observations and retained assumptions before applying a patch.`);
|
|
37204
|
+
previous = { day, rate };
|
|
36920
37205
|
}
|
|
36921
|
-
|
|
36922
|
-
return {
|
|
36923
|
-
retention: c[0] ?? 0,
|
|
36924
|
-
conversion: c[1] ?? 0,
|
|
36925
|
-
arppu: c[2] ?? 0,
|
|
36926
|
-
ad: c[3] ?? 0
|
|
36927
|
-
};
|
|
36928
|
-
}
|
|
36929
|
-
function applyLogNormalShock(mean, sigma, z) {
|
|
36930
|
-
if (sigma <= 0)
|
|
36931
|
-
return mean;
|
|
36932
|
-
return mean * Math.exp(sigma * z - 0.5 * sigma * sigma);
|
|
36933
|
-
}
|
|
36934
|
-
function makeUncertainConfig(base, rng) {
|
|
36935
|
-
const shock = sampleCorrelatedNormals(rng, base.uncertainty.correlation);
|
|
36936
|
-
const d1 = clamp014(applyLogNormalShock(base.retention.d1, base.uncertainty.sigma.retention, shock.retention));
|
|
36937
|
-
const d7 = Math.min(d1, clamp014(applyLogNormalShock(base.retention.d7, base.uncertainty.sigma.retention, shock.retention)));
|
|
36938
|
-
const d30 = Math.min(d7, clamp014(applyLogNormalShock(base.retention.d30, base.uncertainty.sigma.retention, shock.retention)));
|
|
36939
|
-
const d90 = Math.min(d30, clamp014(applyLogNormalShock(base.retention.d90, base.uncertainty.sigma.retention, shock.retention)));
|
|
36940
|
-
return {
|
|
36941
|
-
...base,
|
|
36942
|
-
retention: {
|
|
36943
|
-
...base.retention,
|
|
36944
|
-
d1,
|
|
36945
|
-
d7,
|
|
36946
|
-
d30,
|
|
36947
|
-
d90
|
|
36948
|
-
},
|
|
36949
|
-
revenue: {
|
|
36950
|
-
...base.revenue,
|
|
36951
|
-
payerConversion: clamp014(applyLogNormalShock(base.revenue.payerConversion, base.uncertainty.sigma.conversion, shock.conversion)),
|
|
36952
|
-
arppuDaily: Math.max(0, applyLogNormalShock(base.revenue.arppuDaily, base.uncertainty.sigma.arppu, shock.arppu)),
|
|
36953
|
-
adArpDau: Math.max(0, applyLogNormalShock(base.revenue.adArpDau, base.uncertainty.sigma.ad, shock.ad))
|
|
36954
|
-
}
|
|
36955
|
-
};
|
|
37206
|
+
return warnings.length ? { status: "requires-review", retention: {}, warnings } : { status: "ready", retention: rates, warnings };
|
|
36956
37207
|
}
|
|
36957
|
-
function
|
|
36958
|
-
|
|
36959
|
-
|
|
36960
|
-
|
|
36961
|
-
|
|
36962
|
-
|
|
37208
|
+
function compareRetention(observation, assumptions) {
|
|
37209
|
+
return observation.retention.map((row) => {
|
|
37210
|
+
const assumption = assumptions[`d${row.day}`] ?? null;
|
|
37211
|
+
return {
|
|
37212
|
+
day: row.day,
|
|
37213
|
+
assumption,
|
|
37214
|
+
observed: row.rate,
|
|
37215
|
+
differencePercentagePoints: row.rate === null || assumption === null ? null : (row.rate - assumption) * 100,
|
|
37216
|
+
status: row.rate === null ? "not-observed" : assumption === null ? "unconfigured" : "compared"
|
|
37217
|
+
};
|
|
37218
|
+
});
|
|
36963
37219
|
}
|
|
36964
|
-
function
|
|
36965
|
-
const
|
|
36966
|
-
const
|
|
36967
|
-
|
|
36968
|
-
|
|
36969
|
-
|
|
36970
|
-
|
|
36971
|
-
|
|
36972
|
-
|
|
36973
|
-
|
|
36974
|
-
|
|
36975
|
-
|
|
36976
|
-
});
|
|
36977
|
-
grossSamples.push(point.cumulativeGrossRevenuePerUser);
|
|
36978
|
-
netSamples.push(point.cumulativeNetRevenuePerUser);
|
|
36979
|
-
ltvSamples.push(point.cumulativeLtvPerUser);
|
|
36980
|
-
}
|
|
36981
|
-
const avg = (arr) => arr.reduce((a, b) => a + b, 0) / Math.max(1, arr.length);
|
|
36982
|
-
const byQuantile = {};
|
|
36983
|
-
for (const q of args.quantiles) {
|
|
36984
|
-
byQuantile[`q${Math.round(q * 100)}`] = quantile2(ltvSamples, q);
|
|
37220
|
+
function renderCohortMarkdown(observation, comparison, patch) {
|
|
37221
|
+
const percent = (value) => value === null ? "unavailable" : `${(value * 100).toFixed(2)}%`;
|
|
37222
|
+
const lines = [
|
|
37223
|
+
"# Observed player retention",
|
|
37224
|
+
"",
|
|
37225
|
+
`${observation.users} installed users; complete activity export through day ${observation.observedThroughDay}. Exact-day retention counts every installed user, including those who never return.`,
|
|
37226
|
+
"",
|
|
37227
|
+
"| Day | Retained / installed | Observed rate | Wilson 95% interval | Assumption | Difference (percentage points) | Status |",
|
|
37228
|
+
"| --- | --- | --- | --- | --- | --- | --- |"
|
|
37229
|
+
];
|
|
37230
|
+
for (const [index, row] of observation.retention.entries()) {
|
|
37231
|
+
const match = comparison?.[index];
|
|
37232
|
+
lines.push(`| D${row.day} | ${row.activeUsers === null ? "unavailable" : `${row.activeUsers} / ${row.users}`} | ${percent(row.rate)} | ${row.interval95 ? `${percent(row.interval95.min)} to ${percent(row.interval95.max)}` : "unavailable"} | ${percent(match?.assumption ?? null)} | ${match?.differencePercentagePoints == null ? "unavailable" : match.differencePercentagePoints.toFixed(2)} | ${match?.status ?? row.status} |`);
|
|
36985
37233
|
}
|
|
36986
|
-
|
|
36987
|
-
|
|
36988
|
-
|
|
36989
|
-
cumulativeNetRevenuePerUser: avg(netSamples),
|
|
36990
|
-
cumulativeLtvPerUser: avg(ltvSamples)
|
|
36991
|
-
},
|
|
36992
|
-
quantiles: byQuantile
|
|
36993
|
-
};
|
|
37234
|
+
lines.push("", `Retention patch: **${patch.status}**.`, "", ...patch.warnings.map((warning) => `- ${warning}`), "", "Positive differences mean observed retention exceeds the configured assumption. Only apply a ready patch. Unobserved days are omitted from scenarioPatch; merge its retention fields into the scenario and preserve the remaining assumptions.", "", "Use a separate held-out cohort to validate a fitted model. These observations and intervals describe this cohort; they do not establish fun, causal improvement, or future revenue. Mixed install ages, missing activity exports, and omitted nonreturning users invalidate this comparison.");
|
|
37235
|
+
return lines.join(`
|
|
37236
|
+
`);
|
|
36994
37237
|
}
|
|
37238
|
+
|
|
36995
37239
|
// src/commands/calibrate.ts
|
|
36996
37240
|
init_bun();
|
|
36997
37241
|
function parseCsvRows(raw) {
|
|
@@ -37064,21 +37308,46 @@ function parseNumeric(raw, row, field, opts) {
|
|
|
37064
37308
|
}
|
|
37065
37309
|
return n;
|
|
37066
37310
|
}
|
|
37067
|
-
function
|
|
37311
|
+
function parseActive(value, strict) {
|
|
37312
|
+
if (value === undefined)
|
|
37313
|
+
return true;
|
|
37314
|
+
if (typeof value === "boolean")
|
|
37315
|
+
return value;
|
|
37316
|
+
if (typeof value === "number" && (value === 0 || value === 1))
|
|
37317
|
+
return value === 1;
|
|
37318
|
+
if (typeof value === "string") {
|
|
37319
|
+
if (/^(1|true|yes|y)$/i.test(value))
|
|
37320
|
+
return true;
|
|
37321
|
+
if (/^(0|false|no|n)$/i.test(value))
|
|
37322
|
+
return false;
|
|
37323
|
+
}
|
|
37324
|
+
if (strict)
|
|
37325
|
+
throw usageError("Observed cohort active must be true/false, yes/no, or 1/0");
|
|
37326
|
+
return typeof value === "string" ? /^(1|true|yes|y)$/i.test(value) : Boolean(value);
|
|
37327
|
+
}
|
|
37328
|
+
function parseCsvTelemetry(raw, strictCohort = false) {
|
|
37068
37329
|
const rows2d = parseCsvRows(raw);
|
|
37069
37330
|
if (rows2d.length < 2) {
|
|
37070
37331
|
throw cliError("CLI_USAGE", "CSV telemetry requires a header and at least one data row");
|
|
37071
37332
|
}
|
|
37072
37333
|
const headers = (rows2d[0] ?? []).map(normalizeHeader);
|
|
37334
|
+
if (strictCohort && new Set(headers).size !== headers.length)
|
|
37335
|
+
throw usageError("Observed cohort CSV has duplicate headers");
|
|
37073
37336
|
const rows = [];
|
|
37074
37337
|
for (let i = 1;i < rows2d.length; i++) {
|
|
37075
37338
|
const cols = rows2d[i] ?? [];
|
|
37339
|
+
if (strictCohort && cols.length !== headers.length)
|
|
37340
|
+
throw usageError(`Observed cohort CSV row ${i + 1} has the wrong number of columns`);
|
|
37076
37341
|
const rec = {};
|
|
37077
37342
|
headers.forEach((h, idx) => {
|
|
37078
37343
|
rec[h] = cols[idx] ?? "";
|
|
37079
37344
|
});
|
|
37080
|
-
const
|
|
37081
|
-
const
|
|
37345
|
+
const userInput = pick2(rec, ["user_id", "userid", "user", "id"]);
|
|
37346
|
+
const dayInput = pick2(rec, ["day", "day_index", "days_since_install"]);
|
|
37347
|
+
if (strictCohort && (!userInput?.trim() || !dayInput?.trim()))
|
|
37348
|
+
throw usageError("Observed cohort requires explicit user_id and day fields");
|
|
37349
|
+
const userId = userInput ?? `anon-${i}`;
|
|
37350
|
+
const dayRaw = dayInput ?? "0";
|
|
37082
37351
|
const iapRaw = pick2(rec, ["iap_revenue", "revenue", "iap", "purchase_revenue"]) ?? "0";
|
|
37083
37352
|
const adRaw = pick2(rec, ["ad_revenue", "adrevenue", "ads"]) ?? "0";
|
|
37084
37353
|
const activeRaw = pick2(rec, ["active", "is_active"]);
|
|
@@ -37087,7 +37356,7 @@ function parseCsvTelemetry(raw) {
|
|
|
37087
37356
|
const iapRevenue = parseNumeric(iapRaw, i + 1, "revenue");
|
|
37088
37357
|
const adRevenue = parseNumeric(adRaw, i + 1, "ad_revenue");
|
|
37089
37358
|
const acquisitionCost = acqRaw !== undefined ? parseNumeric(acqRaw, i + 1, "acquisition_cost", { allowEmpty: true }) : undefined;
|
|
37090
|
-
const active = activeRaw
|
|
37359
|
+
const active = parseActive(activeRaw, strictCohort);
|
|
37091
37360
|
rows.push({
|
|
37092
37361
|
userId,
|
|
37093
37362
|
day,
|
|
@@ -37099,7 +37368,7 @@ function parseCsvTelemetry(raw) {
|
|
|
37099
37368
|
}
|
|
37100
37369
|
return rows;
|
|
37101
37370
|
}
|
|
37102
|
-
function parseJsonTelemetry(raw) {
|
|
37371
|
+
function parseJsonTelemetry(raw, strictCohort = false) {
|
|
37103
37372
|
const parsed = JSON.parse(raw);
|
|
37104
37373
|
if (!Array.isArray(parsed)) {
|
|
37105
37374
|
throw cliError("CLI_USAGE", "JSON telemetry must be an array");
|
|
@@ -37110,12 +37379,21 @@ function parseJsonTelemetry(raw) {
|
|
|
37110
37379
|
throw cliError("CLI_USAGE", `JSON telemetry row ${idx} must be an object`);
|
|
37111
37380
|
}
|
|
37112
37381
|
const r = row;
|
|
37113
|
-
const
|
|
37114
|
-
const
|
|
37382
|
+
const userInput = r.userId ?? r.user_id ?? r.user ?? r.id;
|
|
37383
|
+
const dayInput = r.day ?? r.dayIndex ?? r.daysSinceInstall;
|
|
37384
|
+
if (strictCohort && (typeof userInput !== "string" || !userInput.trim() || typeof dayInput !== "number"))
|
|
37385
|
+
throw usageError("Observed cohort JSON requires explicit string userId and numeric day fields");
|
|
37386
|
+
if (strictCohort)
|
|
37387
|
+
for (const key of ["iapRevenue", "iap_revenue", "revenue", "adRevenue", "ad_revenue", "acquisitionCost", "acquisition_cost", "cpi"]) {
|
|
37388
|
+
if (r[key] !== undefined && typeof r[key] !== "number")
|
|
37389
|
+
throw usageError(`Observed cohort JSON ${key} must be numeric`);
|
|
37390
|
+
}
|
|
37391
|
+
const userId = String(userInput ?? `anon-${idx}`);
|
|
37392
|
+
const day = Number(dayInput ?? 0);
|
|
37115
37393
|
const iapRevenue = Number(r.iapRevenue ?? r.iap_revenue ?? r.revenue ?? 0);
|
|
37116
37394
|
const adRevenue = Number(r.adRevenue ?? r.ad_revenue ?? 0);
|
|
37117
37395
|
const acquisitionCost = r.acquisitionCost !== undefined || r.acquisition_cost !== undefined || r.cpi !== undefined ? Number(r.acquisitionCost ?? r.acquisition_cost ?? r.cpi) : undefined;
|
|
37118
|
-
const active = r.active
|
|
37396
|
+
const active = parseActive(r.active, strictCohort);
|
|
37119
37397
|
if (!Number.isFinite(day) || !Number.isFinite(iapRevenue) || !Number.isFinite(adRevenue)) {
|
|
37120
37398
|
throw cliError("CLI_USAGE", `Invalid numeric value in json row ${idx}`);
|
|
37121
37399
|
}
|
|
@@ -37137,26 +37415,65 @@ var calibrate_default = defineCommand({
|
|
|
37137
37415
|
"input-format": option(_enum(["auto", "csv", "json"]).default("auto"), {
|
|
37138
37416
|
description: "Telemetry file format"
|
|
37139
37417
|
}),
|
|
37418
|
+
"observed-through-day": option(exports_coerce.number().int().nonnegative().max(Number.MAX_SAFE_INTEGER).optional(), {
|
|
37419
|
+
description: "Complete activity cutoff for one install cohort; requires day 0 records for every installed user"
|
|
37420
|
+
}),
|
|
37421
|
+
scenario: option(string2().optional(), { description: "Compare observed retention with this scenario's explicit assumptions (requires --observed-through-day)" }),
|
|
37140
37422
|
out: option(string2().optional(), { description: "Output path" }),
|
|
37141
37423
|
format: option(_enum(["json", "md", "csv"]).default("json"), { description: "Output format" })
|
|
37142
37424
|
},
|
|
37143
37425
|
async handler({ flags, positional }) {
|
|
37144
37426
|
const telemetryPath = positional[0];
|
|
37145
|
-
if (!telemetryPath) {
|
|
37427
|
+
if (!telemetryPath || positional.length !== 1) {
|
|
37146
37428
|
throw usageError("Usage: idk calibrate <telemetry.csv|json> [--input-format auto|csv|json]");
|
|
37147
37429
|
}
|
|
37430
|
+
if (flags.scenario && flags["observed-through-day"] === undefined)
|
|
37431
|
+
throw usageError("--scenario requires --observed-through-day for a mature cohort comparison");
|
|
37148
37432
|
const abs = resolve8(process.cwd(), telemetryPath);
|
|
37149
37433
|
const raw = await readTextFile(abs);
|
|
37150
37434
|
const format = flags["input-format"] === "auto" ? extname3(abs).toLowerCase() : flags["input-format"];
|
|
37151
|
-
const rows = format === ".csv" || format === "csv" ? parseCsvTelemetry(raw) : parseJsonTelemetry(raw);
|
|
37152
|
-
|
|
37435
|
+
const rows = format === ".csv" || format === "csv" ? parseCsvTelemetry(raw, flags["observed-through-day"] !== undefined) : parseJsonTelemetry(raw, flags["observed-through-day"] !== undefined);
|
|
37436
|
+
let observation;
|
|
37437
|
+
if (flags["observed-through-day"] !== undefined) {
|
|
37438
|
+
try {
|
|
37439
|
+
observation = observeCohort(rows, flags["observed-through-day"]);
|
|
37440
|
+
} catch (error) {
|
|
37441
|
+
throw usageError(error instanceof Error ? error.message : String(error));
|
|
37442
|
+
}
|
|
37443
|
+
}
|
|
37444
|
+
let calibrated;
|
|
37445
|
+
try {
|
|
37446
|
+
calibrated = calibrateMonetization(rows);
|
|
37447
|
+
} catch (error) {
|
|
37448
|
+
if (error instanceof RangeError)
|
|
37449
|
+
throw usageError(error.message);
|
|
37450
|
+
throw error;
|
|
37451
|
+
}
|
|
37452
|
+
const retentionRates = observation ? observedRetentionRates(observation) : undefined;
|
|
37453
|
+
const scenario = flags.scenario ? await readScenarioFile(flags.scenario) : undefined;
|
|
37454
|
+
const rate = number2().min(0).max(1).optional();
|
|
37455
|
+
const parsedScenario = scenario === undefined ? undefined : object({
|
|
37456
|
+
schemaVersion: literal(1),
|
|
37457
|
+
monetization: object({ retention: object({ d1: rate, d7: rate, d30: rate, d90: rate }).passthrough().optional() }).passthrough().optional()
|
|
37458
|
+
}).passthrough().safeParse(scenario);
|
|
37459
|
+
if (parsedScenario && !parsedScenario.success)
|
|
37460
|
+
throw usageError(`Invalid scenario retention assumptions: ${parsedScenario.error.message}`);
|
|
37461
|
+
const comparison = observation && parsedScenario?.success ? compareRetention(observation, parsedScenario.data.monetization?.retention ?? {}) : undefined;
|
|
37462
|
+
const patch = observation ? planRetentionPatch(observation, parsedScenario?.success ? parsedScenario.data.monetization?.retention : undefined) : undefined;
|
|
37463
|
+
const monetization = observation ? {
|
|
37464
|
+
...calibrated.monetization,
|
|
37465
|
+
retention: { longTailDailyDecay: calibrated.monetization?.retention?.longTailDailyDecay, ...patch?.retention }
|
|
37466
|
+
} : calibrated.monetization;
|
|
37153
37467
|
const outputMeta = buildOutputMeta({
|
|
37154
37468
|
command: "calibrate",
|
|
37155
|
-
telemetry: rows
|
|
37469
|
+
telemetry: observation ? { rows, observedThroughDay: observation.observedThroughDay } : rows,
|
|
37470
|
+
scenario,
|
|
37471
|
+
scenarioPath: flags.scenario ? resolve8(flags.scenario) : undefined
|
|
37156
37472
|
});
|
|
37157
37473
|
await writeOutput({
|
|
37158
37474
|
format: flags.format,
|
|
37159
37475
|
outPath: flags.out,
|
|
37476
|
+
protectedInputs: [abs, ...flags.scenario ? [resolve8(flags.scenario)] : []],
|
|
37160
37477
|
data: flags.format === "json" ? {
|
|
37161
37478
|
ok: true,
|
|
37162
37479
|
source: abs,
|
|
@@ -37164,12 +37481,27 @@ var calibrate_default = defineCommand({
|
|
|
37164
37481
|
rows: rows.length,
|
|
37165
37482
|
users: calibrated.diagnostics.users
|
|
37166
37483
|
},
|
|
37167
|
-
monetization
|
|
37168
|
-
diagnostics: calibrated.diagnostics,
|
|
37169
|
-
|
|
37484
|
+
monetization,
|
|
37485
|
+
diagnostics: observation ? { ...calibrated.diagnostics, retention: retentionRates, observedThroughDay: observation.observedThroughDay } : calibrated.diagnostics,
|
|
37486
|
+
...observation ? { observation } : {},
|
|
37487
|
+
...comparison ? { comparison } : {},
|
|
37488
|
+
...patch ? { patchStatus: patch.status, patchWarnings: patch.warnings } : {},
|
|
37489
|
+
scenarioPatch: observation ? { monetization: { retention: patch.retention } } : {
|
|
37170
37490
|
monetization: calibrated.monetization
|
|
37171
37491
|
}
|
|
37172
|
-
} :
|
|
37492
|
+
} : observation ? flags.format === "md" ? renderCohortMarkdown(observation, comparison, patch) : observation.retention.map((row, index) => ({
|
|
37493
|
+
day: row.day,
|
|
37494
|
+
users: row.users,
|
|
37495
|
+
activeUsers: row.activeUsers,
|
|
37496
|
+
observedRate: row.rate,
|
|
37497
|
+
interval95Min: row.interval95?.min ?? null,
|
|
37498
|
+
interval95Max: row.interval95?.max ?? null,
|
|
37499
|
+
assumption: comparison?.[index]?.assumption ?? null,
|
|
37500
|
+
differencePercentagePoints: comparison?.[index]?.differencePercentagePoints ?? null,
|
|
37501
|
+
status: comparison?.[index]?.status ?? row.status,
|
|
37502
|
+
patchStatus: patch.status,
|
|
37503
|
+
patchWarnings: patch.warnings.join(" ")
|
|
37504
|
+
})) : [
|
|
37173
37505
|
{
|
|
37174
37506
|
source: abs,
|
|
37175
37507
|
rows: rows.length,
|
|
@@ -37191,12 +37523,13 @@ var calibrate_default = defineCommand({
|
|
|
37191
37523
|
|
|
37192
37524
|
// src/commands/balance.ts
|
|
37193
37525
|
init_zod();
|
|
37526
|
+
import { resolve as resolve11 } from "path";
|
|
37194
37527
|
|
|
37195
37528
|
// src/balance/workflow.ts
|
|
37196
37529
|
init_dist2();
|
|
37197
37530
|
init_zod();
|
|
37198
37531
|
import { resolve as resolve10, dirname as dirname8 } from "path";
|
|
37199
|
-
import { stat } from "fs/promises";
|
|
37532
|
+
import { stat as stat2 } from "fs/promises";
|
|
37200
37533
|
|
|
37201
37534
|
// src/cliMeta.ts
|
|
37202
37535
|
var import_package2 = __toESM(require_package(), 1);
|
|
@@ -37442,7 +37775,7 @@ async function runPacingChecks(input, evaluator) {
|
|
|
37442
37775
|
// src/balance/sheet.ts
|
|
37443
37776
|
init_bun();
|
|
37444
37777
|
import { constants } from "fs";
|
|
37445
|
-
import { lstat, mkdir, open as open2, realpath, rename, rm } from "fs/promises";
|
|
37778
|
+
import { lstat, mkdir, open as open2, realpath as realpath2, rename, rm } from "fs/promises";
|
|
37446
37779
|
import { dirname as dirname7, isAbsolute as isAbsolute2, join, relative as relative2, resolve as resolve9, sep as sep2 } from "path";
|
|
37447
37780
|
var SHEET_LIMITS = Object.freeze({
|
|
37448
37781
|
maxCsvBytes: 2 * 1024 * 1024,
|
|
@@ -37816,7 +38149,7 @@ async function snapshotInputs(inputFiles, contextValue, limits) {
|
|
|
37816
38149
|
let total = 0;
|
|
37817
38150
|
for (const name of validInputNames(inputFiles, limits)) {
|
|
37818
38151
|
const path = resolve9(inputFiles[name]);
|
|
37819
|
-
const realPath = await
|
|
38152
|
+
const realPath = await realpath2(path);
|
|
37820
38153
|
const bytes = await boundedRead(realPath, Math.min(limits.maxInputBytes, limits.maxTotalInputBytes - total));
|
|
37821
38154
|
total += bytes.length;
|
|
37822
38155
|
if (total > limits.maxTotalInputBytes)
|
|
@@ -37843,7 +38176,7 @@ async function snapshotInputs(inputFiles, contextValue, limits) {
|
|
|
37843
38176
|
async function canonicalOutput(path) {
|
|
37844
38177
|
path = resolve9(path);
|
|
37845
38178
|
try {
|
|
37846
|
-
return await
|
|
38179
|
+
return await realpath2(path);
|
|
37847
38180
|
} catch (error) {
|
|
37848
38181
|
if (error.code !== "ENOENT")
|
|
37849
38182
|
throw error;
|
|
@@ -37880,7 +38213,7 @@ async function refreshSheetBundle(options) {
|
|
|
37880
38213
|
validInputNames(options.inputFiles, limits);
|
|
37881
38214
|
const output = await canonicalOutput(options.outputDir);
|
|
37882
38215
|
for (const [name, path] of Object.entries(options.inputFiles)) {
|
|
37883
|
-
if (isWithin(output, resolve9(path)) || isWithin(output, await
|
|
38216
|
+
if (isWithin(output, resolve9(path)) || isWithin(output, await realpath2(path)))
|
|
37884
38217
|
throw new Error(`Output/input collision: ${name}`);
|
|
37885
38218
|
}
|
|
37886
38219
|
await mkdir(output, { recursive: true });
|
|
@@ -37940,7 +38273,7 @@ async function refreshSheetBundle(options) {
|
|
|
37940
38273
|
if (manifestBytes.length > 1024 * 1024)
|
|
37941
38274
|
throw new Error("Bundle manifest exceeds byte limit");
|
|
37942
38275
|
await mkdir(generations, { recursive: true });
|
|
37943
|
-
if (await
|
|
38276
|
+
if (await realpath2(generations) !== generations)
|
|
37944
38277
|
throw new Error("Bundle generations directory must not be a symlink");
|
|
37945
38278
|
await mkdir(stage);
|
|
37946
38279
|
for (const name of names)
|
|
@@ -38029,10 +38362,10 @@ async function readCurrentBundle(outputDir, inputFiles, options = {}) {
|
|
|
38029
38362
|
if (!isRecord5(pointer) || pointer.version !== 1 || typeof pointer.generation !== "string" || !generationText.test(pointer.generation) || typeof pointer.manifestSha256 !== "string" || !sha256Text2.test(pointer.manifestSha256))
|
|
38030
38363
|
throw new Error("Invalid bundle pointer");
|
|
38031
38364
|
const generationPath = join(output, "generations", pointer.generation);
|
|
38032
|
-
if (await
|
|
38365
|
+
if (await realpath2(generationPath) !== generationPath)
|
|
38033
38366
|
throw new Error("Bundle generation must not be a symlink");
|
|
38034
38367
|
const manifestPath = join(generationPath, "manifest.json");
|
|
38035
|
-
if (await
|
|
38368
|
+
if (await realpath2(manifestPath) !== manifestPath)
|
|
38036
38369
|
throw new Error("Bundle manifest must not be a symlink");
|
|
38037
38370
|
const bytes = await boundedRead(manifestPath, 1024 * 1024);
|
|
38038
38371
|
if (sha256Hex(bytes) !== pointer.manifestSha256)
|
|
@@ -38041,7 +38374,7 @@ async function readCurrentBundle(outputDir, inputFiles, options = {}) {
|
|
|
38041
38374
|
let outputBytesRead = 0;
|
|
38042
38375
|
for (const [name, artifact] of Object.entries(manifest.artifacts)) {
|
|
38043
38376
|
try {
|
|
38044
|
-
if (await
|
|
38377
|
+
if (await realpath2(artifact.path) !== artifact.path)
|
|
38045
38378
|
throw new Error("Artifact is a symlink");
|
|
38046
38379
|
const artifactBytes = await boundedRead(artifact.path, Math.min(limits.maxOutputBytes, limits.maxTotalOutputBytes - outputBytesRead));
|
|
38047
38380
|
outputBytesRead += artifactBytes.length;
|
|
@@ -38141,7 +38474,7 @@ function normalizeRunDocument(document, strategy, horizon) {
|
|
|
38141
38474
|
}
|
|
38142
38475
|
async function refreshBalanceWorkflow(configPath, flags, check = false) {
|
|
38143
38476
|
const absoluteConfig = resolve10(configPath);
|
|
38144
|
-
const configInfo = await
|
|
38477
|
+
const configInfo = await stat2(absoluteConfig);
|
|
38145
38478
|
if (!configInfo.isFile() || configInfo.size > SHEET_LIMITS.maxInputBytes)
|
|
38146
38479
|
throw new Error("Workflow config is not a bounded regular file");
|
|
38147
38480
|
const rawConfig = await readTextFile(absoluteConfig);
|
|
@@ -38337,6 +38670,46 @@ async function refreshBalanceWorkflow(configPath, flags, check = false) {
|
|
|
38337
38670
|
return { ...manifest, outcome };
|
|
38338
38671
|
}
|
|
38339
38672
|
|
|
38673
|
+
// src/balance/report.ts
|
|
38674
|
+
function cell(value) {
|
|
38675
|
+
return String(value).replaceAll("|", "\\|").replace(/[\r\n]/g, " ");
|
|
38676
|
+
}
|
|
38677
|
+
function explain(result) {
|
|
38678
|
+
if (result.status === "breach" && result.value !== null) {
|
|
38679
|
+
if (result.min !== undefined && result.value < result.min)
|
|
38680
|
+
return `below the minimum by ${result.min - result.value} ${result.unit}`;
|
|
38681
|
+
if (result.max !== undefined && result.value > result.max)
|
|
38682
|
+
return `above the maximum by ${result.value - result.max} ${result.unit}`;
|
|
38683
|
+
}
|
|
38684
|
+
if (result.status === "pass")
|
|
38685
|
+
return "within the inclusive target bounds";
|
|
38686
|
+
return result.message ?? (result.status === "unreached" ? "not reached within the horizon" : "measurement unavailable");
|
|
38687
|
+
}
|
|
38688
|
+
function renderBalanceReport(report, generationPath) {
|
|
38689
|
+
const lines = [
|
|
38690
|
+
"# Balance targets",
|
|
38691
|
+
"",
|
|
38692
|
+
`Result: **${report.status}**. Simulated ${report.horizonSec} seconds using strategy ${cell(report.strategy)}; seeds ${report.seeds.join(", ")}.`,
|
|
38693
|
+
"A target is a design constraint on this model under these conditions. End net worth is modeled asset value; it can differ from spendable wallet money.",
|
|
38694
|
+
"",
|
|
38695
|
+
"| Variant | Seed | Metric | Measured | Target | Status | Explanation |",
|
|
38696
|
+
"| --- | --- | --- | --- | --- | --- | --- |"
|
|
38697
|
+
];
|
|
38698
|
+
for (const entry of report.runs)
|
|
38699
|
+
for (const result of entry.results) {
|
|
38700
|
+
const target = [result.min === undefined ? undefined : `minimum ${result.min} ${result.unit}`, result.max === undefined ? undefined : `maximum ${result.max} ${result.unit}`].filter(Boolean).join(", ");
|
|
38701
|
+
const measured = result.value === null ? "unavailable" : `${result.value} ${result.unit}`;
|
|
38702
|
+
lines.push(`| ${cell(entry.run.variantId)} | ${entry.run.seed} | ${cell(result.metric)} | ${cell(measured)} | ${cell(target)} | ${result.status} | ${cell(explain(result))} |`);
|
|
38703
|
+
}
|
|
38704
|
+
lines.push("", report.ok ? "All declared targets passed." : "A target breached, was not reached, or could not be measured. The command exits 1; inspect the rows before changing the design.", "", `Saved generation: ${cell(generationPath)}`, "The generation contains exact values in results.json and reproduction inputs in provenance.json. Passing targets does not establish fun or player retention.");
|
|
38705
|
+
return lines.join(`
|
|
38706
|
+
`);
|
|
38707
|
+
}
|
|
38708
|
+
function renderBalanceFreshness(result) {
|
|
38709
|
+
return ["# Balance result freshness", "", `State: **${result.state}**. This checks saved inputs and artifacts; it does not check target success. Run balance without --check to evaluate the targets.`, ...result.staleReasons.map((reason) => `- ${cell(reason)}`)].join(`
|
|
38710
|
+
`);
|
|
38711
|
+
}
|
|
38712
|
+
|
|
38340
38713
|
// src/commands/balance.ts
|
|
38341
38714
|
init_errors3();
|
|
38342
38715
|
init_bun();
|
|
@@ -38345,13 +38718,15 @@ var balance_default = defineCommand({
|
|
|
38345
38718
|
description: "Refresh a typed CSV balance sheet into scenario, pacing checks, and result sheets",
|
|
38346
38719
|
options: {
|
|
38347
38720
|
...pluginOptions(),
|
|
38348
|
-
check: option(exports_coerce.boolean().default(false), { description: "Check result freshness without rerunning simulations" })
|
|
38721
|
+
check: option(exports_coerce.boolean().default(false), { description: "Check result freshness without rerunning simulations" }),
|
|
38722
|
+
format: option(_enum(["json", "md"]).default("json"), { description: "JSON manifest or human-readable target explanations" })
|
|
38349
38723
|
},
|
|
38350
38724
|
async handler({ flags, positional }) {
|
|
38351
38725
|
if (positional.length !== 1)
|
|
38352
38726
|
throw usageError("Usage: idk balance <workflow.json> [--check] [plugin options]");
|
|
38353
38727
|
const result = await refreshBalanceWorkflow(positional[0], flags, flags.check);
|
|
38354
|
-
|
|
38728
|
+
const body = flags.format === "json" ? JSON.stringify(result, null, 2) : ("state" in result) ? renderBalanceFreshness(result) : renderBalanceReport(JSON.parse(await readTextFile(resolve11(result.generationPath, "results.json"))), result.generationPath);
|
|
38729
|
+
await writeStdout(body + `
|
|
38355
38730
|
`);
|
|
38356
38731
|
if (flags.check && "state" in result && result.state !== "current")
|
|
38357
38732
|
process.exitCode = 1;
|
|
@@ -38363,7 +38738,7 @@ var balance_default = defineCommand({
|
|
|
38363
38738
|
// src/commands/compare.ts
|
|
38364
38739
|
init_dist2();
|
|
38365
38740
|
init_zod();
|
|
38366
|
-
import { resolve as
|
|
38741
|
+
import { resolve as resolve14 } from "path";
|
|
38367
38742
|
|
|
38368
38743
|
// src/commands/_shared/compareEval.ts
|
|
38369
38744
|
function formatEtaLabel(seconds, reached) {
|
|
@@ -38390,12 +38765,12 @@ init_errors3();
|
|
|
38390
38765
|
init_outputMeta();
|
|
38391
38766
|
|
|
38392
38767
|
// src/io/replayPolicy.ts
|
|
38393
|
-
import { resolve as
|
|
38768
|
+
import { resolve as resolve13 } from "path";
|
|
38394
38769
|
|
|
38395
38770
|
// src/io/replayArtifact.ts
|
|
38396
38771
|
init_outputMeta();
|
|
38397
38772
|
init_bun();
|
|
38398
|
-
import { resolve as
|
|
38773
|
+
import { resolve as resolve12 } from "path";
|
|
38399
38774
|
var REDACTED_KEYS = new Set([
|
|
38400
38775
|
"generatedAt",
|
|
38401
38776
|
"scenario",
|
|
@@ -38461,7 +38836,7 @@ function toReplayCommandLine(args) {
|
|
|
38461
38836
|
return `bun run --cwd packages/cli dev -- ${rendered}`;
|
|
38462
38837
|
}
|
|
38463
38838
|
async function writeReplayArtifact(args) {
|
|
38464
|
-
const abs =
|
|
38839
|
+
const abs = resolve12(args.outPath);
|
|
38465
38840
|
const runId = args.meta.runId;
|
|
38466
38841
|
const seed = args.meta.seed;
|
|
38467
38842
|
const scenarioHash = args.meta.scenarioHash;
|
|
@@ -38537,7 +38912,7 @@ var PATH_FLAG_KINDS = {
|
|
|
38537
38912
|
function normalizePathLike(pathLike, cwd) {
|
|
38538
38913
|
if (pathLike.trim().length === 0)
|
|
38539
38914
|
return pathLike;
|
|
38540
|
-
return
|
|
38915
|
+
return resolve13(cwd, pathLike);
|
|
38541
38916
|
}
|
|
38542
38917
|
function normalizeCsvPaths(raw, cwd) {
|
|
38543
38918
|
return raw.split(",").map((x) => x.trim()).filter(Boolean).map((x) => normalizePathLike(x, cwd)).join(",");
|
|
@@ -38612,7 +38987,7 @@ function plain(value) {
|
|
|
38612
38987
|
return "n/a";
|
|
38613
38988
|
return String(value).replace(/[\r\n]+/g, " ");
|
|
38614
38989
|
}
|
|
38615
|
-
function
|
|
38990
|
+
function cell2(value) {
|
|
38616
38991
|
return plain(value).replaceAll("|", "\\|");
|
|
38617
38992
|
}
|
|
38618
38993
|
function inlineCode(value) {
|
|
@@ -38640,9 +39015,9 @@ function renderCompareMarkdown(args) {
|
|
|
38640
39015
|
const higher = ["endMoney", "endNetWorth", "visibleChangesPerMinute"].includes(metric);
|
|
38641
39016
|
const lower = ["droppedRate", "etaToTargetWorth", "timeToMilestone", "maxNoRewardGapSec"].includes(metric);
|
|
38642
39017
|
const seconds = ["etaToTargetWorth", "timeToMilestone", "maxNoRewardGapSec"].includes(metric);
|
|
38643
|
-
const value = (item) => seconds && (typeof item === "number" || typeof item === "string" && item.trim().length > 0) && Number.isFinite(Number(item)) ? `${
|
|
39018
|
+
const value = (item) => seconds && (typeof item === "number" || typeof item === "string" && item.trim().length > 0) && Number.isFinite(Number(item)) ? `${cell2(item)}s` : cell2(item);
|
|
38644
39019
|
const better = result.better === "a" || result.better === "b" ? result.better.toUpperCase() : result.better === "tie" ? "tie" : "undetermined";
|
|
38645
|
-
lines.push(`| ${
|
|
39020
|
+
lines.push(`| ${cell2(metric)} | ${value(a)} | ${value(b)} | ${higher ? "higher" : lower ? "lower" : "n/a"} | ${better} |`);
|
|
38646
39021
|
if (result.better === "a" || result.better === "b")
|
|
38647
39022
|
preferences[result.better].push(metric);
|
|
38648
39023
|
}
|
|
@@ -39319,8 +39694,8 @@ var compare_default = defineCommand({
|
|
|
39319
39694
|
}
|
|
39320
39695
|
} : singleResults[0];
|
|
39321
39696
|
if (flags["artifact-out"]) {
|
|
39322
|
-
const aAbs =
|
|
39323
|
-
const bAbs =
|
|
39697
|
+
const aAbs = resolve14(process.cwd(), aPath);
|
|
39698
|
+
const bAbs = resolve14(process.cwd(), bPath);
|
|
39324
39699
|
await writeCommandReplayArtifact({
|
|
39325
39700
|
outPath: flags["artifact-out"],
|
|
39326
39701
|
command: "compare",
|
|
@@ -39358,7 +39733,7 @@ init_outputMeta();
|
|
|
39358
39733
|
init_errors3();
|
|
39359
39734
|
init_bun();
|
|
39360
39735
|
init_selfCli();
|
|
39361
|
-
import { basename as basename4, dirname as dirname9, relative as relative3, resolve as
|
|
39736
|
+
import { basename as basename4, dirname as dirname9, relative as relative3, resolve as resolve15 } from "path";
|
|
39362
39737
|
var START_MARKER = "# >>> idk completions >>>";
|
|
39363
39738
|
var END_MARKER = "# <<< idk completions <<<";
|
|
39364
39739
|
function userHome() {
|
|
@@ -39384,13 +39759,13 @@ function defaultRcPath(shell) {
|
|
|
39384
39759
|
const home = userHome();
|
|
39385
39760
|
switch (shell) {
|
|
39386
39761
|
case "zsh":
|
|
39387
|
-
return
|
|
39762
|
+
return resolve15(home, ".zshrc");
|
|
39388
39763
|
case "bash":
|
|
39389
|
-
return
|
|
39764
|
+
return resolve15(home, ".bashrc");
|
|
39390
39765
|
case "fish":
|
|
39391
|
-
return
|
|
39766
|
+
return resolve15(home, ".config/fish/config.fish");
|
|
39392
39767
|
case "powershell":
|
|
39393
|
-
return
|
|
39768
|
+
return resolve15(home, "Documents/PowerShell/Microsoft.PowerShell_profile.ps1");
|
|
39394
39769
|
}
|
|
39395
39770
|
}
|
|
39396
39771
|
function completionsSourceLine(shell, executable = "idk") {
|
|
@@ -39423,7 +39798,7 @@ function replaceManagedBlock(existing, block) {
|
|
|
39423
39798
|
}
|
|
39424
39799
|
async function installCompletions(args) {
|
|
39425
39800
|
const shell = detectShell(args.shell);
|
|
39426
|
-
const rcPath =
|
|
39801
|
+
const rcPath = resolve15(args.rcPath ?? defaultRcPath(shell));
|
|
39427
39802
|
const line = completionsSourceLine(shell, args.executable ?? "idk");
|
|
39428
39803
|
const script = runSelfCli(["completions", shell]);
|
|
39429
39804
|
if (script.exitCode !== 0) {
|
|
@@ -39467,7 +39842,7 @@ ${END_MARKER}
|
|
|
39467
39842
|
}
|
|
39468
39843
|
async function readCompletionSetup(args) {
|
|
39469
39844
|
const shell = detectShell(args.shell);
|
|
39470
|
-
const rcPath =
|
|
39845
|
+
const rcPath = resolve15(args.rcPath ?? defaultRcPath(shell));
|
|
39471
39846
|
const line = completionsSourceLine(shell, args.executable ?? "idk");
|
|
39472
39847
|
const exists = await fileExists(rcPath);
|
|
39473
39848
|
if (!exists) {
|
|
@@ -39478,7 +39853,7 @@ async function readCompletionSetup(args) {
|
|
|
39478
39853
|
return { shell, rcPath, installed, line };
|
|
39479
39854
|
}
|
|
39480
39855
|
function commonDir(paths) {
|
|
39481
|
-
const parts = paths.map((value) => dirname9(
|
|
39856
|
+
const parts = paths.map((value) => dirname9(resolve15(value)).split("/").filter(Boolean));
|
|
39482
39857
|
if (parts.length === 0)
|
|
39483
39858
|
return process.cwd();
|
|
39484
39859
|
const first = parts[0] ?? [];
|
|
@@ -39499,13 +39874,13 @@ async function writePluginTrust(args) {
|
|
|
39499
39874
|
if (args.plugins.length === 0) {
|
|
39500
39875
|
throw usageError("setup plugin-trust requires at least one --plugin path.");
|
|
39501
39876
|
}
|
|
39502
|
-
const outPath =
|
|
39877
|
+
const outPath = resolve15(args.outPath);
|
|
39503
39878
|
if (await fileExists(outPath) && !args.force) {
|
|
39504
39879
|
throw usageError(`Output file already exists: ${outPath}`, "Pass --force true to overwrite.");
|
|
39505
39880
|
}
|
|
39506
39881
|
const entries = {};
|
|
39507
39882
|
for (const plugin of args.plugins) {
|
|
39508
|
-
const path =
|
|
39883
|
+
const path = resolve15(plugin);
|
|
39509
39884
|
const digest = sha256Hex(await readTextFile(path));
|
|
39510
39885
|
const key = args.relative === false ? path : relative3(dirname9(outPath), path);
|
|
39511
39886
|
entries[key] = digest;
|
|
@@ -39517,18 +39892,18 @@ async function writePluginTrust(args) {
|
|
|
39517
39892
|
`);
|
|
39518
39893
|
return {
|
|
39519
39894
|
outPath,
|
|
39520
|
-
plugins: args.plugins.map((plugin) =>
|
|
39895
|
+
plugins: args.plugins.map((plugin) => resolve15(plugin)),
|
|
39521
39896
|
pluginCount: args.plugins.length,
|
|
39522
39897
|
pluginEntries: entries,
|
|
39523
|
-
pluginRootSuggestion: commonDir(args.plugins.map((plugin) =>
|
|
39898
|
+
pluginRootSuggestion: commonDir(args.plugins.map((plugin) => resolve15(plugin))),
|
|
39524
39899
|
updated: true
|
|
39525
39900
|
};
|
|
39526
39901
|
}
|
|
39527
39902
|
function parsePluginList(input) {
|
|
39528
|
-
return input.split(",").map((value) => value.trim()).filter(Boolean).map((value) =>
|
|
39903
|
+
return input.split(",").map((value) => value.trim()).filter(Boolean).map((value) => resolve15(value));
|
|
39529
39904
|
}
|
|
39530
39905
|
function suggestedTrustPath(baseDir = process.cwd()) {
|
|
39531
|
-
return
|
|
39906
|
+
return resolve15(baseDir, ".idk/plugin-trust.json");
|
|
39532
39907
|
}
|
|
39533
39908
|
function describePluginTrustFlags(result) {
|
|
39534
39909
|
return [
|
|
@@ -39540,7 +39915,7 @@ function describePluginTrustFlags(result) {
|
|
|
39540
39915
|
}
|
|
39541
39916
|
function inferredTuneWizardPath(scenarioPath) {
|
|
39542
39917
|
const file = basename4(scenarioPath).replace(/\.json$/i, "");
|
|
39543
|
-
return
|
|
39918
|
+
return resolve15(process.cwd(), `${file}-tune.json`);
|
|
39544
39919
|
}
|
|
39545
39920
|
|
|
39546
39921
|
// src/commands/doctor.ts
|
|
@@ -39912,7 +40287,7 @@ var eta_default = defineCommand({
|
|
|
39912
40287
|
// src/commands/experiment.ts
|
|
39913
40288
|
init_dist2();
|
|
39914
40289
|
init_zod();
|
|
39915
|
-
import { resolve as
|
|
40290
|
+
import { resolve as resolve16 } from "path";
|
|
39916
40291
|
init_errors3();
|
|
39917
40292
|
init_outputMeta();
|
|
39918
40293
|
init_bun();
|
|
@@ -39942,7 +40317,7 @@ var experiment_default = defineCommand({
|
|
|
39942
40317
|
const spec = checked.data;
|
|
39943
40318
|
if (spec.scenario.ref.length === 0)
|
|
39944
40319
|
throw usageError("Usage: idk experiment <spec>");
|
|
39945
|
-
const scenarioPath =
|
|
40320
|
+
const scenarioPath = resolve16(spec.scenario.ref);
|
|
39946
40321
|
const scenario = await readScenarioFile(scenarioPath);
|
|
39947
40322
|
const loaded = await loadRegistriesFromFlags(flags);
|
|
39948
40323
|
const valid = validateScenarioV1(scenario, loaded.modelRegistry);
|
|
@@ -39991,12 +40366,12 @@ var experiment_default = defineCommand({
|
|
|
39991
40366
|
// src/commands/evaluate.ts
|
|
39992
40367
|
init_dist2();
|
|
39993
40368
|
init_zod();
|
|
39994
|
-
import { resolve as
|
|
40369
|
+
import { resolve as resolve18 } from "path";
|
|
39995
40370
|
|
|
39996
40371
|
// src/commands/ltv.ts
|
|
39997
40372
|
init_dist2();
|
|
39998
40373
|
init_zod();
|
|
39999
|
-
import { resolve as
|
|
40374
|
+
import { resolve as resolve17 } from "path";
|
|
40000
40375
|
init_errors3();
|
|
40001
40376
|
init_outputMeta();
|
|
40002
40377
|
var strategySchema2 = string2().min(1).optional();
|
|
@@ -40035,7 +40410,7 @@ function parseHorizons(raw) {
|
|
|
40035
40410
|
}
|
|
40036
40411
|
var DEFAULT_LTV_HORIZONS = "30m,2h,24h,7d,30d,90d";
|
|
40037
40412
|
function ltvUncertaintyDraws(scenario, draws) {
|
|
40038
|
-
const uncertainty =
|
|
40413
|
+
const uncertainty = deriveMonetizationConfig2(scenario).uncertainty;
|
|
40039
40414
|
return uncertainty.enabled || draws !== undefined ? draws ?? uncertainty.draws : null;
|
|
40040
40415
|
}
|
|
40041
40416
|
function ltvStageInputs(scenario, horizons, draws) {
|
|
@@ -40097,7 +40472,7 @@ function runLtvAnalysis(args) {
|
|
|
40097
40472
|
const horizons = parseHorizons(args.horizonsRaw);
|
|
40098
40473
|
const stepSec = args.step ?? args.compiled.run.stepSec;
|
|
40099
40474
|
const runFast = resolveFastMode(args.fast, args.compiled.run.fast);
|
|
40100
|
-
const monetizationConfig =
|
|
40475
|
+
const monetizationConfig = deriveMonetizationConfig2(args.scenario);
|
|
40101
40476
|
const uncertainEnabled = monetizationConfig.uncertainty.enabled || args.draws !== undefined;
|
|
40102
40477
|
const draws = args.draws ?? monetizationConfig.uncertainty.draws;
|
|
40103
40478
|
let state = args.compiled.initial;
|
|
@@ -40157,12 +40532,12 @@ function runLtvAnalysis(args) {
|
|
|
40157
40532
|
const endWorthLog10 = args.compiled.ctx.E.absLog10(worth.amount);
|
|
40158
40533
|
const progression = progressionFactor(startWorthLog10, endWorthLog10, monetizationConfig.revenue.progressionLogSpan);
|
|
40159
40534
|
const horizonDays = h.seconds / 86400;
|
|
40160
|
-
const point =
|
|
40535
|
+
const point = estimateLtvPerUser2({
|
|
40161
40536
|
config: monetizationConfig,
|
|
40162
40537
|
horizonDays,
|
|
40163
40538
|
progression
|
|
40164
40539
|
});
|
|
40165
|
-
const distribution = uncertainEnabled && draws > 1 ?
|
|
40540
|
+
const distribution = uncertainEnabled && draws > 1 ? estimateLtvDistribution2({
|
|
40166
40541
|
config: monetizationConfig,
|
|
40167
40542
|
horizonDays,
|
|
40168
40543
|
progression,
|
|
@@ -40365,7 +40740,7 @@ var ltv_default = defineCommand({
|
|
|
40365
40740
|
};
|
|
40366
40741
|
const output = flags.format === "json" ? jsonOutput : analysis.horizons;
|
|
40367
40742
|
if (flags["artifact-out"]) {
|
|
40368
|
-
const scenarioAbs =
|
|
40743
|
+
const scenarioAbs = resolve17(process.cwd(), scenarioPath);
|
|
40369
40744
|
await writeCommandReplayArtifact({
|
|
40370
40745
|
outPath: flags["artifact-out"],
|
|
40371
40746
|
command: "ltv",
|
|
@@ -40444,7 +40819,7 @@ async function loadBalanceChecks(flags) {
|
|
|
40444
40819
|
if (!failOn) {
|
|
40445
40820
|
throw usageError("--fail-on must list error, warn, inconclusive, or unsupported");
|
|
40446
40821
|
}
|
|
40447
|
-
const path =
|
|
40822
|
+
const path = resolve18(process.cwd(), flags.checks);
|
|
40448
40823
|
let raw;
|
|
40449
40824
|
try {
|
|
40450
40825
|
raw = await readJsonFile(path);
|
|
@@ -40525,7 +40900,7 @@ var evaluate_default = defineCommand({
|
|
|
40525
40900
|
throw scenarioInvalidError(valid.issues);
|
|
40526
40901
|
}
|
|
40527
40902
|
const scenario = valid.scenario;
|
|
40528
|
-
const scenarioAbs =
|
|
40903
|
+
const scenarioAbs = resolve18(process.cwd(), scenarioPath);
|
|
40529
40904
|
const unseeded = prepareResolvedRun({
|
|
40530
40905
|
scenario: valid.scenario,
|
|
40531
40906
|
modelRegistry: loaded.modelRegistry,
|
|
@@ -40702,9 +41077,9 @@ var evaluate_default = defineCommand({
|
|
|
40702
41077
|
stageScope
|
|
40703
41078
|
});
|
|
40704
41079
|
const files = {
|
|
40705
|
-
simulate:
|
|
40706
|
-
experience:
|
|
40707
|
-
ltv:
|
|
41080
|
+
simulate: resolve18(process.cwd(), flags["out-dir"] ?? ".", "simulate.json"),
|
|
41081
|
+
experience: resolve18(process.cwd(), flags["out-dir"] ?? ".", "experience.json"),
|
|
41082
|
+
ltv: resolve18(process.cwd(), flags["out-dir"] ?? ".", "ltv.json")
|
|
40708
41083
|
};
|
|
40709
41084
|
const output = {
|
|
40710
41085
|
ok: balance?.exitCode !== 2,
|
|
@@ -40735,12 +41110,12 @@ var evaluate_default = defineCommand({
|
|
|
40735
41110
|
...balance ? { balanceChecks: balance.exitCode === 2 ? toBalanceCheckError(balance) : balance } : {}
|
|
40736
41111
|
};
|
|
40737
41112
|
if (flags["out-dir"]) {
|
|
40738
|
-
const outDir =
|
|
41113
|
+
const outDir = resolve18(process.cwd(), flags["out-dir"]);
|
|
40739
41114
|
await ensureDir(outDir);
|
|
40740
|
-
files.simulate =
|
|
40741
|
-
files.experience =
|
|
40742
|
-
files.ltv =
|
|
40743
|
-
const summaryPath =
|
|
41115
|
+
files.simulate = resolve18(outDir, "simulate.json");
|
|
41116
|
+
files.experience = resolve18(outDir, "experience.json");
|
|
41117
|
+
files.ltv = resolve18(outDir, "ltv.json");
|
|
41118
|
+
const summaryPath = resolve18(process.cwd(), flags["out-dir"], flags.format === "md" ? "summary.md" : "summary.json");
|
|
40744
41119
|
files.summary = summaryPath;
|
|
40745
41120
|
await writeTextFile(files.simulate, `${JSON.stringify({ ...simulate, _meta: simulateMeta }, null, 2)}
|
|
40746
41121
|
`);
|
|
@@ -41170,7 +41545,7 @@ var report_default = defineCommand({
|
|
|
41170
41545
|
// src/commands/simulate.ts
|
|
41171
41546
|
init_dist2();
|
|
41172
41547
|
init_zod();
|
|
41173
|
-
import { resolve as
|
|
41548
|
+
import { resolve as resolve19 } from "path";
|
|
41174
41549
|
|
|
41175
41550
|
// src/commands/_shared/simulateView.ts
|
|
41176
41551
|
function resolveEventLog(args) {
|
|
@@ -41324,11 +41699,11 @@ var simulate_default = defineCommand({
|
|
|
41324
41699
|
const input = await readScenarioFile(scenarioPath);
|
|
41325
41700
|
const loaded = await loadRegistriesFromFlags(flags);
|
|
41326
41701
|
const scenario = assertValidScenario2(validateScenarioV1(input, loaded.modelRegistry));
|
|
41327
|
-
const resumedJson = flags.resume ? await readJsonFile(
|
|
41702
|
+
const resumedJson = flags.resume ? await readJsonFile(resolve19(process.cwd(), flags.resume)).then((raw) => {
|
|
41328
41703
|
try {
|
|
41329
41704
|
return parseSimStateJSON(raw);
|
|
41330
41705
|
} catch (error) {
|
|
41331
|
-
throw cliError("SIM_STATE_INVALID_JSON", `Invalid resume state json: ${
|
|
41706
|
+
throw cliError("SIM_STATE_INVALID_JSON", `Invalid resume state json: ${resolve19(process.cwd(), flags.resume)}`, {
|
|
41332
41707
|
detail: errorDetail(error),
|
|
41333
41708
|
cause: error
|
|
41334
41709
|
});
|
|
@@ -41336,7 +41711,7 @@ var simulate_default = defineCommand({
|
|
|
41336
41711
|
}).catch((error) => {
|
|
41337
41712
|
if (error instanceof Error && error.name === "CliError")
|
|
41338
41713
|
throw error;
|
|
41339
|
-
throw cliError("SIM_STATE_INVALID_JSON", `Invalid resume state json: ${
|
|
41714
|
+
throw cliError("SIM_STATE_INVALID_JSON", `Invalid resume state json: ${resolve19(process.cwd(), flags.resume)}`, {
|
|
41340
41715
|
detail: errorDetail(error),
|
|
41341
41716
|
cause: error
|
|
41342
41717
|
});
|
|
@@ -41453,7 +41828,7 @@ var simulate_default = defineCommand({
|
|
|
41453
41828
|
const run = runScenario(effectiveScenario);
|
|
41454
41829
|
const netWorth = effectiveScenario.model.netWorth?.(effectiveScenario.ctx, run.end) ?? run.end.wallet.money;
|
|
41455
41830
|
const totalElapsedSec = priorElapsedSec + (offlineRun ? runElapsedSec(offlineRun) : 0) + runElapsedSec(run);
|
|
41456
|
-
const stateOutPath = flags["state-out"] ?
|
|
41831
|
+
const stateOutPath = flags["state-out"] ? resolve19(process.cwd(), flags["state-out"]) : undefined;
|
|
41457
41832
|
const seed = deterministicSeed;
|
|
41458
41833
|
const offlineEndWorth = offlineRun && (runScenarioInput.model.netWorth?.(runScenarioInput.ctx, offlineRun.end) ?? offlineRun.end.wallet.money);
|
|
41459
41834
|
if (stateOutPath) {
|
|
@@ -41517,7 +41892,7 @@ var simulate_default = defineCommand({
|
|
|
41517
41892
|
eventLog: run.eventLog
|
|
41518
41893
|
};
|
|
41519
41894
|
if (flags["artifact-out"]) {
|
|
41520
|
-
const scenarioAbs =
|
|
41895
|
+
const scenarioAbs = resolve19(process.cwd(), scenarioPath);
|
|
41521
41896
|
await writeCommandReplayArtifact({
|
|
41522
41897
|
outPath: flags["artifact-out"],
|
|
41523
41898
|
command: "simulate",
|
|
@@ -41552,7 +41927,7 @@ var simulate_default = defineCommand({
|
|
|
41552
41927
|
// src/commands/tune.ts
|
|
41553
41928
|
init_dist2();
|
|
41554
41929
|
init_zod();
|
|
41555
|
-
import { resolve as
|
|
41930
|
+
import { resolve as resolve21 } from "path";
|
|
41556
41931
|
init_errors3();
|
|
41557
41932
|
init_outputMeta();
|
|
41558
41933
|
|
|
@@ -41560,7 +41935,7 @@ init_outputMeta();
|
|
|
41560
41935
|
init_dist2();
|
|
41561
41936
|
init_errors3();
|
|
41562
41937
|
init_bun();
|
|
41563
|
-
import { basename as basename5, resolve as
|
|
41938
|
+
import { basename as basename5, resolve as resolve20 } from "path";
|
|
41564
41939
|
var OBJECTIVE_OPTIONS = [
|
|
41565
41940
|
{
|
|
41566
41941
|
value: "endNetWorthLog10",
|
|
@@ -41634,7 +42009,7 @@ async function runTuneWizard(args) {
|
|
|
41634
42009
|
}
|
|
41635
42010
|
const scenario = valid.scenario;
|
|
41636
42011
|
const strategyId = String(scenario.strategy?.id ?? "greedy");
|
|
41637
|
-
const suggestedOut =
|
|
42012
|
+
const suggestedOut = resolve20(args.outPath?.trim() || inferredTuneWizardPath(args.scenarioPath));
|
|
41638
42013
|
args.prompt.intro(`Tune wizard for ${basename5(args.scenarioPath)}`);
|
|
41639
42014
|
const objectiveId = await args.prompt.select("Objective", {
|
|
41640
42015
|
options: OBJECTIVE_OPTIONS.map((entry) => ({ ...entry })),
|
|
@@ -41651,7 +42026,7 @@ async function runTuneWizard(args) {
|
|
|
41651
42026
|
const tunePathRaw = await args.prompt.text("Tune spec output path", {
|
|
41652
42027
|
default: suggestedOut
|
|
41653
42028
|
});
|
|
41654
|
-
const tunePath =
|
|
42029
|
+
const tunePath = resolve20(String(tunePathRaw));
|
|
41655
42030
|
if (await fileExists(tunePath) && !args.force) {
|
|
41656
42031
|
throw usageError(`Output file already exists: ${tunePath}`, "Pass --force true to overwrite the generated tune spec.");
|
|
41657
42032
|
}
|
|
@@ -41868,7 +42243,7 @@ var tune_default = defineCommand({
|
|
|
41868
42243
|
throw usageError("Usage: idk tune <scenario> --tune <tunespec>");
|
|
41869
42244
|
}
|
|
41870
42245
|
const tuneSpecInput = wizardResult?.tuneSpec ?? await readScenarioFile(tunePath);
|
|
41871
|
-
const baselinePath = flags["baseline-artifact"] ?
|
|
42246
|
+
const baselinePath = flags["baseline-artifact"] ? resolve21(flags["baseline-artifact"]) : undefined;
|
|
41872
42247
|
const baselineRaw = baselinePath ? await readJsonFile(baselinePath) : undefined;
|
|
41873
42248
|
const seed = flags.seed ?? deriveDeterministicSeed({
|
|
41874
42249
|
command: "tune",
|
|
@@ -41932,8 +42307,8 @@ var tune_default = defineCommand({
|
|
|
41932
42307
|
};
|
|
41933
42308
|
})();
|
|
41934
42309
|
if (flags["artifact-out"]) {
|
|
41935
|
-
const scenarioAbs =
|
|
41936
|
-
const tuneAbs =
|
|
42310
|
+
const scenarioAbs = resolve21(process.cwd(), scenarioPath);
|
|
42311
|
+
const tuneAbs = resolve21(process.cwd(), tunePath);
|
|
41937
42312
|
await writeCommandReplayArtifact({
|
|
41938
42313
|
outPath: flags["artifact-out"],
|
|
41939
42314
|
command: "tune",
|
|
@@ -41999,13 +42374,13 @@ var validate_default = defineCommand({
|
|
|
41999
42374
|
// src/commands/initScenario.ts
|
|
42000
42375
|
init_zod();
|
|
42001
42376
|
init_errors3();
|
|
42002
|
-
import { resolve as
|
|
42377
|
+
import { resolve as resolve23 } from "path";
|
|
42003
42378
|
|
|
42004
42379
|
// src/lib/initWizard.ts
|
|
42005
42380
|
init_errors3();
|
|
42006
42381
|
|
|
42007
42382
|
// src/templates/scenario.ts
|
|
42008
|
-
import { resolve as
|
|
42383
|
+
import { resolve as resolve22, parse as parse5 } from "path";
|
|
42009
42384
|
var TEMPLATE_TRACKS = ["intro", "design", "personal"];
|
|
42010
42385
|
var TEMPLATE_PRESETS = ["session", "builder", "longrun"];
|
|
42011
42386
|
var INTRO_PRESET_DEFAULT = {
|
|
@@ -42873,14 +43248,14 @@ function buildInitTemplatePlan(args) {
|
|
|
42873
43248
|
const tuneSpec = createPersonalTuneSpec(preset, baseScenario);
|
|
42874
43249
|
applyPersonalBundleIdentity({ base: baseScenario, compare: compareScenario, tune: tuneSpec }, { stem, displayName });
|
|
42875
43250
|
return [
|
|
42876
|
-
{ kind: "scenario", path:
|
|
42877
|
-
{ kind: "compare", path:
|
|
42878
|
-
{ kind: "tune", path:
|
|
43251
|
+
{ kind: "scenario", path: resolve22(dir, `${stem}${ext}`), content: baseScenario },
|
|
43252
|
+
{ kind: "compare", path: resolve22(dir, `${stem}-compare-b${ext}`), content: compareScenario },
|
|
43253
|
+
{ kind: "tune", path: resolve22(dir, `${stem}-tune${ext}`), content: tuneSpec }
|
|
42879
43254
|
];
|
|
42880
43255
|
}
|
|
42881
43256
|
const content = track === "intro" ? clone2(preset === "session" ? introSession : preset === "builder" ? introBuilder : introLongrun) : createDesignScenario(preset);
|
|
42882
43257
|
applyScenarioOverrides(content, args.overrides);
|
|
42883
|
-
return [{ kind: "scenario", path:
|
|
43258
|
+
return [{ kind: "scenario", path: resolve22(args.outPath), content }];
|
|
42884
43259
|
}
|
|
42885
43260
|
function serializeTemplate(template) {
|
|
42886
43261
|
return `${JSON.stringify(template, null, 2)}
|
|
@@ -43137,7 +43512,7 @@ var initScenario_default = defineCommand({
|
|
|
43137
43512
|
})
|
|
43138
43513
|
},
|
|
43139
43514
|
async handler({ flags, prompt, terminal, runtime }) {
|
|
43140
|
-
const outPath =
|
|
43515
|
+
const outPath = resolve23(process.cwd(), flags.out);
|
|
43141
43516
|
const wizard = flags.wizard;
|
|
43142
43517
|
const wizardResult = wizard ? await runInitWizard({
|
|
43143
43518
|
prompt,
|
|
@@ -43207,11 +43582,11 @@ var init_default = defineGroup({
|
|
|
43207
43582
|
init_dist2();
|
|
43208
43583
|
init_zod();
|
|
43209
43584
|
init_outputMeta();
|
|
43210
|
-
import { resolve as
|
|
43585
|
+
import { resolve as resolve24 } from "path";
|
|
43211
43586
|
init_bun();
|
|
43212
43587
|
async function runKpiRegression(flags) {
|
|
43213
|
-
const baselinePath =
|
|
43214
|
-
const currentPath =
|
|
43588
|
+
const baselinePath = resolve24(flags.baseline);
|
|
43589
|
+
const currentPath = resolve24(flags.current);
|
|
43215
43590
|
const baseline = await readJsonFile(baselinePath);
|
|
43216
43591
|
const current = await readJsonFile(currentPath);
|
|
43217
43592
|
const output = adaptKpiRegression({
|
|
@@ -43229,6 +43604,7 @@ async function runKpiRegression(flags) {
|
|
|
43229
43604
|
await writeOutput({
|
|
43230
43605
|
format: flags.format,
|
|
43231
43606
|
outPath: flags.out,
|
|
43607
|
+
protectedInputs: [baselinePath, currentPath],
|
|
43232
43608
|
data: output,
|
|
43233
43609
|
meta: buildOutputMeta({ command: "kpi.regress" })
|
|
43234
43610
|
});
|
|
@@ -43282,7 +43658,7 @@ init_zod();
|
|
|
43282
43658
|
// src/commands/_shared/listOutput.ts
|
|
43283
43659
|
init_outputMeta();
|
|
43284
43660
|
init_bun();
|
|
43285
|
-
import { resolve as
|
|
43661
|
+
import { resolve as resolve25 } from "path";
|
|
43286
43662
|
async function writeListOutput(args) {
|
|
43287
43663
|
const outputForRender = args.format === "json" ? {
|
|
43288
43664
|
...args.payload,
|
|
@@ -43293,7 +43669,7 @@ async function writeListOutput(args) {
|
|
|
43293
43669
|
process.stdout.write(body);
|
|
43294
43670
|
return;
|
|
43295
43671
|
}
|
|
43296
|
-
const path =
|
|
43672
|
+
const path = resolve25(args.out);
|
|
43297
43673
|
await writeTextFile(path, body);
|
|
43298
43674
|
console.log(`Wrote ${args.format} output to ${path}`);
|
|
43299
43675
|
}
|
|
@@ -43442,7 +43818,7 @@ var objectives_default2 = defineGroup({
|
|
|
43442
43818
|
init_zod();
|
|
43443
43819
|
init_errors3();
|
|
43444
43820
|
init_outputMeta();
|
|
43445
|
-
import { resolve as
|
|
43821
|
+
import { resolve as resolve26 } from "path";
|
|
43446
43822
|
init_bun();
|
|
43447
43823
|
init_selfCli();
|
|
43448
43824
|
function deepEqual(a, b) {
|
|
@@ -43509,7 +43885,7 @@ var replayVerify_default = defineCommand({
|
|
|
43509
43885
|
if (!artifactPath) {
|
|
43510
43886
|
throw usageError("Usage: idk replay verify <artifact.json>");
|
|
43511
43887
|
}
|
|
43512
|
-
const artifactAbs =
|
|
43888
|
+
const artifactAbs = resolve26(process.cwd(), artifactPath);
|
|
43513
43889
|
const raw = await readJsonFile(artifactAbs).catch((error) => {
|
|
43514
43890
|
if (error instanceof Error && error.name === "CliError")
|
|
43515
43891
|
throw error;
|
|
@@ -43810,4 +44186,4 @@ export {
|
|
|
43810
44186
|
cli2 as cli
|
|
43811
44187
|
};
|
|
43812
44188
|
|
|
43813
|
-
//# debugId=
|
|
44189
|
+
//# debugId=A3CAAFDF1955C7F064756E2164756E21
|