tokentracker-cli 0.96.3 → 0.97.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/dashboard/dist/assets/{AchievementBadge-BX4iBBYM.js → AchievementBadge-BdvqY4Qb.js} +1 -1
- package/dashboard/dist/assets/{AchievementsPage-DRYRuCbh.js → AchievementsPage-CrqvRxnb.js} +2 -2
- package/dashboard/dist/assets/{AchievementsSection-BM_JWWhJ.js → AchievementsSection-wyLAM9N7.js} +1 -1
- package/dashboard/dist/assets/{BadgeDetailModal-DxoxXst2.js → BadgeDetailModal-CquST3dj.js} +1 -1
- package/dashboard/dist/assets/{BotAnimated-BVpFxG8u.js → BotAnimated-EXhdUHSg.js} +1 -1
- package/dashboard/dist/assets/{Card-BJJs701t.js → Card-Il2WWgOG.js} +1 -1
- package/dashboard/dist/assets/{ClawdAnimated-46wPZ_Tc.js → ClawdAnimated-DioF5wa-.js} +2 -2
- package/dashboard/dist/assets/{CommandPalette-B_zkc7IU.js → CommandPalette-CaDx06tM.js} +1 -1
- package/dashboard/dist/assets/{CommunityStatsModal-M2k5rIf1.js → CommunityStatsModal-BbYWzM07.js} +1 -1
- package/dashboard/dist/assets/{ConfirmModal-DBvOTcAQ.js → ConfirmModal-BHWQLUCM.js} +1 -1
- package/dashboard/dist/assets/{Controls-BCkjPMSH.js → Controls--LFhExZX.js} +1 -1
- package/dashboard/dist/assets/{DashboardPage-3bHhou09.js → DashboardPage-DuqJOpHL.js} +1 -1
- package/dashboard/dist/assets/{DevicePage-DFBUce8C.js → DevicePage-RKTirqz0.js} +1 -1
- package/dashboard/dist/assets/{DialogClose-kvDd8h3V.js → DialogClose-DPFsTKmJ.js} +1 -1
- package/dashboard/dist/assets/DialogDescription-DgaV8Gtk.js +1 -0
- package/dashboard/dist/assets/{DialogTitle-DL5nvD31.js → DialogTitle-DolLpmFL.js} +1 -1
- package/dashboard/dist/assets/{FadeIn-B-Bg6SEy.js → FadeIn-92GGNCPe.js} +1 -1
- package/dashboard/dist/assets/{HeaderGithubStar-BExz0hh4.js → HeaderGithubStar-GfXudotT.js} +1 -1
- package/dashboard/dist/assets/{HoverTooltip-Dj5zemYT.js → HoverTooltip-n7gbPEsq.js} +1 -1
- package/dashboard/dist/assets/{IpCheckPage-DN7FuJ_v.js → IpCheckPage-BaQOTbXi.js} +1 -1
- package/dashboard/dist/assets/{LandingPage-9dKgSBRy.js → LandingPage-D3JcvxW_.js} +2 -2
- package/dashboard/dist/assets/{LeaderboardAvatar-00V95XIg.js → LeaderboardAvatar-tVaWJXZ3.js} +1 -1
- package/dashboard/dist/assets/{LeaderboardPage-DkKfUt0Z.js → LeaderboardPage-NqHcStnk.js} +2 -2
- package/dashboard/dist/assets/{LeaderboardProfileModal-C9sIHBgN.js → LeaderboardProfileModal-DmvcCVS7.js} +3 -3
- package/dashboard/dist/assets/{LeaderboardProfilePage-BAiLMsEb.js → LeaderboardProfilePage-VSv3IlzS.js} +1 -1
- package/dashboard/dist/assets/{LimitsPage-BuenTaHa.js → LimitsPage-CtxgT20z.js} +1 -1
- package/dashboard/dist/assets/{LocalOnlyNotice-CKhY27Lr.js → LocalOnlyNotice-O9o586G8.js} +1 -1
- package/dashboard/dist/assets/{LoginCard-BbvoBx5N.js → LoginCard-JCejigVu.js} +1 -1
- package/dashboard/dist/assets/{LoginModal-P7zIkBhO.js → LoginModal-CJ3g7tBc.js} +1 -1
- package/dashboard/dist/assets/{LoginPage-Bo0bR26x.js → LoginPage-DPWzPywx.js} +1 -1
- package/dashboard/dist/assets/{PetPage-C6i9bSSl.js → PetPage-MSJ3jseK.js} +1 -1
- package/dashboard/dist/assets/{PopoverPopup-h0gf36vg.js → PopoverPopup-nL3YOLPO.js} +1 -1
- package/dashboard/dist/assets/{ProviderIcon-Dm8fwPdj.js → ProviderIcon-C5RX_xPk.js} +1 -1
- package/dashboard/dist/assets/{ResetPasswordPage-DvSJ8kg0.js → ResetPasswordPage-DW_NCwA_.js} +1 -1
- package/dashboard/dist/assets/{SegmentedControl-CP7oiGZE.js → SegmentedControl-CS6wQtck.js} +1 -1
- package/dashboard/dist/assets/{Select-iwP92x9C.js → Select-YqlqvbT5.js} +1 -1
- package/dashboard/dist/assets/{SelectItemText-DzljA15_.js → SelectItemText-BahqsiIc.js} +1 -1
- package/dashboard/dist/assets/{ServiceStatusPage-uD4D0Tid.js → ServiceStatusPage-DyhutLUy.js} +1 -1
- package/dashboard/dist/assets/{SessionsPage-Cmb1ssNP.js → SessionsPage-wbvi4EHi.js} +1 -1
- package/dashboard/dist/assets/{SettingsPage-D8ryg1Pv.js → SettingsPage-HGSwpV1O.js} +2 -2
- package/dashboard/dist/assets/SkillsPage-BbVT9oyh.js +1 -0
- package/dashboard/dist/assets/{TokenGalaxy-f2djrlUv.js → TokenGalaxy-Cej6MLu6.js} +1 -1
- package/dashboard/dist/assets/{ToolbarRootContext-CDxCPYLv.js → ToolbarRootContext-D9pPY73U.js} +1 -1
- package/dashboard/dist/assets/{TrendMonitor-C419X22G.js → TrendMonitor-BtBcCjx-.js} +1 -1
- package/dashboard/dist/assets/{WidgetsPage-AYq__pOO.js → WidgetsPage-BSNl2WIL.js} +1 -1
- package/dashboard/dist/assets/{WrappedPage-Bsm9k5XI.js → WrappedPage-d5oiNV4T.js} +1 -1
- package/dashboard/dist/assets/{__vite-browser-external-BDkeh90N.js → __vite-browser-external-CjrP5KfC.js} +12 -12
- package/dashboard/dist/assets/{agent-logos-B6Vg-4_1.js → agent-logos-CCVzO3uU.js} +1 -1
- package/dashboard/dist/assets/{arrow-up-right-DIQXwaOD.js → arrow-up-right-AKItXMyh.js} +1 -1
- package/dashboard/dist/assets/{check-CgD_bV39.js → check-BQw5n0V0.js} +1 -1
- package/dashboard/dist/assets/{composite-BIgEqWZD.js → composite-CWz1s64R.js} +1 -1
- package/dashboard/dist/assets/{copy-DlM1BHdp.js → copy-CF8XNmv3.js} +1 -1
- package/dashboard/dist/assets/{download-CQFG-VIY.js → download-BZvLU_hD.js} +1 -1
- package/dashboard/dist/assets/{external-link-BQyDqGxz.js → external-link-bZrcVkeE.js} +1 -1
- package/dashboard/dist/assets/{flame-BamzyXzF.js → flame-CMHolW1z.js} +1 -1
- package/dashboard/dist/assets/{format-tokens-C4G-_xYt.js → format-tokens-Bs2YotMr.js} +1 -1
- package/dashboard/dist/assets/{icons-Ntscvl1a.js → icons-OKyij2y_.js} +1 -1
- package/dashboard/dist/assets/{index-DGxgsNoQ.js → index-C8nY1OaT.js} +1 -1
- package/dashboard/dist/assets/{index-FRIt5mlv.js → index-CN7xY7j_.js} +1 -1
- package/dashboard/dist/assets/{index-CUrcPqgp.js → index-DOwPCQx2.js} +1 -1
- package/dashboard/dist/assets/{info-CXytWcZi.js → info-CU7bMBhU.js} +1 -1
- package/dashboard/dist/assets/{link-2-Dexoo_jO.js → link-2-wwoUOujz.js} +1 -1
- package/dashboard/dist/assets/{loader-circle-Cn0JHc8t.js → loader-circle-BK3Ltc-j.js} +1 -1
- package/dashboard/dist/assets/{maximize-2-DLh7gYuP.js → maximize-2-DIPnLqR8.js} +1 -1
- package/dashboard/dist/assets/{modifiers.esm-DOh_xtyt.js → modifiers.esm-D_aVVjx5.js} +1 -1
- package/dashboard/dist/assets/{monitor-smartphone-CpqS9Ior.js → monitor-smartphone-4NOM9BHb.js} +1 -1
- package/dashboard/dist/assets/{plus-bfQV7nyv.js → plus-JqSTJ28Y.js} +1 -1
- package/dashboard/dist/assets/{provider-display-Bf9pW06U.js → provider-display-CEFlg9ho.js} +1 -1
- package/dashboard/dist/assets/{react-BmFZKA_b.js → react-jS0-5kwE.js} +1 -1
- package/dashboard/dist/assets/{search-l3jJ2fzC.js → search-CbBetPN7.js} +1 -1
- package/dashboard/dist/assets/{skills-api-CWmLnaxn.js → skills-api-PwUMTdmV.js} +1 -1
- package/dashboard/dist/assets/{sortable.esm-DLgyXLHM.js → sortable.esm-BDdw7tkz.js} +1 -1
- package/dashboard/dist/assets/{store-JM-a8ElZ.js → store-CFdu9yv_.js} +1 -1
- package/dashboard/dist/assets/{terminal-6aY2Sn8P.js → terminal-Dh7Sh6IG.js} +1 -1
- package/dashboard/dist/assets/{trash-D5oon5pT.js → trash-B6_OaEVL.js} +1 -1
- package/dashboard/dist/assets/{use-community-stats-ZWUMFwzc.js → use-community-stats-D55oU9bu.js} +1 -1
- package/dashboard/dist/assets/{use-latest-request-guard-BDaXr4gK.js → use-latest-request-guard-BO89yQ3n.js} +1 -1
- package/dashboard/dist/assets/{use-limits-display-prefs-yXXToNIc.js → use-limits-display-prefs-C358N7B0.js} +1 -1
- package/dashboard/dist/assets/{use-ordered-list-C6RG8N6c.js → use-ordered-list-B9ZYDS1K.js} +1 -1
- package/dashboard/dist/assets/use-reduced-motion-DWHVI51t.js +1 -0
- package/dashboard/dist/assets/{use-session-efficiency-pref-DpgZO_Lx.js → use-session-efficiency-pref-CwGRDyVq.js} +1 -1
- package/dashboard/dist/assets/{use-transform-D42P7jkd.js → use-transform-9S4Ruq_s.js} +1 -1
- package/dashboard/dist/assets/{use-usage-limits-CV6puFwq.js → use-usage-limits-CpnM5V06.js} +1 -1
- package/dashboard/dist/assets/{useCurrency-GWBmOrCR.js → useCurrency-BKJGW_4l.js} +1 -1
- package/dashboard/dist/assets/{useTokenFormat-Beybkb0v.js → useTokenFormat-CTB5svOB.js} +1 -1
- package/dashboard/dist/assets/{zap-CEq2616W.js → zap-DGdBuT6m.js} +1 -1
- package/dashboard/dist/index.html +1 -1
- package/dashboard/dist/ip-check.html +1 -1
- package/dashboard/dist/leaderboard.html +1 -1
- package/dashboard/dist/share.html +1 -1
- package/package.json +1 -1
- package/src/lib/codex-rollout-parser.js +48 -1
- package/src/lib/codex-service-tier.js +49 -0
- package/src/lib/local-api.js +5 -0
- package/src/lib/pricing/curated-overrides.json +2 -1
- package/src/lib/pricing/index.js +61 -3
- package/src/lib/pricing/seed-snapshot.json +1 -1
- package/src/lib/rollout.js +83 -2
- package/src/lib/session-analytics.js +11 -0
- package/src/lib/skills-manager.js +226 -30
- package/dashboard/dist/assets/DialogDescription-BMyOicfz.js +0 -1
- package/dashboard/dist/assets/SkillsPage-eXI09q03.js +0 -1
- package/dashboard/dist/assets/use-reduced-motion-BqDKim_3.js +0 -1
package/src/lib/rollout.js
CHANGED
|
@@ -21,6 +21,11 @@ const {
|
|
|
21
21
|
currentCodexModel,
|
|
22
22
|
snapshotCodexModelAttributionState,
|
|
23
23
|
} = require("./codex-model-attribution");
|
|
24
|
+
const {
|
|
25
|
+
CODEX_SERVICE_TIER_MARKER,
|
|
26
|
+
readCodexServiceTier,
|
|
27
|
+
isPriorityServiceTier,
|
|
28
|
+
} = require("./codex-service-tier");
|
|
24
29
|
const {
|
|
25
30
|
DEVIN_TABLE_PROBE_SQL,
|
|
26
31
|
devinUsageSql,
|
|
@@ -471,6 +476,7 @@ async function parseRolloutIncremental({
|
|
|
471
476
|
? prev.tokenUsageBaselines || null
|
|
472
477
|
: null;
|
|
473
478
|
const lastModel = sameInode && !truncated ? prev.lastModel || null : null;
|
|
479
|
+
const lastServiceTier = sameInode && !truncated ? prev.lastServiceTier || null : null;
|
|
474
480
|
const modelAttributionState = sameInode && !truncated
|
|
475
481
|
? prev.modelAttributionState || null
|
|
476
482
|
: null;
|
|
@@ -551,6 +557,7 @@ async function parseRolloutIncremental({
|
|
|
551
557
|
lastTotal,
|
|
552
558
|
tokenUsageBaselines,
|
|
553
559
|
lastModel,
|
|
560
|
+
lastServiceTier,
|
|
554
561
|
modelAttributionState,
|
|
555
562
|
projectState,
|
|
556
563
|
projectMetaCache,
|
|
@@ -566,6 +573,7 @@ async function parseRolloutIncremental({
|
|
|
566
573
|
lastTotal,
|
|
567
574
|
tokenUsageBaselines,
|
|
568
575
|
lastModel,
|
|
576
|
+
lastServiceTier,
|
|
569
577
|
modelAttributionState,
|
|
570
578
|
hourlyState,
|
|
571
579
|
touchedBuckets,
|
|
@@ -589,6 +597,7 @@ async function parseRolloutIncremental({
|
|
|
589
597
|
lastTotal: result.lastTotal,
|
|
590
598
|
tokenUsageBaselines: result.tokenUsageBaselines,
|
|
591
599
|
lastModel: result.lastModel,
|
|
600
|
+
lastServiceTier: result.lastServiceTier || null,
|
|
592
601
|
modelAttributionState: result.modelAttributionState,
|
|
593
602
|
updatedAt: new Date().toISOString(),
|
|
594
603
|
};
|
|
@@ -2060,6 +2069,7 @@ async function parseRolloutFile({
|
|
|
2060
2069
|
lastTotal,
|
|
2061
2070
|
tokenUsageBaselines,
|
|
2062
2071
|
lastModel,
|
|
2072
|
+
lastServiceTier,
|
|
2063
2073
|
modelAttributionState: previousModelAttributionState,
|
|
2064
2074
|
hourlyState,
|
|
2065
2075
|
touchedBuckets,
|
|
@@ -2086,6 +2096,7 @@ async function parseRolloutFile({
|
|
|
2086
2096
|
lastTotal,
|
|
2087
2097
|
tokenUsageBaselines,
|
|
2088
2098
|
lastModel,
|
|
2099
|
+
lastServiceTier: typeof lastServiceTier === "string" ? lastServiceTier : null,
|
|
2089
2100
|
modelAttributionState: previousModelAttributionState,
|
|
2090
2101
|
eventsAggregated: 0,
|
|
2091
2102
|
projectFileContexts,
|
|
@@ -2098,6 +2109,12 @@ async function parseRolloutFile({
|
|
|
2098
2109
|
});
|
|
2099
2110
|
|
|
2100
2111
|
let model = typeof lastModel === "string" ? lastModel : null;
|
|
2112
|
+
// Codex writes thread_settings_applied before the turn_context of the turn it
|
|
2113
|
+
// takes effect on, and token_count rows carry no turn id, so a row's tier is
|
|
2114
|
+
// whatever the last such record said. A session's first turn has none, and
|
|
2115
|
+
// most CLI sessions have none at all — that stays null, and null is billed at
|
|
2116
|
+
// Standard rather than guessed from the current config.
|
|
2117
|
+
let serviceTier = typeof lastServiceTier === "string" ? lastServiceTier : null;
|
|
2101
2118
|
const modelAttributionState = createCodexModelAttributionState(
|
|
2102
2119
|
previousModelAttributionState || { model },
|
|
2103
2120
|
);
|
|
@@ -2148,7 +2165,10 @@ async function parseRolloutFile({
|
|
|
2148
2165
|
line.includes('"cwd"') ||
|
|
2149
2166
|
line.includes('"current_date"') ||
|
|
2150
2167
|
line.includes('"forked_from_id"'));
|
|
2151
|
-
|
|
2168
|
+
const maybeServiceTier =
|
|
2169
|
+
!maybeTokenCount && !maybeModelReroute && !maybeTurnContext &&
|
|
2170
|
+
line.includes(CODEX_SERVICE_TIER_MARKER);
|
|
2171
|
+
if (!maybeTokenCount && !maybeTurnContext && !maybeModelReroute && !maybeServiceTier) {
|
|
2152
2172
|
if (invalidRecordPolicy === "throw" || !record.terminated) {
|
|
2153
2173
|
try {
|
|
2154
2174
|
JSON.parse(line);
|
|
@@ -2174,6 +2194,12 @@ async function parseRolloutFile({
|
|
|
2174
2194
|
applyCodexModelEvent(modelAttributionState, obj);
|
|
2175
2195
|
model = currentCodexModel(modelAttributionState) || model;
|
|
2176
2196
|
|
|
2197
|
+
const appliedServiceTier = readCodexServiceTier(obj);
|
|
2198
|
+
if (appliedServiceTier) {
|
|
2199
|
+
serviceTier = appliedServiceTier;
|
|
2200
|
+
continue;
|
|
2201
|
+
}
|
|
2202
|
+
|
|
2177
2203
|
if (
|
|
2178
2204
|
(obj?.type === "turn_context" || obj?.type === "session_meta") &&
|
|
2179
2205
|
obj?.payload &&
|
|
@@ -2301,6 +2327,9 @@ async function parseRolloutFile({
|
|
|
2301
2327
|
|
|
2302
2328
|
const bucket = getHourlyBucket(hourlyState, source, model, bucketStart);
|
|
2303
2329
|
addTotals(bucket.totals, delta);
|
|
2330
|
+
// Only the model bucket gets the subset: the project bucket below carries
|
|
2331
|
+
// no model, so no per-model rate can be applied to it anyway.
|
|
2332
|
+
if (isPriorityServiceTier(serviceTier)) addPriorityUsage(bucket.totals, delta);
|
|
2304
2333
|
touchedBuckets.add(bucketKey(source, model, bucketStart));
|
|
2305
2334
|
if (currentProjectKey && projectState && projectTouchedBuckets) {
|
|
2306
2335
|
const projectBucket = getProjectBucket(
|
|
@@ -2321,6 +2350,7 @@ async function parseRolloutFile({
|
|
|
2321
2350
|
lastTotal: latestTotal,
|
|
2322
2351
|
tokenUsageBaselines: snapshotUsageBaselines(usageDeltaState),
|
|
2323
2352
|
lastModel: model,
|
|
2353
|
+
lastServiceTier: serviceTier,
|
|
2324
2354
|
modelAttributionState: snapshotCodexModelAttributionState(modelAttributionState),
|
|
2325
2355
|
eventsAggregated,
|
|
2326
2356
|
projectFileContexts,
|
|
@@ -2333,6 +2363,7 @@ async function scanRolloutProjectFileContexts({
|
|
|
2333
2363
|
lastTotal,
|
|
2334
2364
|
tokenUsageBaselines,
|
|
2335
2365
|
lastModel,
|
|
2366
|
+
lastServiceTier,
|
|
2336
2367
|
modelAttributionState,
|
|
2337
2368
|
projectState,
|
|
2338
2369
|
projectMetaCache,
|
|
@@ -2351,6 +2382,7 @@ async function scanRolloutProjectFileContexts({
|
|
|
2351
2382
|
lastTotal,
|
|
2352
2383
|
tokenUsageBaselines,
|
|
2353
2384
|
lastModel,
|
|
2385
|
+
lastServiceTier: lastServiceTier || null,
|
|
2354
2386
|
modelAttributionState,
|
|
2355
2387
|
eventsAggregated: 0,
|
|
2356
2388
|
projectFileContexts,
|
|
@@ -2428,6 +2460,7 @@ async function scanRolloutProjectFileContexts({
|
|
|
2428
2460
|
lastTotal,
|
|
2429
2461
|
tokenUsageBaselines,
|
|
2430
2462
|
lastModel,
|
|
2463
|
+
lastServiceTier: lastServiceTier || null,
|
|
2431
2464
|
modelAttributionState,
|
|
2432
2465
|
eventsAggregated: 0,
|
|
2433
2466
|
projectFileContexts,
|
|
@@ -3003,6 +3036,7 @@ async function enqueueTouchedBuckets({ queuePath, hourlyState, touchedBuckets })
|
|
|
3003
3036
|
billable_total_tokens: totals.billable_total_tokens ?? totals.total_tokens,
|
|
3004
3037
|
total_cost_usd: totals.total_cost_usd || 0,
|
|
3005
3038
|
usage_precision: usagePrecision || undefined,
|
|
3039
|
+
...prioritySubsetOf(totals),
|
|
3006
3040
|
conversation_count: totals.conversation_count,
|
|
3007
3041
|
}),
|
|
3008
3042
|
);
|
|
@@ -3083,6 +3117,7 @@ async function enqueueTouchedBuckets({ queuePath, hourlyState, touchedBuckets })
|
|
|
3083
3117
|
billable_total_tokens: unknownBucket.totals.billable_total_tokens ?? unknownBucket.totals.total_tokens,
|
|
3084
3118
|
total_cost_usd: unknownBucket.totals.total_cost_usd || 0,
|
|
3085
3119
|
usage_precision: usagePrecision || undefined,
|
|
3120
|
+
...prioritySubsetOf(unknownBucket.totals),
|
|
3086
3121
|
conversation_count: unknownBucket.totals.conversation_count,
|
|
3087
3122
|
}),
|
|
3088
3123
|
);
|
|
@@ -3131,6 +3166,7 @@ async function enqueueTouchedBuckets({ queuePath, hourlyState, touchedBuckets })
|
|
|
3131
3166
|
total_tokens: group.totals.total_tokens,
|
|
3132
3167
|
billable_total_tokens: group.totals.billable_total_tokens ?? group.totals.total_tokens,
|
|
3133
3168
|
total_cost_usd: group.totals.total_cost_usd || 0,
|
|
3169
|
+
...prioritySubsetOf(group.totals),
|
|
3134
3170
|
conversation_count: group.totals.conversation_count,
|
|
3135
3171
|
}),
|
|
3136
3172
|
);
|
|
@@ -3716,7 +3752,43 @@ function initTotals() {
|
|
|
3716
3752
|
};
|
|
3717
3753
|
}
|
|
3718
3754
|
|
|
3755
|
+
// Observed-subset markers, same shape as the session sidecar's
|
|
3756
|
+
// long_context_* columns: "how much of the columns above came from requests on
|
|
3757
|
+
// the priority (Astra Fast) service tier". They annotate the existing columns
|
|
3758
|
+
// and are NEVER part of any sum — total_tokens stays
|
|
3759
|
+
// input + output + cache_creation + cache_read + reasoning_output. They are
|
|
3760
|
+
// omitted entirely when zero so no other provider's queue rows change.
|
|
3761
|
+
const PRIORITY_SUBSET_FIELDS = [
|
|
3762
|
+
["priority_input_tokens", "input_tokens"],
|
|
3763
|
+
["priority_cached_input_tokens", "cached_input_tokens"],
|
|
3764
|
+
["priority_cache_creation_input_tokens", "cache_creation_input_tokens"],
|
|
3765
|
+
["priority_output_tokens", "output_tokens"],
|
|
3766
|
+
["priority_reasoning_output_tokens", "reasoning_output_tokens"],
|
|
3767
|
+
];
|
|
3768
|
+
|
|
3769
|
+
// The whole delta belongs to a priority request, so every base column it
|
|
3770
|
+
// carries is priority usage.
|
|
3771
|
+
function addPriorityUsage(target, delta) {
|
|
3772
|
+
for (const [field, base] of PRIORITY_SUBSET_FIELDS) {
|
|
3773
|
+
const value = Number(delta?.[base] || 0);
|
|
3774
|
+
if (value > 0) target[field] = (Number(target[field]) || 0) + value;
|
|
3775
|
+
}
|
|
3776
|
+
}
|
|
3777
|
+
|
|
3778
|
+
function prioritySubsetOf(totals) {
|
|
3779
|
+
const subset = {};
|
|
3780
|
+
for (const [field] of PRIORITY_SUBSET_FIELDS) {
|
|
3781
|
+
const value = Number(totals?.[field]) || 0;
|
|
3782
|
+
if (value > 0) subset[field] = value;
|
|
3783
|
+
}
|
|
3784
|
+
return subset;
|
|
3785
|
+
}
|
|
3786
|
+
|
|
3719
3787
|
function addTotals(target, delta) {
|
|
3788
|
+
for (const [field] of PRIORITY_SUBSET_FIELDS) {
|
|
3789
|
+
const value = Number(delta?.[field]) || 0;
|
|
3790
|
+
if (value > 0) target[field] = (Number(target[field]) || 0) + value;
|
|
3791
|
+
}
|
|
3720
3792
|
target.input_tokens += delta.input_tokens || 0;
|
|
3721
3793
|
target.cached_input_tokens += delta.cached_input_tokens || 0;
|
|
3722
3794
|
target.cache_creation_input_tokens += delta.cache_creation_input_tokens || 0;
|
|
@@ -3731,6 +3803,11 @@ function addTotals(target, delta) {
|
|
|
3731
3803
|
}
|
|
3732
3804
|
|
|
3733
3805
|
function subtractTotals(target, totals) {
|
|
3806
|
+
for (const [field] of PRIORITY_SUBSET_FIELDS) {
|
|
3807
|
+
const current = Number(target[field]) || 0;
|
|
3808
|
+
if (current <= 0) continue;
|
|
3809
|
+
target[field] = Math.max(0, current - (Number(totals?.[field]) || 0));
|
|
3810
|
+
}
|
|
3734
3811
|
target.input_tokens = Math.max(0, target.input_tokens - (totals.input_tokens || 0));
|
|
3735
3812
|
target.cached_input_tokens = Math.max(
|
|
3736
3813
|
0,
|
|
@@ -3764,7 +3841,7 @@ function subtractTotals(target, totals) {
|
|
|
3764
3841
|
}
|
|
3765
3842
|
|
|
3766
3843
|
function totalsKey(totals) {
|
|
3767
|
-
|
|
3844
|
+
const base = [
|
|
3768
3845
|
totals.input_tokens || 0,
|
|
3769
3846
|
totals.cached_input_tokens || 0,
|
|
3770
3847
|
totals.cache_creation_input_tokens || 0,
|
|
@@ -3775,6 +3852,10 @@ function totalsKey(totals) {
|
|
|
3775
3852
|
totals.total_cost_usd || 0,
|
|
3776
3853
|
totals.conversation_count || 0,
|
|
3777
3854
|
].join("|");
|
|
3855
|
+
// Appended only when a priority subset exists, so buckets that never see the
|
|
3856
|
+
// tier keep their previously persisted queuedKey and are not re-enqueued.
|
|
3857
|
+
const priority = PRIORITY_SUBSET_FIELDS.map(([field]) => Number(totals[field]) || 0);
|
|
3858
|
+
return priority.some((value) => value > 0) ? `${base}|p:${priority.join(",")}` : base;
|
|
3778
3859
|
}
|
|
3779
3860
|
|
|
3780
3861
|
function toUtcHalfHourStart(ts) {
|
|
@@ -131,9 +131,20 @@ const MODEL_USAGE_SUM_FIELDS = [
|
|
|
131
131
|
"long_context_cache_creation_input_tokens",
|
|
132
132
|
"long_context_output_tokens",
|
|
133
133
|
"long_context_reasoning_output_tokens",
|
|
134
|
+
"priority_input_tokens",
|
|
135
|
+
"priority_cached_input_tokens",
|
|
136
|
+
"priority_cache_creation_input_tokens",
|
|
137
|
+
"priority_output_tokens",
|
|
138
|
+
"priority_reasoning_output_tokens",
|
|
139
|
+
"priority_long_context_input_tokens",
|
|
140
|
+
"priority_long_context_cached_input_tokens",
|
|
141
|
+
"priority_long_context_cache_creation_input_tokens",
|
|
142
|
+
"priority_long_context_output_tokens",
|
|
143
|
+
"priority_long_context_reasoning_output_tokens",
|
|
134
144
|
"usage_events",
|
|
135
145
|
"rerouted_usage_events",
|
|
136
146
|
"long_context_usage_events",
|
|
147
|
+
"priority_usage_events",
|
|
137
148
|
// Not a token counter: an edit turn is a turn, and turn_context both marks
|
|
138
149
|
// where a turn begins and names its model, so an edit turn has exactly one
|
|
139
150
|
// owner. Splitting it here keeps by_model.tokens_per_edit a ratio over one
|
|
@@ -909,7 +909,12 @@ function listInstalledSkills() {
|
|
|
909
909
|
});
|
|
910
910
|
}
|
|
911
911
|
|
|
912
|
-
async function installSkill(
|
|
912
|
+
async function installSkill(
|
|
913
|
+
skillInput,
|
|
914
|
+
targetIds = ["claude", "codex"],
|
|
915
|
+
prefetchedTree = null,
|
|
916
|
+
{ expectExistingId = null } = {},
|
|
917
|
+
) {
|
|
913
918
|
const skill = {
|
|
914
919
|
key: String(skillInput?.key || ""),
|
|
915
920
|
name: String(skillInput?.name || ""),
|
|
@@ -939,7 +944,9 @@ async function installSkill(skillInput, targetIds = ["claude", "codex"]) {
|
|
|
939
944
|
);
|
|
940
945
|
}
|
|
941
946
|
|
|
942
|
-
|
|
947
|
+
// Bulk update hands in the repo tree it already fetched, so N skills from one
|
|
948
|
+
// repo cost one GitHub call instead of N.
|
|
949
|
+
const { branch, tree } = prefetchedTree || await getRepoTree({
|
|
943
950
|
owner: skill.repoOwner,
|
|
944
951
|
name: skill.repoName,
|
|
945
952
|
branch: skill.repoBranch,
|
|
@@ -950,9 +957,10 @@ async function installSkill(skillInput, targetIds = ["claude", "codex"]) {
|
|
|
950
957
|
if (!files.some((entry) => /(^|\/)SKILL\.md$/i.test(entry.path))) throw new Error("SKILL.md not found in selected directory");
|
|
951
958
|
|
|
952
959
|
const dest = managedSkillPath(installName);
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
ensureDir(
|
|
960
|
+
let selectedTargets = targetIds.filter((id) => TARGETS[id]);
|
|
961
|
+
const tempRoot = path.join(dataDir(), "tmp");
|
|
962
|
+
ensureDir(tempRoot);
|
|
963
|
+
const temp = fs.mkdtempSync(path.join(tempRoot, `${installName}-`));
|
|
956
964
|
try {
|
|
957
965
|
for (const entry of files) {
|
|
958
966
|
const relative = entry.path === sourceDir ? path.basename(entry.path) : entry.path.slice(sourceDir.length + 1);
|
|
@@ -962,6 +970,17 @@ async function installSkill(skillInput, targetIds = ["claude", "codex"]) {
|
|
|
962
970
|
ensureDir(path.dirname(out));
|
|
963
971
|
fs.writeFileSync(out, await fetchText(githubRawUrl(skill.repoOwner, skill.repoName, branch, entry.path)));
|
|
964
972
|
}
|
|
973
|
+
// An update must not recreate a skill that was uninstalled while we were
|
|
974
|
+
// downloading, nor overwrite whatever took its install directory in the
|
|
975
|
+
// meantime. Checked here because everything from this point to
|
|
976
|
+
// saveRegistry() is synchronous — no await can interleave a change we would
|
|
977
|
+
// then clobber.
|
|
978
|
+
if (expectExistingId) {
|
|
979
|
+
const current = readRegistry().skills.find((entry) => !entry.trashedAt && entry.id === expectExistingId);
|
|
980
|
+
if (!current) throw new Error("Skill is no longer installed");
|
|
981
|
+
// Another page or API caller may have changed target intent during a download.
|
|
982
|
+
selectedTargets = (current.targets || []).filter((id) => TARGETS[id]);
|
|
983
|
+
}
|
|
965
984
|
removePath(dest);
|
|
966
985
|
ensureDir(path.dirname(dest));
|
|
967
986
|
fs.renameSync(temp, dest);
|
|
@@ -973,7 +992,6 @@ async function installSkill(skillInput, targetIds = ["claude", "codex"]) {
|
|
|
973
992
|
const skillMarker = findSkillMarker(dest);
|
|
974
993
|
const skillMd = skillMarker ? fs.readFileSync(skillMarker, "utf8") : "";
|
|
975
994
|
const metadata = readSkillMetadata(skillMd, skill.name || installName);
|
|
976
|
-
const selectedTargets = targetIds.filter((id) => TARGETS[id]);
|
|
977
995
|
const installed = {
|
|
978
996
|
id: `${skill.repoOwner}/${skill.repoName}:${sourceDir}`,
|
|
979
997
|
key: `${skill.repoOwner}/${skill.repoName}:${sourceDir}`,
|
|
@@ -992,9 +1010,13 @@ async function installSkill(skillInput, targetIds = ["claude", "codex"]) {
|
|
|
992
1010
|
targets: selectedTargets,
|
|
993
1011
|
};
|
|
994
1012
|
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
1013
|
+
// Re-read: the downloads above can take minutes and the registry is shared
|
|
1014
|
+
// with every other mutation path, so the copy fetched before them is stale by
|
|
1015
|
+
// now. Reading here keeps the read/modify/write window down to this block.
|
|
1016
|
+
const latest = readRegistry();
|
|
1017
|
+
latest.skills = latest.skills.filter((entry) => entry.id !== installed.id && entry.directory.toLowerCase() !== installName.toLowerCase());
|
|
1018
|
+
latest.skills.push(installed);
|
|
1019
|
+
saveRegistry(latest);
|
|
998
1020
|
|
|
999
1021
|
for (const id of selectedTargets) syncSkillToTarget(installName, id);
|
|
1000
1022
|
appendActivity({ action: "install", name: installed.name, directory: installName, targets: selectedTargets, source: `${skill.repoOwner}/${skill.repoName}` });
|
|
@@ -1244,20 +1266,89 @@ function updateCachePath() {
|
|
|
1244
1266
|
return path.join(dataDir(), "updates-cache.json");
|
|
1245
1267
|
}
|
|
1246
1268
|
|
|
1269
|
+
// checkUpdates() and updateSkills() have to agree on three things: which skills
|
|
1270
|
+
// are candidates, how a cached verdict is keyed, and which tree a skill's
|
|
1271
|
+
// signature comes from. They share these so the badge and the update can never
|
|
1272
|
+
// disagree about the same skill.
|
|
1273
|
+
function managedUpdateCandidates(registry) {
|
|
1274
|
+
return registry.skills.filter(
|
|
1275
|
+
(skill) => !skill.trashedAt && skill.repoOwner && skill.repoName && skill.sourceSignature,
|
|
1276
|
+
);
|
|
1277
|
+
}
|
|
1278
|
+
|
|
1279
|
+
function updateFingerprint(managed) {
|
|
1280
|
+
return managed
|
|
1281
|
+
.map((skill) => `${skill.id}@${skill.sourceSignature}`)
|
|
1282
|
+
.sort()
|
|
1283
|
+
.join("|");
|
|
1284
|
+
}
|
|
1285
|
+
|
|
1286
|
+
// Write the verdicts a round derived, carrying forward the previous map for
|
|
1287
|
+
// anything it could not derive — a repo whose tree failed, everything past a
|
|
1288
|
+
// rate-limited stop. "We could not check" must not render as "nothing is
|
|
1289
|
+
// stale": that clears every badge and with it the way to retry. Returns the
|
|
1290
|
+
// merged map. Entries for skills that are no longer installed are dropped.
|
|
1291
|
+
//
|
|
1292
|
+
// `full` says the round attempted the whole library, which only checkUpdates()
|
|
1293
|
+
// does. An update attempts the ids it was handed, so it may only publish an
|
|
1294
|
+
// entry when the carried map fills in the rest — see the guard below.
|
|
1295
|
+
function writeUpdateCache(verdicts, { full = false } = {}) {
|
|
1296
|
+
const managed = managedUpdateCandidates(readRegistry());
|
|
1297
|
+
const live = new Set(managed.map((skill) => skill.id));
|
|
1298
|
+
const cached = readJson(updateCachePath(), null);
|
|
1299
|
+
const carriedFresh = Boolean(
|
|
1300
|
+
cached &&
|
|
1301
|
+
cached.updates &&
|
|
1302
|
+
typeof cached.updates === "object" &&
|
|
1303
|
+
Number.isFinite(cached.checkedAt) &&
|
|
1304
|
+
Date.now() - cached.checkedAt < UPDATE_CACHE_TTL_MS,
|
|
1305
|
+
);
|
|
1306
|
+
const updates = {};
|
|
1307
|
+
if (carriedFresh) {
|
|
1308
|
+
for (const [id, stale] of Object.entries(cached.updates)) if (live.has(id)) updates[id] = Boolean(stale);
|
|
1309
|
+
}
|
|
1310
|
+
for (const [id, stale] of Object.entries(verdicts)) if (live.has(id)) updates[id] = Boolean(stale);
|
|
1311
|
+
|
|
1312
|
+
// The entry is read back as a verdict for the entire library, and a missing
|
|
1313
|
+
// key reads as "not stale". A partial round that stamped the library
|
|
1314
|
+
// fingerprint would therefore publish silence about skills nobody looked at,
|
|
1315
|
+
// and the next check would hit it. Leave the cache alone instead and let that
|
|
1316
|
+
// check do the work.
|
|
1317
|
+
if (!full && !managed.every((skill) => Object.prototype.hasOwnProperty.call(updates, skill.id))) {
|
|
1318
|
+
return updates;
|
|
1319
|
+
}
|
|
1320
|
+
|
|
1321
|
+
writeJson(updateCachePath(), {
|
|
1322
|
+
fingerprint: updateFingerprint(managed),
|
|
1323
|
+
// A prime inherits the window of the check it built on: it re-derived some
|
|
1324
|
+
// ids, not the library, so it must not restart the hour for the rest.
|
|
1325
|
+
checkedAt: full || !carriedFresh ? Date.now() : cached.checkedAt,
|
|
1326
|
+
updates,
|
|
1327
|
+
});
|
|
1328
|
+
return updates;
|
|
1329
|
+
}
|
|
1330
|
+
|
|
1331
|
+
function groupSkillsByRepo(skills) {
|
|
1332
|
+
const byRepo = new Map();
|
|
1333
|
+
for (const skill of skills) {
|
|
1334
|
+
const branch = skill.repoBranch || "main";
|
|
1335
|
+
const key = `${skill.repoOwner}/${skill.repoName}@${branch}`.toLowerCase();
|
|
1336
|
+
if (!byRepo.has(key)) {
|
|
1337
|
+
byRepo.set(key, { owner: skill.repoOwner, name: skill.repoName, branch, skills: [] });
|
|
1338
|
+
}
|
|
1339
|
+
byRepo.get(key).skills.push(skill);
|
|
1340
|
+
}
|
|
1341
|
+
return Array.from(byRepo.values());
|
|
1342
|
+
}
|
|
1343
|
+
|
|
1247
1344
|
// Compare each managed GitHub/skills.sh skill's stored source signature against a
|
|
1248
1345
|
// freshly fetched repo tree. One tree call per repo (skills from the same repo
|
|
1249
1346
|
// share it), concurrency-limited and cached for an hour so a background check
|
|
1250
1347
|
// can't trip GitHub's unauthenticated rate limit. Returns { updates: {id:bool} }.
|
|
1251
1348
|
// Read-only: never mutates the registry or the on-disk skills.
|
|
1252
1349
|
async function checkUpdates({ force = false } = {}) {
|
|
1253
|
-
const
|
|
1254
|
-
const
|
|
1255
|
-
(skill) => !skill.trashedAt && skill.repoOwner && skill.repoName && skill.sourceSignature,
|
|
1256
|
-
);
|
|
1257
|
-
const fingerprint = managed
|
|
1258
|
-
.map((skill) => `${skill.id}@${skill.sourceSignature}`)
|
|
1259
|
-
.sort()
|
|
1260
|
-
.join("|");
|
|
1350
|
+
const managed = managedUpdateCandidates(readRegistry());
|
|
1351
|
+
const fingerprint = updateFingerprint(managed);
|
|
1261
1352
|
|
|
1262
1353
|
if (!force) {
|
|
1263
1354
|
const cached = readJson(updateCachePath(), null);
|
|
@@ -1273,18 +1364,8 @@ async function checkUpdates({ force = false } = {}) {
|
|
|
1273
1364
|
}
|
|
1274
1365
|
}
|
|
1275
1366
|
|
|
1276
|
-
const byRepo = new Map();
|
|
1277
|
-
for (const skill of managed) {
|
|
1278
|
-
const branch = skill.repoBranch || "main";
|
|
1279
|
-
const key = `${skill.repoOwner}/${skill.repoName}@${branch}`.toLowerCase();
|
|
1280
|
-
if (!byRepo.has(key)) {
|
|
1281
|
-
byRepo.set(key, { owner: skill.repoOwner, name: skill.repoName, branch, skills: [] });
|
|
1282
|
-
}
|
|
1283
|
-
byRepo.get(key).skills.push(skill);
|
|
1284
|
-
}
|
|
1285
|
-
|
|
1286
1367
|
const updates = {};
|
|
1287
|
-
await mapWithConcurrency(
|
|
1368
|
+
await mapWithConcurrency(groupSkillsByRepo(managed), UPDATE_CHECK_CONCURRENCY, async (repo) => {
|
|
1288
1369
|
let tree;
|
|
1289
1370
|
try {
|
|
1290
1371
|
({ tree } = await getRepoTree(repo));
|
|
@@ -1299,8 +1380,122 @@ async function checkUpdates({ force = false } = {}) {
|
|
|
1299
1380
|
});
|
|
1300
1381
|
|
|
1301
1382
|
const checkedAt = Date.now();
|
|
1302
|
-
|
|
1303
|
-
|
|
1383
|
+
return { updates: writeUpdateCache(updates, { full: true }), checkedAt, cached: false };
|
|
1384
|
+
}
|
|
1385
|
+
|
|
1386
|
+
// Re-install managed skills from upstream, grouped by repo like checkUpdates()
|
|
1387
|
+
// so N skills from one repo cost one tree call. Sequential by necessity:
|
|
1388
|
+
// installSkill() read/modify/writes the registry, so parallel installs would
|
|
1389
|
+
// drop each other's entries. Every requested id gets a result row — including
|
|
1390
|
+
// ones that are no longer installed — so updated + skipped + failed always
|
|
1391
|
+
// reconciles with what the caller asked for.
|
|
1392
|
+
async function updateSkills(ids = []) {
|
|
1393
|
+
// An empty or malformed list means "nothing was asked for", never "every
|
|
1394
|
+
// managed skill" — the permissive reading turned one bad request field into a
|
|
1395
|
+
// full re-install of the library.
|
|
1396
|
+
const wanted = new Set(
|
|
1397
|
+
(Array.isArray(ids) ? ids : []).map((id) => String(id || "")).filter(Boolean),
|
|
1398
|
+
);
|
|
1399
|
+
if (!wanted.size) return { results: [], updated: 0, skipped: 0, failed: 0, rateLimited: null };
|
|
1400
|
+
const managed = readRegistry().skills.filter(
|
|
1401
|
+
(skill) => !skill.trashedAt && skill.repoOwner && skill.repoName && wanted.has(skill.id),
|
|
1402
|
+
);
|
|
1403
|
+
|
|
1404
|
+
const results = [];
|
|
1405
|
+
const verdicts = {};
|
|
1406
|
+
let rateLimited = null;
|
|
1407
|
+
|
|
1408
|
+
const known = new Set(managed.map((skill) => skill.id));
|
|
1409
|
+
for (const id of wanted) {
|
|
1410
|
+
if (!known.has(id)) results.push({ id, name: null, ok: false, error: "Skill is no longer installed" });
|
|
1411
|
+
}
|
|
1412
|
+
|
|
1413
|
+
for (const repo of groupSkillsByRepo(managed)) {
|
|
1414
|
+
let branch;
|
|
1415
|
+
let tree;
|
|
1416
|
+
try {
|
|
1417
|
+
({ branch, tree } = await getRepoTree(repo));
|
|
1418
|
+
} catch (error) {
|
|
1419
|
+
if (error instanceof RateLimitError) {
|
|
1420
|
+
rateLimited = error;
|
|
1421
|
+
break;
|
|
1422
|
+
}
|
|
1423
|
+
for (const skill of repo.skills) {
|
|
1424
|
+
results.push({ id: skill.id, name: skill.name, ok: false, error: error?.message || "Unable to read repository" });
|
|
1425
|
+
}
|
|
1426
|
+
continue;
|
|
1427
|
+
}
|
|
1428
|
+
|
|
1429
|
+
for (const skill of repo.skills) {
|
|
1430
|
+
const sourceDir = skill.sourceDirectory || skill.directory;
|
|
1431
|
+
const fresh = sourceSignatureFromTree(tree, sourceDir);
|
|
1432
|
+
if (skill.sourceSignature && fresh === skill.sourceSignature) {
|
|
1433
|
+
results.push({ id: skill.id, name: skill.name, ok: true, skipped: true });
|
|
1434
|
+
verdicts[skill.id] = false;
|
|
1435
|
+
continue;
|
|
1436
|
+
}
|
|
1437
|
+
// This loop runs for as long as the downloads take, and the registry is
|
|
1438
|
+
// shared with every other mutation path — re-check rather than write back
|
|
1439
|
+
// a skill that was uninstalled while we were working.
|
|
1440
|
+
if (!readRegistry().skills.some((entry) => !entry.trashedAt && entry.id === skill.id)) {
|
|
1441
|
+
results.push({ id: skill.id, name: skill.name, ok: false, error: "Skill is no longer installed" });
|
|
1442
|
+
continue;
|
|
1443
|
+
}
|
|
1444
|
+
try {
|
|
1445
|
+
await installSkill(
|
|
1446
|
+
{
|
|
1447
|
+
key: skill.key,
|
|
1448
|
+
name: skill.name,
|
|
1449
|
+
description: skill.description,
|
|
1450
|
+
// installSkill() wants the repo path, not the flat install name.
|
|
1451
|
+
directory: sourceDir,
|
|
1452
|
+
readmeUrl: skill.readmeUrl,
|
|
1453
|
+
repoOwner: skill.repoOwner,
|
|
1454
|
+
repoName: skill.repoName,
|
|
1455
|
+
repoBranch: branch,
|
|
1456
|
+
},
|
|
1457
|
+
skill.targets || [],
|
|
1458
|
+
{ branch, tree },
|
|
1459
|
+
{ expectExistingId: skill.id },
|
|
1460
|
+
);
|
|
1461
|
+
results.push({ id: skill.id, name: skill.name, ok: true, skipped: false });
|
|
1462
|
+
if (fresh) verdicts[skill.id] = false;
|
|
1463
|
+
} catch (error) {
|
|
1464
|
+
if (error instanceof RateLimitError) {
|
|
1465
|
+
rateLimited = error;
|
|
1466
|
+
break;
|
|
1467
|
+
}
|
|
1468
|
+
results.push({ id: skill.id, name: skill.name, ok: false, error: error?.message || "Update failed" });
|
|
1469
|
+
if (fresh) verdicts[skill.id] = true;
|
|
1470
|
+
}
|
|
1471
|
+
}
|
|
1472
|
+
if (rateLimited) break;
|
|
1473
|
+
}
|
|
1474
|
+
|
|
1475
|
+
// A rate-limited stop breaks out of both loops, leaving the skill it happened
|
|
1476
|
+
// on and everything after it unreported. Backfill so every requested id comes
|
|
1477
|
+
// back with a row and the counts still reconcile with what was asked for.
|
|
1478
|
+
if (rateLimited) {
|
|
1479
|
+
const reported = new Set(results.map((row) => row.id));
|
|
1480
|
+
const nameById = new Map(managed.map((skill) => [skill.id, skill.name]));
|
|
1481
|
+
for (const id of wanted) {
|
|
1482
|
+
if (reported.has(id)) continue;
|
|
1483
|
+
results.push({ id, name: nameById.get(id) || null, ok: false, error: rateLimited.message });
|
|
1484
|
+
}
|
|
1485
|
+
}
|
|
1486
|
+
|
|
1487
|
+
// Prime the cache from the trees this run already holds, so the refresh that
|
|
1488
|
+
// follows is a cache hit rather than a second tree call per repo. Skipped when
|
|
1489
|
+
// the run derived nothing — there is no fresh verdict to record.
|
|
1490
|
+
if (Object.keys(verdicts).length) writeUpdateCache(verdicts);
|
|
1491
|
+
|
|
1492
|
+
return {
|
|
1493
|
+
results,
|
|
1494
|
+
updated: results.filter((entry) => entry.ok && !entry.skipped).length,
|
|
1495
|
+
skipped: results.filter((entry) => entry.skipped).length,
|
|
1496
|
+
failed: results.filter((entry) => !entry.ok).length,
|
|
1497
|
+
rateLimited: rateLimited ? rateLimited.message : null,
|
|
1498
|
+
};
|
|
1304
1499
|
}
|
|
1305
1500
|
|
|
1306
1501
|
// skills.sh exposes only /api/search (no leaderboard endpoint), so "Popular" is
|
|
@@ -1385,4 +1580,5 @@ module.exports = {
|
|
|
1385
1580
|
sourceSignatureFromTree,
|
|
1386
1581
|
targetList,
|
|
1387
1582
|
uninstallSkill,
|
|
1583
|
+
updateSkills,
|
|
1388
1584
|
};
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import{r as n,v as a}from"./__vite-browser-external-BDkeh90N.js";import{u as p}from"./DialogTitle-DL5nvD31.js";import{u as c}from"./composite-BIgEqWZD.js";const x=n.forwardRef(function(e,r){const{render:l,className:u,style:m,id:s,...t}=e,i=p(),o=c(s);return i.useSyncedValueWithCleanup("descriptionElementId",o),a("p",e,{ref:r,props:[{id:o},t]})});export{x as D};
|