segreant 0.1.0 → 0.2.0
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/LICENSING.md +5 -3
- package/README.md +57 -19
- package/bin/segreant-statusline.mjs +20 -0
- package/bin/segreant.mjs +19 -8
- package/dist/alerts/detect.js +13 -0
- package/dist/billing/epistemic.js +4 -0
- package/dist/billing/importer.js +29 -10
- package/dist/billing/reconcile.js +122 -32
- package/dist/billing/scope.js +7 -0
- package/dist/budget/guard.js +33 -4
- package/dist/cli/allocCmd.js +73 -4
- package/dist/cli/askModelPrices.js +59 -0
- package/dist/cli/backupCmd.js +2 -1
- package/dist/cli/basisLines.js +45 -0
- package/dist/cli/billingCmd.js +68 -9
- package/dist/cli/capitalCmd.js +2 -1
- package/dist/cli/connectCmd.js +50 -9
- package/dist/cli/egressCmd.js +15 -3
- package/dist/cli/evidenceCmd.js +6 -3
- package/dist/cli/flags.js +68 -0
- package/dist/cli/homeCmd.js +117 -0
- package/dist/cli/importCmd.js +210 -54
- package/dist/cli/launchCmd.js +2 -1
- package/dist/cli/opsCmd.js +49 -2
- package/dist/cli/planCmd.js +181 -0
- package/dist/cli/pricingDeclareCmd.js +178 -0
- package/dist/cli/quotaCmd.js +190 -0
- package/dist/cli/runCmd.js +203 -51
- package/dist/cli/showCmd.js +37 -11
- package/dist/cli/teamCmd.js +5 -5
- package/dist/cli/ui.js +24 -8
- package/dist/cli/valueCmd.js +189 -14
- package/dist/cli.js +102 -21
- package/dist/config.js +95 -6
- package/dist/connect/antigravity.js +310 -0
- package/dist/connect/claudeCode.js +43 -3
- package/dist/connect/codex.js +38 -1
- package/dist/connect/importShared.js +58 -5
- package/dist/connect/opencode.js +37 -9
- package/dist/connect/protobuf.js +126 -0
- package/dist/connect/registry.js +46 -0
- package/dist/connect/repoRoot.js +112 -0
- package/dist/cost/basis.js +168 -0
- package/dist/cost/declarations.js +37 -0
- package/dist/cost/exactPricing.js +4 -1
- package/dist/cost/firstSight.js +98 -0
- package/dist/cost/overrides.js +230 -0
- package/dist/cost/pricing.js +228 -15
- package/dist/cost/reprice.js +130 -0
- package/dist/dashboard/claim-support.js +11 -8
- package/dist/dashboard/contracts.js +23 -3
- package/dist/dashboard/routes.js +980 -112
- package/dist/dashboard/sessions.js +345 -0
- package/dist/dashboard/settings.js +2 -1
- package/dist/dashboard/value-cache.js +105 -0
- package/dist/dashboard/web/app/components/claimInspector.js +6 -5
- package/dist/dashboard/web/app/components/drawer.js +105 -35
- package/dist/dashboard/web/app/components/ruleFields.js +144 -0
- package/dist/dashboard/web/app/components/spine.js +54 -24
- package/dist/dashboard/web/app/components/workflow.js +82 -0
- package/dist/dashboard/web/app/core/actions.js +734 -23
- package/dist/dashboard/web/app/core/api.js +37 -1
- package/dist/dashboard/web/app/core/chain.js +11 -4
- package/dist/dashboard/web/app/core/claimLayers.js +179 -51
- package/dist/dashboard/web/app/core/claimTypes.js +30 -0
- package/dist/dashboard/web/app/core/fmt.js +19 -2
- package/dist/dashboard/web/app/core/generated-contract.js +23 -3
- package/dist/dashboard/web/app/core/generated-payload-contract.js +1944 -465
- package/dist/dashboard/web/app/core/generated-types.js +1 -1
- package/dist/dashboard/web/app/core/griffin.js +10 -0
- package/dist/dashboard/web/app/core/jobs.js +12 -0
- package/dist/dashboard/web/app/core/period.js +35 -0
- package/dist/dashboard/web/app/core/periodWords.js +10 -0
- package/dist/dashboard/web/app/core/periods.js +32 -0
- package/dist/dashboard/web/app/core/pricingActions.js +264 -0
- package/dist/dashboard/web/app/core/privacyText.js +78 -0
- package/dist/dashboard/web/app/core/reconcileWords.js +63 -0
- package/dist/dashboard/web/app/core/registry.js +79 -46
- package/dist/dashboard/web/app/core/slug.js +9 -0
- package/dist/dashboard/web/app/core/theme.js +48 -0
- package/dist/dashboard/web/app/core/view.js +69 -0
- package/dist/dashboard/web/app/main.js +197 -15
- package/dist/dashboard/web/app/views/allocation.js +249 -61
- package/dist/dashboard/web/app/views/control.js +57 -16
- package/dist/dashboard/web/app/views/data.js +4 -4
- package/dist/dashboard/web/app/views/evidence.js +336 -87
- package/dist/dashboard/web/app/views/privacy.js +13 -0
- package/dist/dashboard/web/app/views/spend.js +30 -15
- package/dist/dashboard/web/app/views/statement.js +189 -0
- package/dist/dashboard/web/app/views/system.js +44 -4
- package/dist/dashboard/web/app/views/value.js +105 -59
- package/dist/dashboard/web/app/views/workbench.js +312 -0
- package/dist/dashboard/web/classic.html +16 -4
- package/dist/dashboard/web/index.html +1 -1
- package/dist/dashboard/web/styles/app.css +536 -23
- package/dist/demo/seed.js +153 -14
- package/dist/economics/ledger.js +1 -1
- package/dist/egress/policy.js +129 -5
- package/dist/export/allocationCsv.js +70 -0
- package/dist/export/billingCsv.js +6 -2
- package/dist/export/csv.js +10 -2
- package/dist/export/economic.js +6 -2
- package/dist/export/statementCsv.js +104 -0
- package/dist/git/correlate.js +86 -9
- package/dist/git/quality.js +107 -24
- package/dist/git/repoScope.js +207 -0
- package/dist/git/survivalStore.js +186 -0
- package/dist/guide.js +9 -4
- package/dist/plans/detect.js +147 -0
- package/dist/proxy/server.js +134 -14
- package/dist/proxy/usage.js +9 -4
- package/dist/quota/limits.js +138 -0
- package/dist/quota/statusline.js +68 -0
- package/dist/quota/view.js +122 -0
- package/dist/scan/knownApps.js +6 -2
- package/dist/scan/scan.js +9 -0
- package/dist/store/allocation.js +14 -0
- package/dist/store/db.js +259 -34
- package/dist/store/realization.js +78 -14
- package/dist/store/schema.js +59 -1
- package/dist/store/scopeEvidence.js +67 -0
- package/dist/util/pool.js +19 -0
- package/dist/util/time.js +7 -0
- package/dist/value/realization.js +321 -30
- package/dist/value/report.js +28 -8
- package/dist/value/selfReported.js +10 -9
- package/docs/ARCHITECTURE.md +8 -4
- package/docs/CAPABILITY-EVIDENCE-CONTRACT.md +33 -11
- package/docs/CLI-JSON-CONTRACT.md +38 -0
- package/docs/DATA-BOUNDARIES.md +66 -6
- package/docs/FAQ.md +43 -9
- package/docs/GETTING-STARTED.md +19 -12
- package/docs/GUIDE.md +81 -18
- package/docs/INTEGRATIONS.md +9 -9
- package/docs/METHODOLOGY.md +3 -2
- package/docs/NEUTRALITY.md +1 -1
- package/docs/PRIVACY.md +51 -0
- package/docs/README.md +1 -0
- package/docs/RELEASE-GATE.md +52 -7
- package/docs/THREAT-MODEL.md +2 -1
- package/package.json +3 -2
- package/pricing/models.json +1366 -60
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/** Generated from src/dashboard/shared-types.ts; do not edit by hand. */
|
|
2
|
-
/** Source SHA-256:
|
|
2
|
+
/** Source SHA-256: 2a97a9ba8ac2b9a19eebcf48c7b5aa90f76271509d93f460ec339ad773fc43a6 */
|
|
3
3
|
/**
|
|
4
4
|
* Canonical no-runtime dashboard payload types shared by server contracts and
|
|
5
5
|
* the browser client. Edit this file first; the build generates the browser copy
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The Segreant griffin, the same mark the website and the npm page use.
|
|
3
|
+
*
|
|
4
|
+
* Generated from `web/assets/brand/mark-plain.svg` (its single path, without
|
|
5
|
+
* the square background) so the dashboard and the site cannot drift apart.
|
|
6
|
+
* Inline rather than fetched: the dashboard loads nothing it does not serve.
|
|
7
|
+
*/
|
|
8
|
+
export const GRIFFIN_VIEWBOX = '0 0 128 128';
|
|
9
|
+
export const GRIFFIN_TRANSFORM = 'translate(3.57904 6.02078) scale(0.0957543)';
|
|
10
|
+
export const GRIFFIN_PATH = 'M878.500,1156.702 C876.300,1155.457 873.946,1153.283 873.268,1151.871 C870.943,1147.025 868.746,1136.494 868.623,1129.610 L868.500,1122.721 L866.843,1121.674 C862.574,1118.977 849.000,1127.586 849.000,1132.991 C849.000,1134.358 847.223,1136.849 844.393,1139.447 L839.787,1143.676 L838.606,1149.588 L837.426,1155.500 L835.312,1155.807 C830.643,1156.484 821.292,1145.252 818.485,1135.596 L816.803,1129.810 L817.501,1120.993 L818.198,1112.176 L815.631,1110.494 L813.064,1108.812 L810.282,1109.734 C806.683,1110.927 799.427,1120.471 796.631,1127.688 C792.830,1137.501 789.594,1137.425 784.500,1127.403 L781.500,1121.500 L781.500,1112.376 L781.500,1103.253 L787.275,1091.648 C790.452,1085.265 793.911,1079.583 794.961,1079.021 C796.012,1078.458 800.795,1075.005 805.589,1071.347 C816.213,1063.241 822.555,1060.968 836.519,1060.264 C846.515,1059.760 852.878,1060.646 866.968,1064.503 C873.985,1066.424 877.372,1066.392 881.649,1064.362 C885.504,1062.533 888.621,1057.375 889.518,1051.342 C890.862,1042.298 883.945,1024.251 875.638,1015.128 C873.321,1012.583 870.241,1008.891 868.794,1006.924 C866.311,1003.548 860.984,999.337 849.500,991.673 C846.750,989.838 842.103,986.693 839.173,984.684 C836.243,982.675 831.743,980.372 829.173,979.566 C826.603,978.760 820.450,976.448 815.500,974.429 C804.203,969.821 785.918,963.144 780.000,961.466 C775.478,960.183 764.091,955.310 758.500,952.265 C756.850,951.366 753.250,949.525 750.500,948.173 C743.706,944.832 737.488,940.566 731.428,935.086 C728.638,932.564 724.683,929.229 722.639,927.677 C716.564,923.061 708.428,912.373 703.965,903.143 C701.666,898.389 699.171,893.803 698.419,892.950 C696.456,890.724 694.209,883.903 691.849,873.000 C689.610,862.659 688.937,860.982 685.244,856.545 L682.785,853.591 L683.258,848.669 C683.738,843.670 682.841,837.727 680.855,832.750 C680.088,830.828 679.041,830.000 677.379,830.000 L675.000,830.000 L675.000,834.770 C675.000,837.394 675.850,842.681 676.888,846.520 C677.926,850.359 679.072,857.799 679.434,863.053 L680.092,872.607 L678.296,874.183 C675.323,876.792 669.891,879.203 663.768,880.631 C655.644,882.526 647.906,886.679 637.474,894.741 L628.489,901.685 L625.566,907.092 C623.958,910.067 621.386,914.077 619.851,916.005 C615.729,921.180 611.930,931.393 611.334,938.905 L610.811,945.500 L613.402,953.602 C614.908,958.310 617.485,963.658 619.555,966.368 C624.108,972.331 631.631,990.825 632.465,998.107 C632.819,1001.191 632.581,1006.816 631.937,1010.607 C630.274,1020.401 625.386,1032.701 622.374,1034.675 L619.918,1036.284 L617.709,1035.156 C616.494,1034.536 614.283,1033.049 612.795,1031.853 L610.090,1029.677 L603.295,1032.288 C585.427,1039.154 566.989,1046.912 562.485,1049.460 C545.935,1058.823 535.983,1065.254 526.747,1072.557 C515.110,1081.758 503.246,1094.241 498.891,1101.868 C495.523,1107.764 488.877,1113.156 481.653,1115.853 C478.819,1116.911 471.379,1118.526 465.119,1119.442 C445.250,1122.349 437.668,1126.409 432.705,1136.794 C430.761,1140.861 428.788,1143.632 427.692,1143.835 C426.577,1144.041 424.031,1142.315 421.054,1139.335 L416.224,1134.500 L413.675,1126.192 L411.126,1117.883 L411.691,1111.141 L412.257,1104.399 L410.499,1102.642 C408.353,1100.496 407.576,1100.774 399.395,1106.612 L392.906,1111.242 L389.920,1117.158 C388.278,1120.411 386.498,1125.374 385.964,1128.187 C384.860,1134.013 383.567,1136.000 380.880,1136.000 C377.875,1136.000 371.573,1129.552 367.950,1122.768 L364.603,1116.500 L364.178,1107.727 C363.926,1102.514 364.249,1097.184 364.974,1094.593 L366.195,1090.233 L365.062,1088.116 C364.342,1086.770 362.955,1086.000 361.252,1086.000 C357.437,1086.000 348.670,1095.468 346.536,1101.892 C344.718,1107.366 342.026,1110.344 339.360,1109.831 C336.430,1109.267 333.256,1103.415 331.967,1096.202 L330.758,1089.439 L332.941,1081.483 C335.600,1071.791 339.486,1064.810 347.237,1055.805 C357.661,1043.693 372.301,1034.822 387.366,1031.488 C398.497,1029.025 406.253,1029.800 417.500,1034.499 C422.450,1036.567 430.466,1039.637 435.314,1041.320 L444.129,1044.380 L451.314,1043.343 C460.249,1042.054 471.865,1037.085 483.916,1029.398 C500.207,1019.005 509.581,1011.021 518.432,1000.000 C529.389,986.358 528.734,978.800 514.903,959.238 C511.505,954.432 503.787,945.206 497.753,938.735 C490.761,931.236 486.480,925.797 485.948,923.735 C485.490,921.956 483.776,915.535 482.141,909.468 L479.167,898.435 L480.073,887.164 C480.735,878.932 481.694,874.087 483.629,869.196 C487.716,858.867 489.935,854.854 494.045,850.364 C496.127,848.090 498.503,844.940 499.326,843.364 C501.051,840.060 517.277,824.410 525.151,818.456 C535.720,810.465 565.688,791.000 567.422,791.000 C568.767,791.000 586.788,783.714 591.500,781.265 L596.500,778.667 L604.000,778.866 C614.976,779.156 629.985,782.493 640.398,786.956 C642.791,787.982 646.895,790.460 649.519,792.463 C654.707,796.423 659.205,796.963 659.824,793.701 C660.412,790.604 657.156,787.056 648.684,781.560 C637.588,774.362 633.548,772.432 625.427,770.449 C621.617,769.519 615.303,767.666 611.396,766.331 C607.490,764.996 600.065,763.009 594.896,761.914 C589.728,760.820 580.550,758.884 574.500,757.613 C557.968,754.138 537.954,746.501 531.018,741.021 C529.083,739.492 525.727,737.399 523.560,736.371 C518.803,734.112 514.017,726.670 511.015,716.866 C509.898,713.217 508.334,709.693 507.540,709.033 C505.289,707.165 502.432,708.682 498.564,713.799 C496.609,716.384 493.618,720.300 491.918,722.500 C486.740,729.199 482.006,737.937 479.510,745.404 C476.661,753.925 474.008,768.529 474.003,775.715 L474.000,780.930 L471.897,782.055 C469.317,783.436 441.507,782.525 437.301,780.922 C435.760,780.335 428.425,778.491 421.000,776.824 C407.765,773.853 389.428,768.075 381.500,764.379 C362.846,755.681 331.167,735.968 321.500,727.042 C315.124,721.154 303.195,712.768 296.500,709.466 C292.650,707.568 286.421,705.382 282.659,704.610 L275.817,703.206 L268.659,705.223 C253.809,709.407 246.805,715.037 239.874,728.359 C237.584,732.760 233.127,739.138 229.970,742.532 C226.813,745.926 223.628,749.782 222.894,751.102 C222.002,752.703 221.549,757.321 221.529,765.000 L221.500,776.500 L219.249,776.820 C213.847,777.587 200.440,767.839 195.645,759.657 C194.255,757.286 192.199,751.838 191.077,747.552 L189.035,739.759 L191.164,730.629 C192.334,725.608 193.492,719.406 193.736,716.846 L194.180,712.192 L192.211,711.567 C191.128,711.223 188.081,711.675 185.442,712.571 C174.923,716.142 167.431,723.189 163.295,733.402 C161.758,737.198 159.733,740.783 158.795,741.368 L157.090,742.433 L154.450,739.967 C144.956,731.097 143.251,709.038 150.914,694.220 C151.972,692.174 155.111,687.968 157.889,684.872 C162.635,679.586 168.370,675.835 186.927,665.876 C189.361,664.569 191.527,662.966 191.739,662.314 C192.527,659.888 187.258,657.811 180.204,657.766 C171.937,657.715 164.795,659.919 157.927,664.644 C155.146,666.557 152.113,667.968 151.186,667.780 L149.500,667.438 L149.606,661.874 C149.804,651.482 159.950,634.566 169.742,628.302 C181.910,620.518 198.238,615.569 208.149,616.659 C218.117,617.756 237.227,623.640 247.508,628.778 C253.012,631.528 259.939,634.498 262.901,635.378 C265.862,636.257 271.111,638.107 274.563,639.489 C278.016,640.870 281.945,642.000 283.295,642.000 C284.644,642.000 287.682,642.682 290.046,643.516 C292.409,644.350 307.203,646.302 322.921,647.853 L351.500,650.674 L365.500,649.784 C373.200,649.295 383.126,649.140 387.559,649.439 C400.118,650.286 400.586,648.772 392.461,633.558 L386.922,623.185 L384.417,609.843 C383.040,602.504 381.470,592.000 380.930,586.500 L379.947,576.500 L381.436,572.500 C382.255,570.300 383.629,564.000 384.489,558.500 C386.050,548.516 388.104,538.399 389.397,534.324 C389.783,533.108 389.609,531.876 388.997,531.498 C386.971,530.246 382.825,534.147 380.432,539.558 C378.728,543.410 377.353,545.124 375.617,545.560 C372.796,546.268 369.472,544.751 368.059,542.111 C366.966,540.068 364.761,529.608 362.932,517.790 C361.206,506.638 363.345,481.439 366.819,472.000 C373.015,455.165 375.436,449.933 380.866,441.641 C384.159,436.614 387.835,430.446 389.035,427.936 C390.490,424.894 392.668,422.455 395.568,420.620 C397.961,419.106 400.818,416.413 401.916,414.635 C403.015,412.858 410.893,405.011 419.424,397.198 C437.266,380.856 448.594,367.549 454.699,355.758 C459.508,346.471 460.106,341.601 457.408,333.684 C455.577,328.311 449.150,320.749 441.878,315.411 C433.018,308.907 407.797,301.842 394.500,302.139 C392.850,302.175 387.900,301.880 383.500,301.481 L375.500,300.757 L367.000,302.489 C356.458,304.638 354.083,305.842 349.601,311.312 C344.655,317.348 343.000,321.881 343.000,329.396 C343.000,336.353 341.570,339.000 337.813,339.000 C334.273,339.000 326.358,332.282 322.831,326.284 C316.142,314.907 315.033,312.328 312.890,303.151 L310.708,293.801 L311.980,285.251 L313.252,276.700 L317.799,267.617 C322.369,258.489 325.750,254.494 337.160,244.746 C345.398,237.709 360.803,230.616 379.500,225.252 C381.679,224.627 383.783,222.459 387.187,217.335 C392.215,209.766 401.010,201.470 410.195,195.630 C426.867,185.030 462.220,173.745 501.000,166.645 C513.062,164.437 536.877,159.216 545.000,156.999 C549.125,155.873 556.100,154.029 560.500,152.901 C593.461,144.452 624.421,130.456 638.670,117.561 C644.874,111.946 648.280,110.682 649.393,113.582 C651.407,118.830 648.390,133.059 642.664,145.311 L638.291,154.670 L630.717,162.895 C623.111,171.155 614.075,178.178 604.180,183.521 C598.476,186.601 596.640,188.800 597.973,190.956 L598.806,192.304 L606.726,191.643 C617.211,190.769 630.391,186.889 641.704,181.346 C646.767,178.865 651.717,176.989 652.704,177.177 C654.016,177.425 654.580,178.609 654.798,181.572 L655.097,185.626 L650.365,192.563 C639.676,208.234 633.269,213.827 616.166,222.417 C611.170,224.926 606.654,227.779 606.131,228.756 C604.778,231.284 606.202,233.486 616.184,244.298 C620.944,249.454 626.835,256.559 629.276,260.086 C633.750,266.552 646.714,293.084 649.022,300.500 C649.707,302.700 650.954,305.850 651.794,307.500 C653.764,311.374 655.997,323.087 655.999,329.559 C655.999,332.341 655.380,335.466 654.621,336.503 L653.242,338.389 L648.871,337.098 C646.467,336.388 643.014,334.899 641.197,333.789 C639.381,332.680 637.623,332.043 637.292,332.375 C636.960,332.706 637.434,335.284 638.344,338.104 C639.255,340.923 640.000,343.999 640.000,344.940 C640.000,345.881 641.573,353.592 643.497,362.075 L646.993,377.500 L647.600,391.500 C647.933,399.200 647.739,409.775 647.169,415.000 C644.845,436.284 631.659,470.539 619.576,486.679 L614.500,493.461 L614.781,501.480 C615.331,517.192 626.504,550.422 634.960,561.500 C637.059,564.250 640.005,568.975 641.506,572.000 C645.149,579.338 649.232,584.300 662.963,598.071 C674.872,610.016 691.300,622.109 704.963,628.989 C708.518,630.779 713.243,633.397 715.463,634.807 C720.326,637.895 746.934,649.991 748.875,649.996 C749.631,649.998 752.781,651.095 755.875,652.434 C758.969,653.772 766.450,656.978 772.500,659.558 C795.652,669.430 818.814,685.448 835.500,703.127 C841.974,709.986 865.789,742.823 869.516,750.030 C870.409,751.759 875.590,759.997 881.029,768.337 C893.863,788.018 894.466,788.980 900.261,798.944 C914.002,822.576 929.468,842.096 943.422,853.420 L951.345,859.849 L958.922,862.346 C963.090,863.719 969.650,865.129 973.500,865.478 L980.500,866.114 L987.213,864.201 C995.372,861.877 1003.783,857.026 1010.102,851.000 L1014.821,846.500 L1016.545,840.500 C1017.493,837.200 1019.558,832.074 1021.134,829.109 L1024.000,823.718 L1024.000,815.569 C1024.000,806.661 1022.185,798.172 1019.151,792.891 C1018.079,791.026 1016.497,787.006 1015.634,783.958 C1014.054,778.375 1008.454,769.470 1002.455,763.000 C1000.670,761.075 998.150,757.406 996.855,754.846 C994.458,750.109 982.653,735.891 973.368,726.558 C970.546,723.721 967.103,719.172 965.717,716.450 C964.332,713.727 961.962,710.375 960.452,709.000 C957.082,705.932 946.422,689.760 942.013,681.026 C937.111,671.315 932.396,658.317 930.987,650.630 C929.143,640.569 930.205,624.599 933.447,613.615 C938.027,598.097 945.731,587.532 962.556,573.695 C975.812,562.794 995.827,553.305 1013.812,549.397 C1043.312,542.986 1051.757,540.508 1063.343,534.869 L1068.186,532.511 L1070.187,534.983 C1072.669,538.047 1072.304,540.802 1067.958,551.800 L1064.730,559.969 L1065.956,561.447 C1066.630,562.260 1069.729,563.871 1072.841,565.029 C1081.451,568.231 1086.831,571.703 1094.368,578.922 C1106.040,590.102 1113.753,603.249 1119.640,622.000 C1124.413,637.204 1125.150,660.745 1120.971,664.526 L1119.096,666.223 L1115.408,663.943 L1111.719,661.663 L1110.360,663.523 C1109.612,664.546 1108.942,667.659 1108.871,670.441 C1108.254,694.653 1108.377,697.984 1110.230,707.391 C1111.302,712.831 1113.714,720.649 1115.589,724.766 C1119.455,733.248 1119.618,734.242 1117.435,736.054 L1115.871,737.352 L1108.978,735.132 C1099.692,732.141 1086.121,722.973 1077.277,713.716 L1070.054,706.155 L1064.584,704.931 C1058.650,703.603 1055.494,701.901 1046.172,695.000 C1042.829,692.525 1038.614,689.593 1036.805,688.485 C1031.929,685.497 1016.181,665.331 1014.951,660.500 C1014.391,658.300 1012.763,652.675 1011.334,648.000 C1009.905,643.325 1007.910,635.766 1006.901,631.202 C1004.798,621.693 1000.049,612.060 995.629,608.341 L992.591,605.785 L988.045,606.232 C981.007,606.925 972.362,611.496 969.279,616.155 L966.656,620.119 L965.745,632.213 L964.834,644.308 L966.454,650.755 C968.555,659.121 972.579,667.042 981.311,680.000 C990.530,693.680 995.875,700.925 1003.074,709.500 C1018.672,728.077 1036.827,751.598 1040.798,758.374 C1045.429,766.275 1050.472,778.094 1054.294,790.000 L1057.343,799.500 L1057.393,818.000 L1057.443,836.500 L1053.218,847.930 L1048.992,859.360 L1041.855,868.689 C1034.117,878.806 1026.922,884.791 1013.264,892.472 L1005.149,897.035 L995.396,899.072 C983.662,901.522 976.011,901.654 961.729,899.650 L950.958,898.139 L942.229,893.422 C937.428,890.828 931.475,887.273 929.000,885.523 C926.525,883.772 923.070,881.773 921.322,881.079 C919.575,880.385 916.972,878.045 915.538,875.878 C914.105,873.712 910.789,870.621 908.170,869.010 C905.111,867.127 902.769,864.725 901.619,862.290 C900.636,860.205 897.821,855.800 895.365,852.500 C887.084,841.375 872.155,818.978 868.450,812.124 C866.852,809.167 864.548,806.095 863.330,805.297 C860.138,803.205 856.743,804.031 855.192,807.276 L853.866,810.053 L855.417,830.825 C857.527,859.078 863.509,885.413 871.173,900.192 C875.253,908.058 886.809,922.127 893.670,927.581 C901.302,933.647 909.725,938.048 921.000,941.860 C937.208,947.339 942.776,950.045 948.500,955.220 C956.176,962.160 964.929,979.082 964.988,987.100 C964.995,987.979 963.658,990.679 962.018,993.100 C960.378,995.520 959.028,998.322 959.018,999.327 C959.008,1000.331 960.507,1004.831 962.350,1009.327 C966.560,1019.599 974.249,1042.255 978.190,1056.000 L981.201,1066.500 L981.252,1076.033 C981.335,1091.365 978.208,1100.965 970.161,1110.089 C967.934,1112.614 963.697,1116.342 960.745,1118.375 C954.155,1122.914 937.161,1128.723 919.500,1132.474 C912.350,1133.993 904.925,1136.043 903.000,1137.030 C898.076,1139.555 893.527,1144.621 890.717,1150.709 C886.479,1159.892 885.222,1160.508 878.500,1156.702 Z M365.305,640.098 L359.109,639.500 L355.805,636.336 C353.987,634.596 349.618,632.011 346.096,630.593 C336.605,626.772 325.691,619.604 322.623,615.177 C319.897,611.243 317.874,609.859 312.500,608.246 L309.500,607.346 L308.359,602.923 C307.175,598.331 298.866,581.870 296.118,578.670 L294.546,576.841 L290.326,577.474 C282.765,578.608 278.361,575.044 275.021,565.088 C273.838,561.562 271.700,556.387 270.269,553.588 C267.355,547.885 260.939,530.136 257.949,519.500 C253.757,504.593 251.529,499.247 246.604,492.275 L241.500,485.050 L236.173,482.370 C232.369,480.456 228.779,479.557 223.619,479.226 L216.392,478.762 L210.291,481.227 C206.935,482.582 202.848,484.773 201.210,486.096 C196.898,489.574 191.986,497.431 189.750,504.425 C186.645,514.136 186.178,515.000 184.024,515.000 C180.632,515.000 174.233,508.240 170.907,501.142 C169.195,497.489 167.165,493.306 166.397,491.846 C165.629,490.386 165.000,488.137 165.000,486.849 C165.000,482.909 169.883,469.576 173.567,463.459 C177.476,456.967 181.000,449.392 181.000,447.480 C181.000,445.699 178.650,445.000 172.668,445.001 C165.287,445.002 156.589,449.362 149.910,456.408 C146.995,459.483 143.853,462.000 142.927,462.000 C137.688,462.000 136.937,450.358 141.354,437.595 C144.848,427.501 150.751,419.109 158.405,413.355 C170.739,404.083 177.445,401.997 197.250,401.270 L212.000,400.728 L212.000,398.560 C212.000,395.639 206.610,390.608 200.500,387.826 L195.500,385.550 L187.500,385.525 L179.500,385.500 L179.183,383.269 C178.701,379.877 187.195,372.001 196.061,367.620 C200.357,365.497 207.099,363.226 211.942,362.272 L220.358,360.614 L229.436,362.158 C245.807,364.943 258.982,373.416 268.144,387.050 C271.038,391.358 276.361,404.525 281.027,418.921 C285.948,434.102 298.349,456.306 312.068,474.500 C320.126,485.186 335.875,502.311 345.512,510.866 C349.311,514.237 352.822,517.717 353.315,518.598 C353.808,519.478 354.854,525.414 355.639,531.788 C357.239,544.764 361.204,557.479 367.079,568.475 L370.832,575.500 L370.805,589.500 C370.786,599.826 371.219,604.943 372.459,609.000 C373.383,612.025 374.525,616.975 374.997,620.000 C375.469,623.025 376.338,627.072 376.928,628.993 C377.517,630.915 378.000,633.502 378.000,634.743 C378.000,637.246 374.698,641.085 372.750,640.848 C372.062,640.764 368.712,640.426 365.305,640.098 Z M739.000,630.592 C725.115,625.257 717.028,620.948 702.000,610.879 C692.752,604.683 669.935,584.624 665.737,579.000 C646.063,552.644 644.239,549.494 635.636,527.000 C630.981,514.828 629.047,503.587 630.819,499.000 C631.940,496.099 639.354,486.937 653.577,470.878 C659.120,464.620 665.453,457.198 667.650,454.384 C672.848,447.729 681.296,437.935 687.179,431.744 C689.752,429.036 692.560,425.340 693.418,423.531 L694.979,420.241 L695.240,357.870 C695.428,312.777 695.821,294.946 696.661,293.500 C697.876,291.405 709.254,282.978 724.500,272.881 C744.452,259.668 752.637,254.095 755.387,251.853 C756.974,250.559 760.427,248.150 763.059,246.500 C769.576,242.414 779.182,235.981 787.000,230.466 C795.953,224.150 803.695,218.967 823.500,206.033 C832.850,199.927 852.200,186.809 866.500,176.884 C880.800,166.958 895.603,156.784 899.396,154.276 C910.083,147.207 935.573,129.422 945.000,122.457 C953.533,116.153 965.605,107.689 980.500,97.567 C984.900,94.577 991.265,90.189 994.645,87.815 C998.024,85.442 1005.899,79.987 1012.145,75.694 C1018.390,71.400 1028.634,64.312 1034.910,59.944 C1041.185,55.575 1046.907,52.000 1047.625,52.000 C1048.342,52.000 1049.505,53.013 1050.208,54.250 L1051.486,56.500 L1051.493,102.684 L1051.500,148.869 L1048.500,152.026 C1046.850,153.762 1043.700,156.105 1041.500,157.231 C1039.300,158.358 1033.831,161.691 1029.346,164.640 C1024.861,167.588 1020.870,170.000 1020.476,170.000 C1019.436,170.000 1001.199,180.759 996.500,184.145 C994.300,185.730 989.616,188.375 986.090,190.023 C982.565,191.671 976.040,195.208 971.590,197.882 C967.141,200.557 958.084,205.458 951.463,208.773 C944.843,212.089 937.489,216.421 935.120,218.400 C932.751,220.379 928.492,222.687 925.656,223.529 C922.820,224.371 916.675,227.130 912.000,229.662 C907.325,232.193 897.875,237.177 891.000,240.736 C884.125,244.296 878.028,247.592 877.452,248.059 C876.876,248.527 871.251,251.562 864.952,254.803 C849.673,262.667 821.244,277.776 817.500,280.022 C815.850,281.012 807.525,285.391 799.000,289.754 C746.127,316.809 738.650,320.870 736.250,323.837 L735.000,325.382 L735.000,379.757 C735.000,418.761 734.675,434.749 733.851,436.316 C731.824,440.171 728.032,444.333 720.018,451.500 C715.713,455.350 710.264,460.975 707.908,464.000 C705.553,467.025 694.938,478.187 684.318,488.804 C673.341,499.779 664.738,509.194 664.379,510.625 C663.605,513.710 664.508,520.826 666.124,524.373 C668.044,528.587 671.127,527.723 679.053,520.750 C682.961,517.312 688.372,512.797 691.079,510.715 C693.785,508.633 696.000,506.585 696.000,506.163 C696.000,505.741 701.410,500.314 708.022,494.103 L720.044,482.810 L730.772,481.425 C746.957,479.335 752.911,478.353 759.500,476.686 C762.800,475.851 767.975,474.895 771.000,474.561 C777.830,473.807 793.064,471.360 803.000,469.419 C816.611,466.761 837.265,463.297 853.000,461.034 C861.525,459.808 872.775,457.964 878.000,456.936 C883.225,455.908 889.750,454.798 892.500,454.470 C895.250,454.141 900.650,453.236 904.500,452.458 C910.478,451.250 927.581,448.400 964.500,442.462 C978.229,440.254 996.202,436.970 1003.500,435.336 C1007.350,434.474 1012.075,433.575 1014.000,433.339 C1024.947,431.995 1046.064,428.838 1049.500,428.032 C1054.850,426.776 1058.002,427.639 1057.992,430.358 C1057.988,431.536 1054.458,437.450 1050.148,443.500 C1034.211,465.871 1028.302,474.128 1020.045,485.568 L1011.589,497.282 L1007.545,499.119 C1005.320,500.129 1002.271,500.966 1000.768,500.978 C999.266,500.990 991.166,502.177 982.768,503.616 C956.449,508.126 948.126,509.375 930.000,511.539 C922.575,512.426 912.000,513.828 906.500,514.655 C901.000,515.482 891.550,516.795 885.500,517.572 C879.450,518.349 872.250,519.406 869.500,519.920 C859.778,521.738 845.395,523.966 837.000,524.953 C832.325,525.504 820.850,527.087 811.500,528.473 C802.150,529.859 789.325,531.653 783.000,532.461 C776.675,533.269 763.625,535.057 754.000,536.435 C715.886,541.892 716.992,541.627 717.012,545.285 C717.029,548.504 719.277,556.055 723.773,568.000 C725.946,573.775 729.155,582.325 730.903,587.000 C733.884,594.974 745.527,618.896 749.986,626.209 C752.404,630.175 752.548,632.576 750.418,633.393 C748.291,634.209 748.634,634.294 739.000,630.592 Z M754.655,454.171 L753.000,452.343 L753.000,434.622 L753.000,416.901 L754.431,414.133 C756.079,410.946 759.906,409.114 772.500,405.484 C790.768,400.219 805.797,395.652 817.000,391.963 C823.325,389.881 833.225,386.739 839.000,384.982 C844.775,383.224 854.225,380.086 860.000,378.008 C865.775,375.930 874.775,372.985 880.000,371.463 C901.337,365.251 929.882,356.470 953.000,349.006 C977.564,341.076 1012.961,329.844 1027.000,325.526 C1031.125,324.257 1043.155,320.470 1053.733,317.109 C1064.312,313.749 1073.424,311.000 1073.983,311.000 C1074.542,311.000 1075.900,311.900 1077.000,313.000 L1079.000,315.000 L1079.000,348.453 C1079.000,382.430 1078.704,385.455 1075.158,387.669 C1072.157,389.543 1039.098,398.000 1034.772,398.000 C1033.497,398.000 1027.964,399.147 1022.477,400.549 C1016.990,401.951 1009.350,403.760 1005.500,404.570 C1001.650,405.380 993.721,407.832 987.880,410.020 L977.260,413.997 L972.880,413.428 C965.737,412.499 908.661,424.539 900.705,428.653 C898.899,429.586 894.714,430.384 891.014,430.500 C884.863,430.693 863.697,434.312 850.500,437.426 C847.200,438.205 838.425,440.198 831.000,441.855 C823.575,443.511 812.342,446.052 806.037,447.501 C799.733,448.951 791.183,450.529 787.037,451.009 C782.892,451.490 775.675,452.806 771.000,453.934 C760.476,456.474 756.788,456.528 754.655,454.171 Z M754.655,385.171 L753.000,383.343 L753.000,363.622 L753.000,343.901 L754.499,341.001 L755.999,338.102 L780.356,326.051 C793.753,319.423 805.004,314.000 805.359,314.000 C805.714,314.000 814.216,309.999 824.252,305.109 C870.455,282.598 881.360,277.381 893.500,271.980 C900.650,268.799 911.475,263.790 917.556,260.848 C923.637,257.907 930.731,254.665 933.321,253.645 C935.910,252.625 941.050,249.587 944.743,246.895 C948.815,243.926 952.511,242.000 954.134,242.000 C956.376,242.000 966.806,237.142 980.500,229.718 C985.150,227.198 996.222,222.270 999.000,221.485 C1003.942,220.088 1036.511,203.763 1040.227,200.820 C1042.316,199.166 1046.157,197.246 1048.762,196.553 C1051.368,195.860 1058.104,192.977 1063.732,190.146 C1069.359,187.316 1074.592,185.000 1075.359,185.000 C1076.127,185.000 1077.345,185.711 1078.066,186.580 L1079.377,188.160 L1079.121,225.830 L1078.864,263.500 L1077.354,265.834 C1076.524,267.117 1074.192,268.840 1072.172,269.663 C1070.152,270.485 1065.800,272.441 1062.500,274.009 C1051.557,279.208 1037.617,285.132 1028.500,288.459 C1023.550,290.265 1015.450,293.417 1010.500,295.464 C993.540,302.478 977.499,308.702 974.502,309.432 C972.853,309.834 965.878,312.335 959.002,314.991 C952.126,317.646 942.000,321.470 936.500,323.488 C931.000,325.507 923.125,328.434 919.000,329.993 C914.875,331.552 907.675,334.049 903.000,335.542 C898.325,337.035 892.250,339.154 889.500,340.251 C873.289,346.717 863.101,350.635 852.000,354.671 C824.533,364.657 811.051,369.464 801.612,372.639 C796.173,374.468 788.298,377.298 784.112,378.927 C775.716,382.195 760.030,387.000 757.759,387.000 C756.962,387.000 755.565,386.177 754.655,385.171 Z M396.811,250.311 C397.459,245.765 389.950,241.945 387.105,245.373 C385.557,247.238 386.693,249.761 389.869,251.510 C393.483,253.502 396.430,252.993 396.811,250.311 Z M456.625,249.087 C461.669,246.011 464.938,233.854 461.491,230.992 C460.661,230.304 458.995,227.984 457.789,225.838 C456.583,223.692 454.650,221.699 453.494,221.409 C450.619,220.688 440.427,224.641 436.854,227.863 C430.670,233.440 432.427,242.778 440.639,247.986 C446.269,251.556 451.936,251.946 456.625,249.087 Z';
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The six jobs, in top-bar order. Shared by the top bar and by Settings, which
|
|
3
|
+
* lets each reader choose which of them the top bar shows.
|
|
4
|
+
*/
|
|
5
|
+
export const JOBS = [
|
|
6
|
+
{ id: 'statement', label: 'Statement', plain: 'One month: measured, billed, split and produced, side by side.' },
|
|
7
|
+
{ id: 'spend', label: 'Spend', plain: 'What AI cost, and where it went.' },
|
|
8
|
+
{ id: 'control', label: 'Protect', plain: 'Limits and alerts that stop spend before it happens.' },
|
|
9
|
+
{ id: 'evidence', label: 'Close', plain: 'Check what was measured against the provider bill.' },
|
|
10
|
+
{ id: 'allocation', label: 'Allocate', plain: 'Assign cost to the teams and projects that carry it.' },
|
|
11
|
+
{ id: 'value', label: 'Value', plain: 'What the spend produced.' },
|
|
12
|
+
];
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The one period the dashboard is looking at.
|
|
3
|
+
*
|
|
4
|
+
* The Metered view had its own range buttons while the four claims above it
|
|
5
|
+
* stayed on 30 days, so choosing Today showed a $40 table under an $89 headline
|
|
6
|
+
* with nothing to say the two covered different windows (H006). The period now
|
|
7
|
+
* lives here: every claim and view that can be scoped by time reads it, and
|
|
8
|
+
* every claim that cannot says its own scope instead.
|
|
9
|
+
*
|
|
10
|
+
* Remembered per browser, as a convenience only: storage can be absent or
|
|
11
|
+
* blocked, so every read and write is guarded and the default is 30 days.
|
|
12
|
+
*/
|
|
13
|
+
import { signal } from './signal.js';
|
|
14
|
+
const KEY = 'segreant.period';
|
|
15
|
+
const VALID = ['today', '7d', '30d', 'all'];
|
|
16
|
+
function stored() {
|
|
17
|
+
try {
|
|
18
|
+
const v = globalThis.localStorage?.getItem(KEY);
|
|
19
|
+
return VALID.includes(v) ? v : '30d';
|
|
20
|
+
}
|
|
21
|
+
catch {
|
|
22
|
+
return '30d';
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
export const period = signal(stored());
|
|
26
|
+
export function setPeriod(r) {
|
|
27
|
+
period.set(r);
|
|
28
|
+
try {
|
|
29
|
+
globalThis.localStorage?.setItem(KEY, r);
|
|
30
|
+
}
|
|
31
|
+
catch {
|
|
32
|
+
// A blocked store only means the choice is not remembered.
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
export { periodWords } from './periodWords.js';
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/** The period as a reader says it. Pure, so claim derivation can use it under test. */
|
|
2
|
+
export function periodWords(r) {
|
|
3
|
+
switch (r) {
|
|
4
|
+
case 'today': return 'today';
|
|
5
|
+
case '7d': return 'last 7 days';
|
|
6
|
+
case '30d': return 'last 30 days';
|
|
7
|
+
case 'all': return 'all recorded time';
|
|
8
|
+
default: return r;
|
|
9
|
+
}
|
|
10
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Allocation periods in local time, as the ISO strings the server parses. One
|
|
3
|
+
* copy, shared by the Allocate page and the run drawer, so both mean the same
|
|
4
|
+
* days by "last month" and both treat a chosen last day as included.
|
|
5
|
+
*/
|
|
6
|
+
/** A `yyyy-mm-dd` date input as the local midnight it names, in ISO form. */
|
|
7
|
+
export function localMidnightIso(day) {
|
|
8
|
+
return new Date(`${day}T00:00:00`).toISOString();
|
|
9
|
+
}
|
|
10
|
+
/** The local midnight after a `yyyy-mm-dd` day: the exclusive end of an inclusive range. */
|
|
11
|
+
export function dayAfterIso(day) {
|
|
12
|
+
const d = new Date(`${day}T00:00:00`);
|
|
13
|
+
d.setDate(d.getDate() + 1);
|
|
14
|
+
return d.toISOString();
|
|
15
|
+
}
|
|
16
|
+
/** Month boundaries in local time, as ISO strings the server parses. */
|
|
17
|
+
export function monthRange(offset) {
|
|
18
|
+
const now = new Date();
|
|
19
|
+
const start = new Date(now.getFullYear(), now.getMonth() + offset, 1);
|
|
20
|
+
const end = offset === 0 ? now : new Date(now.getFullYear(), now.getMonth() + offset + 1, 1);
|
|
21
|
+
return {
|
|
22
|
+
from: start.toISOString(),
|
|
23
|
+
to: end.toISOString(),
|
|
24
|
+
label: start.toLocaleDateString(undefined, { month: 'long', year: 'numeric' }) + (offset === 0 ? ' so far' : ''),
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
/** Why spend stayed unallocated, in plain words. */
|
|
28
|
+
export const REASON_WORDS = {
|
|
29
|
+
no_matching_rule: 'no rule claimed it',
|
|
30
|
+
no_driver_for_proportional_pool: 'a shared pool with nothing directly allocated to split it by',
|
|
31
|
+
target_cost_centre_archived: 'its cost centre was archived',
|
|
32
|
+
};
|
|
@@ -0,0 +1,264 @@
|
|
|
1
|
+
/** Local price declarations and audited history corrections, preview first. */
|
|
2
|
+
import { api } from './api.js';
|
|
3
|
+
import { h } from './dom.js';
|
|
4
|
+
import { signal } from './signal.js';
|
|
5
|
+
import { usd, count } from './fmt.js';
|
|
6
|
+
import { capability } from './registry.js';
|
|
7
|
+
import { openAction } from '../components/drawer.js';
|
|
8
|
+
const AFTER_DECLARATION = 'Recorded requests keep their estimate until you re-price them';
|
|
9
|
+
const matchWords = (kind) => kind.startsWith('family_') ? 'nearest model on the card' : 'generic rate';
|
|
10
|
+
const decimal = (raw) => /^(0|[1-9]\d{0,6})(\.\d{1,12})?$/.test(raw) && Number(raw) > 0;
|
|
11
|
+
export function pricingUnknownAction(cap) {
|
|
12
|
+
const data = signal(null);
|
|
13
|
+
const saved = signal(false);
|
|
14
|
+
const selected = signal(null);
|
|
15
|
+
/** A price the user set and chose to remove; exclusive with a model being priced. */
|
|
16
|
+
const removal = signal(null);
|
|
17
|
+
const drafts = new Map();
|
|
18
|
+
let read = null;
|
|
19
|
+
const key = (model) => JSON.stringify([model.provider, model.model]);
|
|
20
|
+
/** A declared price is edited in place: a model already on the card, or whose guessed rows were re-priced, never returns to the list above. */
|
|
21
|
+
const declaredKey = (model) => JSON.stringify(['declared', model]);
|
|
22
|
+
/** A declared price's draft starts from its stored numbers, so changing the input price keeps the cache prices (PR #146 review). */
|
|
23
|
+
function draft(model) {
|
|
24
|
+
const r = model.rate;
|
|
25
|
+
return { model: model.model, mode: signal('skip'), like: signal(model.like ?? ''), input: signal(r?.input ?? ''), output: signal(r?.output ?? ''),
|
|
26
|
+
cacheRead: signal(r?.cacheRead ?? ''), cacheWrite: signal(r?.cacheWrite ?? ''), cacheWrite1h: signal(r?.cacheWrite1h ?? '') };
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Any other model by name: one already on the card (a negotiated rate, a
|
|
30
|
+
* fine-tune billed differently) has no guessed rows and no declaration, so
|
|
31
|
+
* without this the GUI could not do what `pricing set` does (PR #146 review).
|
|
32
|
+
*/
|
|
33
|
+
const OTHER = JSON.stringify(['other']);
|
|
34
|
+
const other = draft({ model: '' });
|
|
35
|
+
drafts.set(OTHER, other);
|
|
36
|
+
const otherName = signal('');
|
|
37
|
+
const load = async () => {
|
|
38
|
+
if (!read)
|
|
39
|
+
read = api.pricingUnknown().then((payload) => {
|
|
40
|
+
for (const model of payload.models)
|
|
41
|
+
if (!drafts.has(key(model)))
|
|
42
|
+
drafts.set(key(model), draft(model));
|
|
43
|
+
for (const declared of payload.declared)
|
|
44
|
+
if (!drafts.has(declaredKey(declared.model)))
|
|
45
|
+
drafts.set(declaredKey(declared.model), draft(declared));
|
|
46
|
+
data.set(payload);
|
|
47
|
+
return payload;
|
|
48
|
+
}).catch((error) => { read = null; throw error; });
|
|
49
|
+
return read;
|
|
50
|
+
};
|
|
51
|
+
const choice = () => {
|
|
52
|
+
const chosen = selected.peek();
|
|
53
|
+
return chosen ? drafts.get(chosen) : undefined;
|
|
54
|
+
};
|
|
55
|
+
const priceBody = () => {
|
|
56
|
+
const d = choice();
|
|
57
|
+
if (!d || d.model.trim() === '')
|
|
58
|
+
return null;
|
|
59
|
+
switch (d.mode.peek()) {
|
|
60
|
+
case 'free': return { model: d.model, kind: 'free' };
|
|
61
|
+
case 'like': return d.like.peek().trim() ? { model: d.model, kind: 'like', like: d.like.peek().trim() } : null;
|
|
62
|
+
case 'rate': {
|
|
63
|
+
// Cache prices are optional; left blank, the engine's defaults apply
|
|
64
|
+
// (reads at 0.1x input, writes at 1.25x). A filled one must be valid.
|
|
65
|
+
const read = d.cacheRead.peek().trim();
|
|
66
|
+
const write = d.cacheWrite.peek().trim();
|
|
67
|
+
const write1h = d.cacheWrite1h.peek().trim();
|
|
68
|
+
if (!decimal(d.input.peek()) || !decimal(d.output.peek()))
|
|
69
|
+
return null;
|
|
70
|
+
if ([read, write, write1h].some((v) => v !== '' && !decimal(v)))
|
|
71
|
+
return null;
|
|
72
|
+
return { model: d.model, kind: 'rate', input: d.input.peek(), output: d.output.peek(),
|
|
73
|
+
...(read === '' ? {} : { cacheRead: read }), ...(write === '' ? {} : { cacheWrite: write }),
|
|
74
|
+
...(write1h === '' ? {} : { cacheWrite1h: write1h }) };
|
|
75
|
+
}
|
|
76
|
+
default: return null;
|
|
77
|
+
}
|
|
78
|
+
};
|
|
79
|
+
return {
|
|
80
|
+
capability: cap,
|
|
81
|
+
command: () => {
|
|
82
|
+
otherName();
|
|
83
|
+
const id = selected();
|
|
84
|
+
const d = id ? drafts.get(id) : undefined;
|
|
85
|
+
const removing = removal();
|
|
86
|
+
if (removing)
|
|
87
|
+
return /^[A-Za-z0-9][A-Za-z0-9._:@/+\-]*$/.test(removing) ? `segreant pricing unset ${removing}` : null;
|
|
88
|
+
if (!d || d.mode() === 'skip')
|
|
89
|
+
return 'segreant pricing unknown';
|
|
90
|
+
// Model names and prices are validated server-side; do not interpolate untrusted shell words.
|
|
91
|
+
const word = (s) => /^[A-Za-z0-9][A-Za-z0-9._:@/+\-]*$/.test(s) ? s : null;
|
|
92
|
+
const model = word(d.model);
|
|
93
|
+
if (!model)
|
|
94
|
+
return null;
|
|
95
|
+
if (d.mode() === 'free')
|
|
96
|
+
return `segreant pricing set ${model} --free`;
|
|
97
|
+
// The same trimmed target the request body sends (PR #146 review).
|
|
98
|
+
const like = d.like().trim();
|
|
99
|
+
if (d.mode() === 'like')
|
|
100
|
+
return word(like) ? `segreant pricing set ${model} --like ${like}` : null;
|
|
101
|
+
const read = d.cacheRead().trim();
|
|
102
|
+
const write = d.cacheWrite().trim();
|
|
103
|
+
const write1h = d.cacheWrite1h().trim();
|
|
104
|
+
if ([read, write, write1h].some((v) => v !== '' && !decimal(v)))
|
|
105
|
+
return null;
|
|
106
|
+
return decimal(d.input()) && decimal(d.output())
|
|
107
|
+
? `segreant pricing set ${model} --input ${d.input()} --output ${d.output()}${read ? ` --cache-read ${read}` : ''}${write ? ` --cache-write ${write}` : ''}${write1h ? ` --cache-write-1h ${write1h}` : ''}`
|
|
108
|
+
: null;
|
|
109
|
+
},
|
|
110
|
+
fields: () => h('div', null, h('p', { class: 'drawer-note', text: 'Choose one model and review its price below. Skip keeps its estimate. Prices apply to future requests; re-pricing history is a separate step.' }), () => data()?.models.map((model, i) => {
|
|
111
|
+
const id = key(model);
|
|
112
|
+
const d = drafts.get(id);
|
|
113
|
+
const change = (field, event) => {
|
|
114
|
+
field.set(event.target.value);
|
|
115
|
+
selected.set(id);
|
|
116
|
+
removal.set(null);
|
|
117
|
+
};
|
|
118
|
+
const input = (label, suffix, field, placeholder) => h('div', null, h('label', { class: 'drawer-h3', for: `price-${i}-${suffix}`, text: label }), h('input', { id: `price-${i}-${suffix}`, class: 'drawer-input', type: 'text',
|
|
119
|
+
inputmode: suffix === 'like' ? 'text' : 'decimal', maxlength: suffix === 'like' ? '200' : '20',
|
|
120
|
+
value: field.peek(), placeholder, oninput: (event) => change(field, event) }));
|
|
121
|
+
return h('section', { class: 'pricing-model' }, h('h3', { class: 'drawer-h3', text: `${model.provider} / ${model.model}` }), h('p', { class: 'drawer-note', text: `${count(model.requests)} requests · ${usd(model.costUsd)} recorded (estimated)` }), h('p', { class: 'drawer-note', text: matchWords(model.rateMatchKind) }), h('label', { class: 'drawer-h3', for: `price-${i}-mode`, text: `Price for ${model.model}` }), h('select', { id: `price-${i}-mode`, class: 'drawer-input', onchange: (event) => {
|
|
122
|
+
d.mode.set(event.target.value);
|
|
123
|
+
selected.set(d.mode.peek() === 'skip' ? null : id);
|
|
124
|
+
if (d.mode.peek() !== 'skip')
|
|
125
|
+
removal.set(null);
|
|
126
|
+
} }, h('option', { value: 'skip', selected: d.mode.peek() === 'skip', text: 'Skip' }), h('option', { value: 'like', selected: d.mode.peek() === 'like', text: 'Price like…' }), h('option', { value: 'rate', selected: d.mode.peek() === 'rate', text: 'Set a price' }), h('option', { value: 'free', selected: d.mode.peek() === 'free', text: 'Free (local model)' })), () => d.mode() === 'like'
|
|
127
|
+
? input(`Exact card model for ${model.model}`, 'like', d.like, 'claude-haiku-4-5')
|
|
128
|
+
: d.mode() === 'rate' ? h('div', null, input(`Input for ${model.model} (USD per 1M tokens)`, 'input', d.input, '1.00'), input(`Output for ${model.model} (USD per 1M tokens)`, 'output', d.output, '3.00'), input(`Cache read for ${model.model}, optional (USD per 1M tokens)`, 'read', d.cacheRead, '0.10 x input if blank'), input(`Cache write for ${model.model}, optional (USD per 1M tokens)`, 'write', d.cacheWrite, '1.25 x input if blank'), input(`1-hour cache write for ${model.model}, optional (USD per 1M tokens)`, 'write1h', d.cacheWrite1h, 'cache write, or 2 x input, if blank')) : null);
|
|
129
|
+
}) ?? null, () => (data()?.declared.length ?? 0) > 0 ? h('section', { class: 'pricing-declared' }, h('h3', { class: 'drawer-h3', text: 'Your prices' }), h('p', { class: 'drawer-note', text: 'Change or remove a price you set. Apply saves the one you changed last.' }), ...data().declared.map((p, i) => {
|
|
130
|
+
const id = declaredKey(p.model);
|
|
131
|
+
const d = drafts.get(id) ?? draft(p);
|
|
132
|
+
if (!drafts.has(id))
|
|
133
|
+
drafts.set(id, d);
|
|
134
|
+
const field = (label, suffix, sig, placeholder) => h('div', null, h('label', { class: 'drawer-h3', for: `declared-${i}-${suffix}`, text: label }), h('input', { id: `declared-${i}-${suffix}`, class: 'drawer-input', type: 'text',
|
|
135
|
+
inputmode: suffix === 'like' ? 'text' : 'decimal', maxlength: suffix === 'like' ? '200' : '20',
|
|
136
|
+
value: sig.peek(), placeholder, oninput: (event) => { sig.set(event.target.value); selected.set(id); removal.set(null); } }));
|
|
137
|
+
return h('div', { class: 'pricing-model' }, h('label', { class: 'drawer-h3', for: `declared-${i}-mode`, text: `${p.model}: ${p.summary}` }), h('select', { id: `declared-${i}-mode`, class: 'drawer-input', onchange: (event) => {
|
|
138
|
+
const mode = event.target.value;
|
|
139
|
+
if (mode === 'remove') {
|
|
140
|
+
d.mode.set('skip');
|
|
141
|
+
removal.set(p.model);
|
|
142
|
+
selected.set(null);
|
|
143
|
+
return;
|
|
144
|
+
}
|
|
145
|
+
if (removal.peek() === p.model)
|
|
146
|
+
removal.set(null);
|
|
147
|
+
d.mode.set(mode === 'keep' ? 'skip' : mode);
|
|
148
|
+
selected.set(mode === 'keep' ? (selected.peek() === id ? null : selected.peek()) : id);
|
|
149
|
+
} }, h('option', { value: 'keep', selected: removal.peek() !== p.model && d.mode.peek() === 'skip', text: 'Keep' }), h('option', { value: 'like', selected: d.mode.peek() === 'like', text: 'Change: price like…' }), h('option', { value: 'rate', selected: d.mode.peek() === 'rate', text: 'Change: set a price' }), h('option', { value: 'free', selected: d.mode.peek() === 'free', text: 'Change: free (local model)' }), h('option', { value: 'remove', selected: removal.peek() === p.model, text: 'Remove price' })), () => d.mode() === 'like'
|
|
150
|
+
? field(`Exact card model for ${p.model}`, 'like', d.like, 'claude-haiku-4-5')
|
|
151
|
+
: d.mode() === 'rate' ? h('div', null, field(`New input for ${p.model} (USD per 1M tokens)`, 'input', d.input, '1.00'), field(`New output for ${p.model} (USD per 1M tokens)`, 'output', d.output, '3.00'), field(`New cache read for ${p.model}, optional (USD per 1M tokens)`, 'read', d.cacheRead, '0.10 x input if blank'), field(`New cache write for ${p.model}, optional (USD per 1M tokens)`, 'write', d.cacheWrite, '1.25 x input if blank'), field(`New 1-hour cache write for ${p.model}, optional (USD per 1M tokens)`, 'write1h', d.cacheWrite1h, 'cache write, or 2 x input, if blank')) : null);
|
|
152
|
+
})) : null, h('section', { class: 'pricing-other' }, h('h3', { class: 'drawer-h3', text: 'Another model' }), h('p', { class: 'drawer-note', text: 'Set your own price for any model, including one already on the price list (a negotiated rate, a fine-tune). Your price is used instead of the list price.' }), h('label', { class: 'drawer-h3', for: 'other-name', text: 'Model name' }), h('input', { id: 'other-name', class: 'drawer-input', type: 'text', maxlength: '200', placeholder: 'gpt-4o',
|
|
153
|
+
value: other.model, oninput: (event) => {
|
|
154
|
+
other.model = event.target.value.trim();
|
|
155
|
+
otherName.set(other.model);
|
|
156
|
+
if (other.mode.peek() !== 'skip') {
|
|
157
|
+
selected.set(OTHER);
|
|
158
|
+
removal.set(null);
|
|
159
|
+
}
|
|
160
|
+
} }), h('label', { class: 'drawer-h3', for: 'other-mode', text: 'Price for another model' }), h('select', { id: 'other-mode', class: 'drawer-input', onchange: (event) => {
|
|
161
|
+
other.mode.set(event.target.value);
|
|
162
|
+
selected.set(other.mode.peek() === 'skip' ? (selected.peek() === OTHER ? null : selected.peek()) : OTHER);
|
|
163
|
+
if (other.mode.peek() !== 'skip')
|
|
164
|
+
removal.set(null);
|
|
165
|
+
} }, h('option', { value: 'skip', selected: other.mode.peek() === 'skip', text: 'Skip' }), h('option', { value: 'like', text: 'Price like…' }), h('option', { value: 'rate', text: 'Set a price' }), h('option', { value: 'free', text: 'Free (local model)' })), () => {
|
|
166
|
+
const field = (label, suffix, sig, placeholder) => h('div', null, h('label', { class: 'drawer-h3', for: `other-${suffix}`, text: label }), h('input', { id: `other-${suffix}`, class: 'drawer-input', type: 'text',
|
|
167
|
+
inputmode: suffix === 'like' ? 'text' : 'decimal', maxlength: suffix === 'like' ? '200' : '20',
|
|
168
|
+
value: sig.peek(), placeholder, oninput: (event) => { sig.set(event.target.value); selected.set(OTHER); removal.set(null); } }));
|
|
169
|
+
return other.mode() === 'like'
|
|
170
|
+
? field('Exact card model for another model', 'like', other.like, 'claude-haiku-4-5')
|
|
171
|
+
: other.mode() === 'rate' ? h('div', null, field('Input for another model (USD per 1M tokens)', 'input', other.input, '1.00'), field('Output for another model (USD per 1M tokens)', 'output', other.output, '3.00'), field('Cache read for another model, optional (USD per 1M tokens)', 'read', other.cacheRead, '0.10 x input if blank'), field('Cache write for another model, optional (USD per 1M tokens)', 'write', other.cacheWrite, '1.25 x input if blank'), field('1-hour cache write for another model, optional (USD per 1M tokens)', 'write1h', other.cacheWrite1h, 'cache write, or 2 x input, if blank')) : null;
|
|
172
|
+
}), h('div', { role: 'status', 'aria-live': 'polite' }, () => saved() ? h('div', null, h('p', { class: 'drawer-note', text: AFTER_DECLARATION }), h('button', { class: 'btn-ghost', text: 'Re-price recorded requests', onclick: () => openAction(repriceAction(capability('reprice'))) })) : null)),
|
|
173
|
+
preview: async () => {
|
|
174
|
+
const payload = await load();
|
|
175
|
+
const removing = removal.peek();
|
|
176
|
+
if (removing) {
|
|
177
|
+
return {
|
|
178
|
+
applicable: !payload.demo,
|
|
179
|
+
summary: `Remove your price for ${removing}. New requests go back to the rate card, or to an estimate if the card does not know the model; recorded requests are unchanged.`,
|
|
180
|
+
blockedReason: 'Sample data: start without --demo to change a price.',
|
|
181
|
+
notes: [payload.boundary],
|
|
182
|
+
};
|
|
183
|
+
}
|
|
184
|
+
const body = priceBody();
|
|
185
|
+
const d = choice();
|
|
186
|
+
const description = !body ? '' : body.kind === 'free' ? 'Free (local model): $0 your price per request.'
|
|
187
|
+
: body.kind === 'like' ? `Your price follows ${body.like}, an exact model on the current card.`
|
|
188
|
+
: `Your price: input $${body.input}, output $${body.output}${body.cacheRead ? `, cache read $${body.cacheRead}` : ''}${body.cacheWrite ? `, cache write $${body.cacheWrite}` : ''}${body.cacheWrite1h ? `, 1-hour cache write $${body.cacheWrite1h}` : ''} USD per 1M tokens.`;
|
|
189
|
+
return {
|
|
190
|
+
applicable: !payload.demo && body !== null,
|
|
191
|
+
summary: body ? `Save your price for ${d.model}. ${description}`
|
|
192
|
+
: payload.models.length ? `${count(payload.models.length)} models still use guessed prices. Choose a price for one model to review it here.`
|
|
193
|
+
: 'No unpriced models remain. Recorded requests may still need re-pricing.',
|
|
194
|
+
blockedReason: payload.demo ? 'Sample data: start without --demo to save a price.' : 'Choose a price for one model. A rate needs positive input and output prices; Price like needs an exact card model name; another model needs its name.',
|
|
195
|
+
notes: [payload.boundary, 'The most recently changed model is the one Apply saves. Other choices stay here until you apply them.'],
|
|
196
|
+
};
|
|
197
|
+
},
|
|
198
|
+
commit: async () => {
|
|
199
|
+
const removing = removal.peek();
|
|
200
|
+
if (removing) {
|
|
201
|
+
const removed = await api.write.pricingUnset(removing);
|
|
202
|
+
removal.set(null);
|
|
203
|
+
read = null;
|
|
204
|
+
try {
|
|
205
|
+
await load();
|
|
206
|
+
}
|
|
207
|
+
catch { /* the message below still holds */ }
|
|
208
|
+
return { ok: removed.ok, message: removed.removed ? `Removed your price for ${removing}.` : `${removing} had no price of yours.` };
|
|
209
|
+
}
|
|
210
|
+
const body = priceBody();
|
|
211
|
+
if (!body)
|
|
212
|
+
return { ok: false, message: 'Choose a price for one model first.' };
|
|
213
|
+
const result = await api.write.pricingDeclare(body);
|
|
214
|
+
choice()?.mode.set('skip');
|
|
215
|
+
saved.set(true);
|
|
216
|
+
selected.set(null);
|
|
217
|
+
read = null;
|
|
218
|
+
try {
|
|
219
|
+
await load();
|
|
220
|
+
}
|
|
221
|
+
catch {
|
|
222
|
+
return { ok: true, message: `Saved your price for ${result.model}. Reopen this drawer to refresh the list.` };
|
|
223
|
+
}
|
|
224
|
+
return { ok: result.ok, message: `Saved your price for ${result.model}.` };
|
|
225
|
+
},
|
|
226
|
+
};
|
|
227
|
+
}
|
|
228
|
+
const afterWords = (result) => [...new Set(result.byModel.map((m) => m.afterBasis))].join(' + ') || 'list price';
|
|
229
|
+
const changedRows = (result) => [
|
|
230
|
+
...result.byModel.map((m) => ({ label: `${m.provider} / ${m.model}`, value: `${count(m.rows)} requests · ${usd(m.beforeUsd)} recorded (estimated) → ${usd(m.afterUsd)} ${m.afterBasis}` })),
|
|
231
|
+
{ label: 'Total', value: `${usd(result.totalBeforeUsd)} recorded (estimated) → ${usd(result.totalAfterUsd)} ${afterWords(result)}` },
|
|
232
|
+
];
|
|
233
|
+
export function repriceAction(cap) {
|
|
234
|
+
const applied = signal(null);
|
|
235
|
+
return {
|
|
236
|
+
capability: cap,
|
|
237
|
+
command: () => 'segreant reprice --apply',
|
|
238
|
+
commitLabel: 'Apply re-pricing',
|
|
239
|
+
fields: () => h('div', null, () => {
|
|
240
|
+
const result = applied();
|
|
241
|
+
return result ? h('section', null, h('h3', { class: 'drawer-h3', text: 'Changes applied' }), h('dl', { class: 'drawer-rows' }, ...changedRows(result).flatMap((r) => [h('dt', { text: r.label }), h('dd', { text: r.value })]))) : null;
|
|
242
|
+
}),
|
|
243
|
+
preview: async () => {
|
|
244
|
+
const result = await api.write.reprice(false);
|
|
245
|
+
return {
|
|
246
|
+
applicable: result.repriceable > 0,
|
|
247
|
+
summary: `${count(result.repriceable)} requests can be re-priced. ${count(result.stillEstimated)} still use guesses and will keep their recorded amount.`,
|
|
248
|
+
blockedReason: 'No recorded estimates can be replaced with a current list price or your price.',
|
|
249
|
+
rows: changedRows(result),
|
|
250
|
+
notes: ['Only recorded estimates change. Each previous amount is kept in the request’s price history.',
|
|
251
|
+
'Apply uses the card, declarations and requests current at that moment. It shows the amounts it actually wrote.',
|
|
252
|
+
'Value snapshot costs are updated on their recorded basis. Older snapshots whose basis cannot be recovered are marked stale. Outcomes stay the same.',
|
|
253
|
+
...(result.cacheDurationUnknown > 0 ? [`${count(result.cacheDurationUnknown)} of these requests were recorded before Segreant kept the cache-write duration, so their cache writes are priced at the 5-minute rate.`] : [])],
|
|
254
|
+
};
|
|
255
|
+
},
|
|
256
|
+
commit: async () => {
|
|
257
|
+
const result = await api.write.reprice(true);
|
|
258
|
+
applied.set(result);
|
|
259
|
+
window.dispatchEvent(new CustomEvent('segreant:ledger-changed', { detail: { claim: 'pricing' } }));
|
|
260
|
+
const sync = result.realizationSync;
|
|
261
|
+
return { ok: true, message: `Re-priced ${count(result.repriceable)} requests: ${usd(result.totalBeforeUsd)} recorded (estimated) → ${usd(result.totalAfterUsd)} ${afterWords(result)}.${sync?.unresolvable ? ` ${count(sync.unresolvable)} older value snapshots keep their previous cost and are marked stale.` : ''}${result.alreadyRepriced > 0 ? ` ${count(result.alreadyRepriced)} were already re-priced by another reprice and are not counted here.` : ''}` };
|
|
262
|
+
},
|
|
263
|
+
};
|
|
264
|
+
}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The privacy statement, as data. The dashboard renders it at #/privacy and
|
|
3
|
+
* docs/PRIVACY.md carries the same words; test/privacy-statement.test.ts fails
|
|
4
|
+
* if the two drift. Every sentence restates docs/DATA-BOUNDARIES.md, which
|
|
5
|
+
* remains the full technical account. Text in backticks is a command or setting.
|
|
6
|
+
*/
|
|
7
|
+
export const PRIVACY_TITLE = 'Privacy';
|
|
8
|
+
export const PRIVACY_LEAD = 'Segreant runs on your computer. There is no Segreant account, no analytics and no telemetry. Your ledger, prompts, code and usage are never sent to us, because nothing in Segreant sends them anywhere you did not choose.';
|
|
9
|
+
export const PRIVACY_SECTIONS = [
|
|
10
|
+
{
|
|
11
|
+
heading: 'What Segreant keeps on this machine',
|
|
12
|
+
paragraphs: [
|
|
13
|
+
'The ledger records each AI request: when it happened, the provider and model, token counts, the estimated cost, and the project and session it belongs to. It also keeps the outcome signals you set up, so it can show what the spend produced.',
|
|
14
|
+
'Some of what it keeps is your own material rather than a measurement, and it stays on this machine with everything else:',
|
|
15
|
+
],
|
|
16
|
+
items: [
|
|
17
|
+
'the working folder a request came from, which is how spend is matched to a project;',
|
|
18
|
+
'the user label a tool reports;',
|
|
19
|
+
'the first line of each commit message it matches to spend;',
|
|
20
|
+
'proposed code lines, used to check whether AI suggestions were kept. Turning on `metadataOnly` stops new ones being kept; Clear proposals in Settings deletes the ones already kept.',
|
|
21
|
+
],
|
|
22
|
+
},
|
|
23
|
+
{
|
|
24
|
+
heading: 'Your provider keys',
|
|
25
|
+
paragraphs: [
|
|
26
|
+
'When a tool sends a request through Segreant, the request and its API key go to the provider you configured, exactly as they would without Segreant. Segreant does not store your provider keys.',
|
|
27
|
+
],
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
heading: 'What leaves this machine',
|
|
31
|
+
paragraphs: [
|
|
32
|
+
'Your AI requests go to the provider you configured.',
|
|
33
|
+
"Segreant keeps its own price list current by itself. The first time it prices a request for a model its rate card does not know, a running Segreant downloads the public price list (LiteLLM's `model_prices_and_context_window.json` on GitHub), at most once an hour. It is a plain download of a public file: it sends nothing about you, your requests or your usage, and nothing is stored anywhere but on this machine. GitHub, which hosts the file, sees the download as it would any other. `segreant pricing --auto off` stops it, and the start banner says when it is on.",
|
|
34
|
+
'Everything else is off until you turn it on, and `segreant egress status` lists what is allowed:',
|
|
35
|
+
],
|
|
36
|
+
items: [
|
|
37
|
+
'refreshing market data, which sends nothing about you;',
|
|
38
|
+
'downloading a baseline from an address you give;',
|
|
39
|
+
'alert summaries to a webhook you set, never prompts, code or keys;',
|
|
40
|
+
'pulling your costs from OpenAI with an admin key, read from your environment for that one request and never stored;',
|
|
41
|
+
'a numeric spending summary sent to a team server you run;',
|
|
42
|
+
'a session excerpt sent to a hosted judge, only if you configure one.',
|
|
43
|
+
],
|
|
44
|
+
},
|
|
45
|
+
{
|
|
46
|
+
heading: 'Keeping and deleting',
|
|
47
|
+
paragraphs: [
|
|
48
|
+
'Captured suggestions older than 30 days are deleted each time Segreant starts. Request records are not deleted on their own: after 180 days they are due for deletion, and `segreant prune --apply` deletes them along with any expired suggestions. You can change both periods in Settings.',
|
|
49
|
+
'To remove everything, delete the folder Segreant keeps its data in. That is `.segreant` in your home directory unless you set `SEGREANT_HOME` or `SEGREANT_DB`, in which case delete what those point to as well; `segreant doctor` prints the database path in use.',
|
|
50
|
+
'To take your records with you, `segreant pack export --out <file>` writes them to a file on this machine.',
|
|
51
|
+
],
|
|
52
|
+
},
|
|
53
|
+
{
|
|
54
|
+
heading: 'Your providers have their own terms',
|
|
55
|
+
paragraphs: [
|
|
56
|
+
'Segreant decides what it sends. How your AI provider, or any webhook or server you add, stores what it receives is governed by their terms, so read those before sending sensitive work.',
|
|
57
|
+
],
|
|
58
|
+
},
|
|
59
|
+
{
|
|
60
|
+
heading: 'Questions',
|
|
61
|
+
paragraphs: [
|
|
62
|
+
'Ask on the project’s GitHub issues. Report a security problem privately, as `SECURITY.md` describes. The full technical account of every path is in `docs/DATA-BOUNDARIES.md`.',
|
|
63
|
+
],
|
|
64
|
+
},
|
|
65
|
+
];
|
|
66
|
+
/** The statement as Markdown: exactly what docs/PRIVACY.md must contain. */
|
|
67
|
+
export function privacyMarkdown() {
|
|
68
|
+
const out = [`# ${PRIVACY_TITLE}`, '', PRIVACY_LEAD, ''];
|
|
69
|
+
for (const section of PRIVACY_SECTIONS) {
|
|
70
|
+
out.push(`## ${section.heading}`, '');
|
|
71
|
+
for (const p of section.paragraphs)
|
|
72
|
+
out.push(p, '');
|
|
73
|
+
if (section.items)
|
|
74
|
+
out.push(...section.items.map((item) => `- ${item}`), '');
|
|
75
|
+
}
|
|
76
|
+
out.push('This file is generated from `src/dashboard/web/app/core/privacyText.ts`, which the dashboard also renders at `#/privacy`. Change the words there.', '');
|
|
77
|
+
return out.join('\n');
|
|
78
|
+
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* How a reconciliation is said: what its residual bounds, and which days it
|
|
3
|
+
* covers. One copy, shared by the Close page and the reconcile drawer, so a
|
|
4
|
+
* comparison reads the same wherever it is previewed or recorded.
|
|
5
|
+
*/
|
|
6
|
+
import { date } from './fmt.js';
|
|
7
|
+
/**
|
|
8
|
+
* A `Record` rather than an if-chain, keyed by the wire's own union.
|
|
9
|
+
*
|
|
10
|
+
* That is the gate: adding a state to `offPathBound` in `shared-types.ts`
|
|
11
|
+
* without a rendering here fails the BROWSER typecheck. With an if-chain the
|
|
12
|
+
* new state fell through to the "older than the check" sentence below, which
|
|
13
|
+
* would have said a fresh run predates a field it carries — an unrecognised
|
|
14
|
+
* value rendered as an absent one, which is the defect class this screen keeps
|
|
15
|
+
* finding.
|
|
16
|
+
*/
|
|
17
|
+
export const RESIDUAL_BOUND_WORDS = {
|
|
18
|
+
none_local_estimate_exceeds_provider: {
|
|
19
|
+
precise: 'residual bounds nothing — the local estimate exceeds the provider total, so no upper bound on off-path spend survives',
|
|
20
|
+
plain: 'This remainder cannot tell you how much went outside Segreant: our own estimate already came out higher than the provider’s bill.',
|
|
21
|
+
},
|
|
22
|
+
upper_bound_conditional: {
|
|
23
|
+
precise: 'residual is an upper bound on off-path spend while the local estimate does not exceed true on-path billed cost',
|
|
24
|
+
plain: 'At most this much could have gone outside Segreant — assuming our own pricing did not overshoot what you were really charged.',
|
|
25
|
+
},
|
|
26
|
+
unknown_local_total_truncated_by_retention: {
|
|
27
|
+
precise: 'residual bounds nothing — retention deleted request rows from inside this period, so the local total is a known undercount by an unknown amount',
|
|
28
|
+
plain: 'This remainder cannot tell you how much went outside Segreant: some of your own metered requests in this period were deleted by your retention setting, so part of the difference is traffic we did see.',
|
|
29
|
+
},
|
|
30
|
+
};
|
|
31
|
+
/** Whether the run's own bound state is bad news rather than a caveat. */
|
|
32
|
+
export function residualBoundIsError(bound) {
|
|
33
|
+
return bound === 'none_local_estimate_exceeds_provider'
|
|
34
|
+
|| bound === 'unknown_local_total_truncated_by_retention';
|
|
35
|
+
}
|
|
36
|
+
export function residualBoundWords(bound, precise) {
|
|
37
|
+
const words = bound === undefined ? undefined : RESIDUAL_BOUND_WORDS[bound];
|
|
38
|
+
if (words === undefined) {
|
|
39
|
+
return precise
|
|
40
|
+
? 'this run predates the recorded bound condition, so what the residual bounds is not established'
|
|
41
|
+
: 'This run is older than the check that explains the remainder, so there is nothing to show for it.';
|
|
42
|
+
}
|
|
43
|
+
return precise ? words.precise : words.plain;
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* The rounding a comparison applied to our side, or null when it changed
|
|
47
|
+
* nothing. Runs recorded before the rule rounded nothing: they refused instead.
|
|
48
|
+
*/
|
|
49
|
+
export function quantizationWords(q, precise) {
|
|
50
|
+
if (!q || q.daysRounded === 0)
|
|
51
|
+
return null;
|
|
52
|
+
const days = `${q.daysRounded} day total${q.daysRounded === 1 ? '' : 's'}`;
|
|
53
|
+
return precise
|
|
54
|
+
? `local exact amounts summed per UTC day; ${days} rounded half to even to the microdollar (each within $0.0000005)`
|
|
55
|
+
: `Our measurement had amounts smaller than a millionth of a dollar. Each day was added up exactly, then ${days} rounded to the nearest millionth.`;
|
|
56
|
+
}
|
|
57
|
+
/** The days a provider period covers; it ends at an exclusive UTC midnight. */
|
|
58
|
+
export function dayRange(startMs, endMs) {
|
|
59
|
+
if (startMs === undefined || endMs === undefined)
|
|
60
|
+
return '—';
|
|
61
|
+
// Provider periods end at an exclusive midnight; show the last day included.
|
|
62
|
+
return `${date(startMs, 'UTC')} – ${date(endMs - 1, 'UTC')} UTC`;
|
|
63
|
+
}
|