@orquentra/codebuddy 2.8.0 → 2.9.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/dist/{chunk-6DONJW3W.js → chunk-2WH2Q4TA.js} +22 -5
- package/dist/chunk-2WH2Q4TA.js.map +1 -0
- package/dist/{chunk-EJQVGFH2.js → chunk-ENKDFLOO.js} +2 -2
- package/dist/{chunk-ZNLSCV3J.js → chunk-F4BJ3DXW.js} +5 -5
- package/dist/{chunk-NY2YXP3R.js → chunk-QN76UJP7.js} +2 -2
- package/dist/{chunk-6KGYAFG7.js → chunk-SRA4MDVG.js} +2 -2
- package/dist/{chunk-6KGYAFG7.js.map → chunk-SRA4MDVG.js.map} +1 -1
- package/dist/{chunk-XL2HLACE.js → chunk-YN7TEIUH.js} +1 -1
- package/dist/chunk-YN7TEIUH.js.map +1 -0
- package/dist/cli/index.js +3 -3
- package/dist/index.js +5 -5
- package/dist/{main-QUASSFP7.js → main-YJKSYFPT.js} +638 -204
- package/dist/main-YJKSYFPT.js.map +1 -0
- package/dist/{operations-WJR7AZZF.js → operations-OG4ISWIM.js} +3 -3
- package/dist/{pretool-command-JTKX6G3M.js → pretool-command-PRFOIHMW.js} +3 -3
- package/dist/{runtime-OQQ22VJF.js → runtime-GCSEIAVD.js} +4 -4
- package/package.json +1 -1
- package/dist/chunk-6DONJW3W.js.map +0 -1
- package/dist/chunk-XL2HLACE.js.map +0 -1
- package/dist/main-QUASSFP7.js.map +0 -1
- /package/dist/{chunk-EJQVGFH2.js.map → chunk-ENKDFLOO.js.map} +0 -0
- /package/dist/{chunk-ZNLSCV3J.js.map → chunk-F4BJ3DXW.js.map} +0 -0
- /package/dist/{chunk-NY2YXP3R.js.map → chunk-QN76UJP7.js.map} +0 -0
- /package/dist/{operations-WJR7AZZF.js.map → operations-OG4ISWIM.js.map} +0 -0
- /package/dist/{pretool-command-JTKX6G3M.js.map → pretool-command-PRFOIHMW.js.map} +0 -0
- /package/dist/{runtime-OQQ22VJF.js.map → runtime-GCSEIAVD.js.map} +0 -0
|
@@ -4,7 +4,7 @@ import {
|
|
|
4
4
|
} from "./chunk-UK6XTS2Y.js";
|
|
5
5
|
import {
|
|
6
6
|
runPreToolHookCommand
|
|
7
|
-
} from "./chunk-
|
|
7
|
+
} from "./chunk-ENKDFLOO.js";
|
|
8
8
|
import {
|
|
9
9
|
failSoft,
|
|
10
10
|
readHookInput,
|
|
@@ -65,9 +65,10 @@ import {
|
|
|
65
65
|
telemetryBlocked,
|
|
66
66
|
telemetryStatePath,
|
|
67
67
|
telemetryUrl,
|
|
68
|
+
traceCliCommand,
|
|
68
69
|
watchRepository,
|
|
69
70
|
writeTelemetryState
|
|
70
|
-
} from "./chunk-
|
|
71
|
+
} from "./chunk-2WH2Q4TA.js";
|
|
71
72
|
import {
|
|
72
73
|
DEFAULT_ZVEC_MODEL,
|
|
73
74
|
buildZvecIndex,
|
|
@@ -90,7 +91,7 @@ import {
|
|
|
90
91
|
runResetHook,
|
|
91
92
|
runStageHook,
|
|
92
93
|
runSyncHook
|
|
93
|
-
} from "./chunk-
|
|
94
|
+
} from "./chunk-F4BJ3DXW.js";
|
|
94
95
|
import {
|
|
95
96
|
SessionCapsuleLedger,
|
|
96
97
|
detectTestCommand,
|
|
@@ -100,7 +101,7 @@ import {
|
|
|
100
101
|
parseTraceLines,
|
|
101
102
|
readTraces,
|
|
102
103
|
tracePath
|
|
103
|
-
} from "./chunk-
|
|
104
|
+
} from "./chunk-YN7TEIUH.js";
|
|
104
105
|
import {
|
|
105
106
|
hookRoot
|
|
106
107
|
} from "./chunk-TGFTKWOR.js";
|
|
@@ -116,11 +117,11 @@ import {
|
|
|
116
117
|
pingDatabase,
|
|
117
118
|
runDoctor,
|
|
118
119
|
runMigrations
|
|
119
|
-
} from "./chunk-
|
|
120
|
+
} from "./chunk-QN76UJP7.js";
|
|
120
121
|
import {
|
|
121
122
|
MemoryFileStore,
|
|
122
123
|
VERSION
|
|
123
|
-
} from "./chunk-
|
|
124
|
+
} from "./chunk-SRA4MDVG.js";
|
|
124
125
|
import {
|
|
125
126
|
computeFactHash,
|
|
126
127
|
generateEntityId
|
|
@@ -1199,7 +1200,7 @@ th.n { text-align: right; }
|
|
|
1199
1200
|
.chip { display: inline-block; font: 600 10px var(--mono); text-transform: uppercase; letter-spacing: .06em; padding: 2px 6px; border-radius: 4px; }
|
|
1200
1201
|
.chip.recall { background: var(--recall-soft); color: var(--recall); }
|
|
1201
1202
|
.chip.tool { background: var(--tool-soft); color: var(--tool); }
|
|
1202
|
-
.chip.capture, .chip.sync, .chip.guard, .chip.kw { background: var(--line); color: var(--capture); }
|
|
1203
|
+
.chip.capture, .chip.sync, .chip.guard, .chip.cli, .chip.kw { background: var(--line); color: var(--capture); }
|
|
1203
1204
|
.chip.err { background: var(--bad-soft); color: var(--bad); }
|
|
1204
1205
|
.feed { display: grid; gap: 10px; max-height: 520px; overflow: auto; }
|
|
1205
1206
|
.prompt { border: 1px solid var(--line); border-radius: 8px; padding: 10px 12px; }
|
|
@@ -3431,101 +3432,174 @@ var ADMIN_HTML = `<!doctype html>
|
|
|
3431
3432
|
<title>CodeBuddy Usage</title>
|
|
3432
3433
|
<style>
|
|
3433
3434
|
:root {
|
|
3434
|
-
--bg: #f4f4f2; --panel: #fcfcfb; --fg: #0b0b0b; --muted: #52514e; --line: #
|
|
3435
|
-
--bar: #2a78d6; --bar-soft: #e3edfa; --
|
|
3435
|
+
--bg: #f4f4f2; --panel: #fcfcfb; --fg: #0b0b0b; --muted: #52514e; --faint: #8a8984; --line: #e4e3de;
|
|
3436
|
+
--bar: #2a78d6; --bar-soft: #e3edfa; --hook: #eb6834; --cli: #1baf7a;
|
|
3437
|
+
--good: #0a7d0a; --bad: #c23030; --bad-soft: #fbeaea;
|
|
3436
3438
|
--sans: ui-sans-serif, -apple-system, "Segoe UI", Roboto, sans-serif;
|
|
3437
3439
|
--mono: ui-monospace, "SF Mono", Menlo, Consolas, monospace;
|
|
3438
3440
|
}
|
|
3439
3441
|
@media (prefers-color-scheme: dark) { :root {
|
|
3440
|
-
--bg: #
|
|
3441
|
-
--bar: #3987e5; --bar-soft: #1d2b3d; --
|
|
3442
|
+
--bg: #111110; --panel: #1a1a19; --fg: #f0efec; --muted: #c3c2b7; --faint: #8f8e86; --line: #2e2e2b;
|
|
3443
|
+
--bar: #3987e5; --bar-soft: #1d2b3d; --hook: #d95926; --cli: #199e70;
|
|
3444
|
+
--good: #3fbf3f; --bad: #f07070; --bad-soft: #3a1a1a; color-scheme: dark } }
|
|
3442
3445
|
* { box-sizing: border-box; }
|
|
3443
3446
|
body { margin: 0; background: var(--bg); color: var(--fg); font: 14px/1.5 var(--sans); }
|
|
3444
|
-
|
|
3447
|
+
a { color: inherit; }
|
|
3448
|
+
.wrap { max-width: 1240px; margin: 0 auto; padding: 0 16px 56px; display: grid; gap: 16px; }
|
|
3445
3449
|
.wrap > * { min-width: 0; }
|
|
3446
|
-
|
|
3447
|
-
|
|
3448
|
-
|
|
3449
|
-
|
|
3450
|
+
.top { position: sticky; top: 0; z-index: 5; background: color-mix(in srgb, var(--bg) 88%, transparent); backdrop-filter: blur(8px); border-bottom: 1px solid var(--line); }
|
|
3451
|
+
.bar-inner { max-width: 1240px; margin: 0 auto; padding: 12px 16px; display: flex; flex-wrap: wrap; align-items: center; gap: 10px 18px; }
|
|
3452
|
+
.brand { display: flex; align-items: center; gap: 10px; font-weight: 650; font-size: 16px; letter-spacing: -.01em; }
|
|
3453
|
+
.mark { width: 22px; height: 22px; border-radius: 6px; background: var(--bar); display: grid; place-items: center; color: #fff; font: 700 12px var(--mono); }
|
|
3454
|
+
.updated { font: 12px var(--mono); color: var(--faint); }
|
|
3455
|
+
.tabs { display: flex; gap: 4px; }
|
|
3456
|
+
.tabs a { text-decoration: none; color: var(--muted); font-weight: 500; font-size: 13px; padding: 6px 10px; border-radius: 7px; }
|
|
3457
|
+
.tabs a[aria-current="page"] { background: var(--panel); color: var(--fg); box-shadow: inset 0 0 0 1px var(--line); }
|
|
3458
|
+
.tabs a:focus-visible, .seg button:focus-visible, .btn:focus-visible, input:focus-visible, th button:focus-visible, tr.link:focus-visible { outline: 2px solid var(--bar); outline-offset: 1px; }
|
|
3450
3459
|
.spacer { flex: 1; }
|
|
3451
3460
|
.seg { display: inline-flex; border: 1px solid var(--line); border-radius: 8px; overflow: hidden; background: var(--panel); }
|
|
3452
|
-
.seg button { border: 0; background: none; color: var(--muted); font: 500 12px var(--sans); padding: 6px
|
|
3461
|
+
.seg button { border: 0; background: none; color: var(--muted); font: 500 12px var(--sans); padding: 6px 11px; cursor: pointer; }
|
|
3453
3462
|
.seg button + button { border-left: 1px solid var(--line); }
|
|
3454
3463
|
.seg button[aria-pressed="true"] { background: var(--fg); color: var(--bg); }
|
|
3455
|
-
|
|
3456
|
-
.
|
|
3457
|
-
.
|
|
3464
|
+
h2 { font-size: 12px; margin: 0; text-transform: uppercase; letter-spacing: .08em; color: var(--muted); font-weight: 600; }
|
|
3465
|
+
.head { display: flex; align-items: baseline; flex-wrap: wrap; gap: 6px 12px; margin-bottom: 12px; }
|
|
3466
|
+
.head .note { font-size: 12px; color: var(--faint); }
|
|
3467
|
+
.hero { display: grid; grid-template-columns: repeat(auto-fit, minmax(min(190px, 100%), 1fr)); gap: 12px; padding-top: 16px; }
|
|
3468
|
+
.kpi { background: var(--panel); border: 1px solid var(--line); border-radius: 12px; padding: 14px 16px; }
|
|
3458
3469
|
.kpi .label { font-size: 12px; color: var(--muted); }
|
|
3459
|
-
.kpi .value { font:
|
|
3460
|
-
.kpi .sub { font-size: 12px; color: var(--muted); margin-top:
|
|
3461
|
-
.
|
|
3462
|
-
.
|
|
3463
|
-
.
|
|
3464
|
-
.
|
|
3465
|
-
.
|
|
3466
|
-
svg
|
|
3467
|
-
|
|
3468
|
-
.
|
|
3469
|
-
.
|
|
3470
|
-
.
|
|
3471
|
-
.
|
|
3470
|
+
.kpi .value { font: 650 32px/1.15 var(--mono); font-variant-numeric: tabular-nums; margin-top: 6px; letter-spacing: -.02em; }
|
|
3471
|
+
.kpi .sub { font-size: 12px; color: var(--muted); margin-top: 4px; }
|
|
3472
|
+
.up { color: var(--good); font-weight: 600; }
|
|
3473
|
+
.down { color: var(--bad); font-weight: 600; }
|
|
3474
|
+
.panel { background: var(--panel); border: 1px solid var(--line); border-radius: 12px; padding: 16px; min-width: 0; }
|
|
3475
|
+
.grid2 { display: grid; grid-template-columns: repeat(auto-fit, minmax(min(340px, 100%), 1fr)); gap: 16px; }
|
|
3476
|
+
.grid3 { display: grid; grid-template-columns: repeat(auto-fit, minmax(min(260px, 100%), 1fr)); gap: 16px; }
|
|
3477
|
+
.chart svg { display: block; width: 100%; height: 190px; overflow: visible; }
|
|
3478
|
+
svg text { fill: var(--faint); font: 11px var(--mono); }
|
|
3479
|
+
.hit { fill: transparent; cursor: crosshair; }
|
|
3480
|
+
.hit:hover { fill: color-mix(in srgb, var(--fg) 6%, transparent); }
|
|
3481
|
+
.tip { position: absolute; pointer-events: none; background: var(--fg); color: var(--bg); font: 12px var(--mono); padding: 5px 8px; border-radius: 6px; white-space: nowrap; display: none; z-index: 10; }
|
|
3482
|
+
.rows { display: grid; gap: 8px; }
|
|
3483
|
+
.row { display: grid; grid-template-columns: minmax(0, 1fr) auto; gap: 3px 12px; font-size: 13px; }
|
|
3484
|
+
.row .track { grid-column: 1 / -1; height: 8px; background: var(--bar-soft); border-radius: 4px; overflow: hidden; }
|
|
3485
|
+
.row .fill { height: 8px; background: var(--bar); border-radius: 4px; }
|
|
3472
3486
|
.row .n { font-family: var(--mono); font-variant-numeric: tabular-nums; color: var(--muted); }
|
|
3487
|
+
.funnel .row { font-size: 14px; }
|
|
3488
|
+
.funnel .track { height: 22px; border-radius: 6px; }
|
|
3489
|
+
.funnel .fill { height: 22px; border-radius: 6px; }
|
|
3473
3490
|
.scroll { overflow-x: auto; }
|
|
3474
3491
|
table { width: 100%; border-collapse: collapse; font-size: 13px; }
|
|
3475
3492
|
th { text-align: left; font: 600 11px var(--sans); text-transform: uppercase; letter-spacing: .06em; color: var(--muted); padding: 6px 8px; border-bottom: 1px solid var(--line); white-space: nowrap; }
|
|
3476
|
-
|
|
3493
|
+
th button { all: unset; cursor: pointer; }
|
|
3494
|
+
th button::after { content: attr(data-dir); margin-left: 4px; color: var(--faint); }
|
|
3495
|
+
td { padding: 7px 8px; border-bottom: 1px solid var(--line); white-space: nowrap; }
|
|
3477
3496
|
tr:last-child td { border-bottom: 0; }
|
|
3478
3497
|
td.n, th.n { text-align: right; font-family: var(--mono); font-variant-numeric: tabular-nums; }
|
|
3498
|
+
tr.link { cursor: pointer; }
|
|
3499
|
+
tr.link:hover td { background: var(--bar-soft); }
|
|
3500
|
+
.inline { display: inline-block; height: 6px; background: var(--bar); border-radius: 3px; vertical-align: middle; margin-left: 8px; }
|
|
3501
|
+
.heat td { text-align: center; font: 12px var(--mono); padding: 6px 4px; border: 2px solid var(--panel); border-radius: 6px; min-width: 44px; }
|
|
3502
|
+
.heat td.lab { text-align: left; color: var(--muted); font-family: var(--sans); white-space: nowrap; }
|
|
3503
|
+
.heat td.empty { background: transparent; color: var(--faint); }
|
|
3479
3504
|
.bad { color: var(--bad); }
|
|
3480
3505
|
.muted { color: var(--muted); }
|
|
3481
3506
|
.mono { font-family: var(--mono); }
|
|
3482
|
-
.
|
|
3483
|
-
.
|
|
3484
|
-
|
|
3485
|
-
.
|
|
3486
|
-
.
|
|
3507
|
+
.empty-state { padding: 36px 16px; text-align: center; color: var(--muted); }
|
|
3508
|
+
.toolbar { display: flex; flex-wrap: wrap; gap: 10px; align-items: center; margin-bottom: 12px; }
|
|
3509
|
+
input[type="search"] { font: 13px var(--sans); padding: 7px 10px; border: 1px solid var(--line); border-radius: 8px; background: var(--bg); color: var(--fg); min-width: 0; flex: 1 1 200px; }
|
|
3510
|
+
.btn { border: 1px solid var(--line); background: var(--panel); color: var(--fg); font: 500 12px var(--sans); padding: 7px 12px; border-radius: 8px; cursor: pointer; text-decoration: none; }
|
|
3511
|
+
.chips { display: flex; flex-wrap: wrap; gap: 6px; }
|
|
3512
|
+
.chip { display: inline-flex; align-items: center; gap: 6px; font: 600 11px var(--mono); padding: 2px 8px; border-radius: 999px; background: var(--bar-soft); color: var(--fg); }
|
|
3513
|
+
.sw { width: 10px; height: 10px; border-radius: 3px; display: inline-block; }
|
|
3514
|
+
.sessions { display: grid; gap: 6px; max-height: 520px; overflow: auto; }
|
|
3515
|
+
.session { all: unset; display: grid; gap: 2px; padding: 8px 10px; border: 1px solid var(--line); border-radius: 8px; cursor: pointer; font-size: 12px; }
|
|
3516
|
+
.session[aria-pressed="true"] { border-color: var(--bar); box-shadow: inset 3px 0 0 var(--bar); }
|
|
3517
|
+
.session:focus-visible { outline: 2px solid var(--bar); }
|
|
3518
|
+
.split { display: grid; grid-template-columns: minmax(220px, 300px) minmax(0, 1fr); gap: 16px; }
|
|
3519
|
+
@media (max-width: 820px) { .split { grid-template-columns: 1fr; } .wf .step { grid-template-columns: 120px 52px minmax(0, 1fr) 56px; } }
|
|
3520
|
+
.wf { display: grid; gap: 3px; font-size: 12px; }
|
|
3521
|
+
.wf .step { display: grid; grid-template-columns: 170px 64px minmax(0, 1fr) 70px; gap: 10px; align-items: center; }
|
|
3522
|
+
.wf .lane { position: relative; height: 14px; background: var(--bg); border-radius: 3px; }
|
|
3523
|
+
.wf .seg-bar { position: absolute; top: 2px; height: 10px; min-width: 3px; border-radius: 3px; }
|
|
3524
|
+
.wf .name { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-family: var(--mono); }
|
|
3525
|
+
.wf .at { font-family: var(--mono); color: var(--faint); text-align: right; }
|
|
3526
|
+
.wf .t { text-align: right; font-family: var(--mono); color: var(--muted); }
|
|
3527
|
+
.legend { display: flex; gap: 14px; font-size: 12px; color: var(--muted); margin-bottom: 10px; flex-wrap: wrap; }
|
|
3528
|
+
.legend span { display: inline-flex; align-items: center; gap: 6px; }
|
|
3529
|
+
footer { font-size: 12px; color: var(--faint); }
|
|
3530
|
+
[hidden] { display: none !important; }
|
|
3487
3531
|
</style>
|
|
3488
3532
|
</head>
|
|
3489
3533
|
<body>
|
|
3534
|
+
<div class="top"><div class="bar-inner">
|
|
3535
|
+
<div class="brand"><span class="mark">cb</span>CodeBuddy usage</div>
|
|
3536
|
+
<nav class="tabs" aria-label="Views">
|
|
3537
|
+
<a href="#overview" data-view="overview">Overview</a>
|
|
3538
|
+
<a href="#users" data-view="users">Users</a>
|
|
3539
|
+
<a href="#events" data-view="events">Events</a>
|
|
3540
|
+
</nav>
|
|
3541
|
+
<span class="spacer"></span>
|
|
3542
|
+
<span class="updated" id="updated">loading\u2026</span>
|
|
3543
|
+
<div class="seg" role="group" aria-label="Time range" id="range">
|
|
3544
|
+
<button data-days="7">7d</button><button data-days="30" aria-pressed="true">30d</button><button data-days="90">90d</button><button data-days="365">1y</button>
|
|
3545
|
+
</div>
|
|
3546
|
+
</div></div>
|
|
3547
|
+
|
|
3490
3548
|
<div class="wrap">
|
|
3491
|
-
<
|
|
3492
|
-
<
|
|
3493
|
-
<
|
|
3494
|
-
|
|
3495
|
-
<
|
|
3496
|
-
<button data-days="30" aria-pressed="true">30 days</button>
|
|
3497
|
-
<button data-days="90">90 days</button>
|
|
3498
|
-
<button data-days="365">1 year</button>
|
|
3549
|
+
<section id="view-overview">
|
|
3550
|
+
<div class="hero" id="hero" aria-label="Headline numbers"></div>
|
|
3551
|
+
<div class="grid2" style="margin-top:16px">
|
|
3552
|
+
<section class="panel"><div class="head"><h2>Total installs</h2><span class="note">all-time, by first-seen day</span></div><div class="chart" id="cumulative"></div></section>
|
|
3553
|
+
<section class="panel"><div class="head"><h2>Daily active installs</h2><span class="note">installs that sent any event</span></div><div class="chart" id="active"></div></section>
|
|
3499
3554
|
</div>
|
|
3500
|
-
|
|
3501
|
-
|
|
3502
|
-
|
|
3503
|
-
|
|
3504
|
-
<section class="panel"><h2>
|
|
3505
|
-
|
|
3506
|
-
|
|
3507
|
-
|
|
3508
|
-
|
|
3509
|
-
|
|
3510
|
-
<section class="panel"><h2>
|
|
3511
|
-
</
|
|
3512
|
-
|
|
3513
|
-
|
|
3514
|
-
<div class="
|
|
3555
|
+
<div class="grid2" style="margin-top:16px">
|
|
3556
|
+
<section class="panel"><div class="head"><h2>Activation</h2><span class="note">installs active in range reaching each step</span></div><div class="rows funnel" id="funnel"></div></section>
|
|
3557
|
+
<section class="panel"><div class="head"><h2>Retention by weekly cohort</h2><span class="note">share of each week's new installs active in later weeks</span></div><div class="scroll" id="cohorts"></div></section>
|
|
3558
|
+
</div>
|
|
3559
|
+
<section class="panel" style="margin-top:16px"><div class="head"><h2>Feature usage</h2><span class="note">MCP tools, hook steps (recall, capture\u2026) and CLI commands</span></div><div class="scroll" id="features"></div></section>
|
|
3560
|
+
<div class="grid3" style="margin-top:16px">
|
|
3561
|
+
<section class="panel"><div class="head"><h2>Agent clients</h2></div><div class="rows" id="clients"></div></section>
|
|
3562
|
+
<section class="panel"><div class="head"><h2>Versions</h2></div><div class="rows" id="versions"></div></section>
|
|
3563
|
+
<section class="panel"><div class="head"><h2>Platforms</h2></div><div class="rows" id="platforms"></div></section>
|
|
3564
|
+
</div>
|
|
3565
|
+
<section class="panel" style="margin-top:16px"><div class="head"><h2>npm downloads per day</h2><span class="note" id="npmNote"></span></div><div class="chart" id="npm"></div></section>
|
|
3566
|
+
</section>
|
|
3567
|
+
|
|
3568
|
+
<section id="view-users" class="panel" style="margin-top:16px" hidden>
|
|
3569
|
+
<div class="toolbar">
|
|
3570
|
+
<h2>Users</h2><span class="muted" id="userCount" style="font-size:12px"></span><span class="spacer"></span>
|
|
3571
|
+
<input type="search" id="userSearch" placeholder="Filter by id, client, version, platform\u2026" aria-label="Filter users">
|
|
3572
|
+
<button class="btn" id="csv">Download CSV</button>
|
|
3573
|
+
</div>
|
|
3574
|
+
<div class="scroll" id="users"></div>
|
|
3515
3575
|
</section>
|
|
3516
|
-
|
|
3517
|
-
<
|
|
3576
|
+
|
|
3577
|
+
<section id="view-install" style="margin-top:16px" hidden></section>
|
|
3578
|
+
|
|
3579
|
+
<section id="view-events" class="panel" style="margin-top:16px" hidden>
|
|
3580
|
+
<div class="toolbar"><h2>Latest events</h2><span class="spacer"></span><div class="chips" id="kindFilter"></div></div>
|
|
3581
|
+
<div class="scroll" id="recent"></div>
|
|
3582
|
+
</section>
|
|
3583
|
+
|
|
3584
|
+
<footer>Anonymous telemetry from installs that did not opt out: random install ids, tool and command names, timings, versions. No prompts, code, paths or repository names are collected. Shares count each install once, from its latest batch.</footer>
|
|
3518
3585
|
</div>
|
|
3519
|
-
<div class="tip" id="tip"></div>
|
|
3586
|
+
<div class="tip" id="tip" role="status"></div>
|
|
3520
3587
|
<script>
|
|
3521
3588
|
(function () {
|
|
3522
3589
|
var days = 30;
|
|
3523
|
-
|
|
3524
|
-
var
|
|
3525
|
-
var
|
|
3526
|
-
var
|
|
3590
|
+
var summary = null, npm = null, install = null;
|
|
3591
|
+
var userSort = { key: "lastSeen", dir: -1 };
|
|
3592
|
+
var kindShown = "all";
|
|
3593
|
+
var selectedSession = null;
|
|
3594
|
+
var path = location.pathname;
|
|
3595
|
+
var base = path.charAt(path.length - 1) === "/" ? path : path + "/";
|
|
3527
3596
|
var tip = document.getElementById("tip");
|
|
3597
|
+
var NL = String.fromCharCode(10);
|
|
3598
|
+
var KIND_COLOR = { tool: "var(--bar)", cli: "var(--cli)" };
|
|
3599
|
+
function kindColor(kind) { return KIND_COLOR[kind] || "var(--hook)"; }
|
|
3600
|
+
function kindGroup(kind) { return kind === "tool" ? "MCP tool" : kind === "cli" ? "CLI command" : "hook step"; }
|
|
3528
3601
|
|
|
3602
|
+
function $(id) { return document.getElementById(id); }
|
|
3529
3603
|
function esc(value) {
|
|
3530
3604
|
return String(value == null ? "" : value).replace(/[&<>"']/g, function (c) {
|
|
3531
3605
|
return { "&": "&", "<": "<", ">": ">", '"': """, "'": "'" }[c];
|
|
@@ -3533,36 +3607,57 @@ td.n, th.n { text-align: right; font-family: var(--mono); font-variant-numeric:
|
|
|
3533
3607
|
}
|
|
3534
3608
|
function num(n) { return Math.round(n).toLocaleString(); }
|
|
3535
3609
|
function compact(n) { return n >= 1e6 ? (n / 1e6).toFixed(1) + "M" : n >= 1e4 ? (n / 1e3).toFixed(1) + "k" : num(n); }
|
|
3536
|
-
function pct(n) { return (n * 100).toFixed(n < 0.1 ? 1 : 0) + "%"; }
|
|
3537
|
-
function
|
|
3610
|
+
function pct(n) { return (n * 100).toFixed(n > 0 && n < 0.1 ? 1 : 0) + "%"; }
|
|
3611
|
+
function plural(n, word) { return num(n) + " " + word + (n === 1 ? "" : "s"); }
|
|
3612
|
+
function offset(n) { var s = Math.round(n / 1000); return "+" + (s >= 60 ? Math.floor(s / 60) + "m " + (s % 60) + "s" : s + "s"); }
|
|
3613
|
+
function ms(n) { return n >= 60000 ? (n / 60000).toFixed(1) + " min" : n >= 1000 ? (n / 1000).toFixed(1) + " s" : Math.round(n) + " ms"; }
|
|
3614
|
+
function date(iso) { return new Date(iso).toLocaleDateString(undefined, { month: "short", day: "numeric" }); }
|
|
3615
|
+
function ago(iso) {
|
|
3616
|
+
var s = Math.max(0, (Date.now() - Date.parse(iso)) / 1000);
|
|
3617
|
+
return s < 60 ? "just now" : s < 3600 ? Math.floor(s / 60) + "m ago" : s < 86400 ? Math.floor(s / 3600) + "h ago" : Math.floor(s / 86400) + "d ago";
|
|
3618
|
+
}
|
|
3619
|
+
function get(url) { return fetch(base + url).then(function (r) { return r.ok ? r.json() : null; }); }
|
|
3538
3620
|
|
|
3539
3621
|
function kpi(label, value, sub) {
|
|
3540
|
-
return '<div class="kpi"><div class="label">' + esc(label) + '</div><div class="value">' + esc(value) +
|
|
3541
|
-
|
|
3622
|
+
return '<div class="kpi"><div class="label">' + esc(label) + '</div><div class="value">' + esc(value) + '</div><div class="sub">' + (sub || "") + "</div></div>";
|
|
3623
|
+
}
|
|
3624
|
+
function delta(now, before) {
|
|
3625
|
+
if (!before) return now ? '<span class="up">\u25B2 new</span> vs previous 7 days' : "no activity yet";
|
|
3626
|
+
var change = (now - before) / before;
|
|
3627
|
+
return '<span class="' + (change >= 0 ? "up" : "down") + '">' + (change >= 0 ? "\u25B2 " : "\u25BC ") + pct(Math.abs(change)) + "</span> vs previous 7 days";
|
|
3542
3628
|
}
|
|
3543
3629
|
|
|
3544
|
-
/** Bars
|
|
3545
|
-
|
|
3546
|
-
|
|
3547
|
-
var
|
|
3630
|
+
/** Bars or a filled line over days, one measure; every point has a hover target. */
|
|
3631
|
+
/** Draw into a container at its own pixel width, so labels never stretch. */
|
|
3632
|
+
function draw(id, points, label, line) {
|
|
3633
|
+
var el = $(id);
|
|
3634
|
+
if (el) el.innerHTML = chart(points, label, line, el.clientWidth);
|
|
3635
|
+
}
|
|
3636
|
+
function chart(points, label, line, width) {
|
|
3637
|
+
if (!points.length) return '<div class="empty-state">No data yet.</div>';
|
|
3638
|
+
var w = Math.max(280, width || 640), h = 190, top = 10, bottom = 22, left = 40, plot = h - top - bottom;
|
|
3548
3639
|
var max = Math.max.apply(null, points.map(function (p) { return p.value; }).concat([1]));
|
|
3549
3640
|
var step = (w - left) / points.length;
|
|
3550
|
-
var
|
|
3641
|
+
var y = function (v) { return top + plot * (1 - v / max); };
|
|
3551
3642
|
var out = '<svg viewBox="0 0 ' + w + " " + h + '" preserveAspectRatio="none" role="img" aria-label="' + esc(label) + '">';
|
|
3552
3643
|
[0, 0.5, 1].forEach(function (f) {
|
|
3553
|
-
|
|
3554
|
-
out += '<
|
|
3555
|
-
out += '<text x="' + (left - 6) + '" y="' + (y + 4) + '" text-anchor="end">' + esc(compact(max * f)) + "</text>";
|
|
3644
|
+
out += '<line x1="' + left + '" x2="' + w + '" y1="' + y(max * f) + '" y2="' + y(max * f) + '" stroke="var(--line)"/>';
|
|
3645
|
+
out += '<text x="' + (left - 6) + '" y="' + (y(max * f) + 4) + '" text-anchor="end">' + esc(compact(max * f)) + "</text>";
|
|
3556
3646
|
});
|
|
3647
|
+
if (line) {
|
|
3648
|
+
var pts = points.map(function (p, i) { return (left + i * step + step / 2).toFixed(1) + "," + y(p.value).toFixed(1); });
|
|
3649
|
+
out += '<path d="M' + (left + step / 2) + "," + (top + plot) + " L" + pts.join(" L") + " L" + (left + (points.length - 0.5) * step) + "," + (top + plot) + ' Z" fill="var(--bar-soft)"/>';
|
|
3650
|
+
out += '<polyline points="' + pts.join(" ") + '" fill="none" stroke="var(--bar)" stroke-width="2" vector-effect="non-scaling-stroke" stroke-linejoin="round"/>';
|
|
3651
|
+
}
|
|
3557
3652
|
points.forEach(function (p, i) {
|
|
3558
|
-
var
|
|
3559
|
-
|
|
3560
|
-
|
|
3561
|
-
|
|
3562
|
-
|
|
3653
|
+
var x = left + i * step;
|
|
3654
|
+
if (!line) {
|
|
3655
|
+
var bh = plot * (p.value / max), bw = Math.max(1, step - 2);
|
|
3656
|
+
out += '<rect x="' + (x + 1) + '" y="' + (top + plot - bh) + '" width="' + bw + '" height="' + bh + '" rx="' + Math.min(2, bw / 2) + '" fill="var(--bar)"/>';
|
|
3657
|
+
}
|
|
3658
|
+
out += '<rect class="hit" x="' + x + '" y="' + top + '" width="' + step + '" height="' + plot + '" data-tip="' + esc(p.date + " \xB7 " + num(p.value) + " " + label) + '"/>';
|
|
3563
3659
|
});
|
|
3564
|
-
|
|
3565
|
-
out += '<text x="' + left + '" y="' + (h - 6) + '">' + esc(first) + '</text><text x="' + w + '" y="' + (h - 6) + '" text-anchor="end">' + esc(last) + "</text>";
|
|
3660
|
+
out += '<text x="' + left + '" y="' + (h - 5) + '">' + esc(points[0].date) + '</text><text x="' + w + '" y="' + (h - 5) + '" text-anchor="end">' + esc(points[points.length - 1].date) + "</text>";
|
|
3566
3661
|
return out + "</svg>";
|
|
3567
3662
|
}
|
|
3568
3663
|
|
|
@@ -3570,85 +3665,230 @@ td.n, th.n { text-align: right; font-family: var(--mono); font-variant-numeric:
|
|
|
3570
3665
|
if (!items.length) return '<div class="muted">No data yet.</div>';
|
|
3571
3666
|
return items.slice(0, 8).map(function (item) {
|
|
3572
3667
|
var share = total ? item.installs / total : 0;
|
|
3573
|
-
return '<div class="row"><span>' + esc(item.label) + '</span><span class="n">' + num(item.installs) + " \xB7 " + pct(share) +
|
|
3574
|
-
'</span><div class="track"><div class="fill" style="width:' + (share * 100).toFixed(1) + '%"></div></div></div>';
|
|
3668
|
+
return '<div class="row"><span>' + esc(item.label) + '</span><span class="n">' + num(item.installs) + " \xB7 " + pct(share) + '</span><div class="track"><div class="fill" style="width:' + (share * 100).toFixed(1) + '%"></div></div></div>';
|
|
3575
3669
|
}).join("");
|
|
3576
3670
|
}
|
|
3577
3671
|
|
|
3578
|
-
function
|
|
3579
|
-
|
|
3580
|
-
|
|
3672
|
+
function renderOverview() {
|
|
3673
|
+
var t = summary.totals;
|
|
3674
|
+
var npm30 = npmTotal(30);
|
|
3675
|
+
$("hero").innerHTML =
|
|
3676
|
+
kpi("Weekly active installs", num(t.wau), delta(t.wau, t.wauPrevious)) +
|
|
3677
|
+
kpi("Total installs", num(t.allTimeInstalls), "+" + num(t.newInRange) + " in the last " + summary.days + " days \xB7 " + num(t.newToday) + " today") +
|
|
3678
|
+
kpi("Week-1 retention", t.week1Retention == null ? "\u2013" : pct(t.week1Retention), t.week1Retention == null ? "needs a cohort older than 7 days" : "new installs active again the next week") +
|
|
3679
|
+
kpi("Agent sessions", compact(t.sessions), num(t.dau) + " installs active today \xB7 " + num(t.mau) + " this month") +
|
|
3680
|
+
kpi("npm downloads \xB7 30 days", npm30 == null ? "\u2013" : num(npm30), npm && npm.error ? esc(npm.error) : num(npmTotal(365) || 0) + " in the last year");
|
|
3681
|
+
draw("cumulative", summary.daily.map(function (d) { return { date: d.date, value: d.totalInstalls }; }), "total installs", true);
|
|
3682
|
+
draw("active", summary.daily.map(function (d) { return { date: d.date, value: d.installs }; }), "active installs", false);
|
|
3683
|
+
|
|
3684
|
+
var active = summary.funnel[0] ? summary.funnel[0].installs : 0;
|
|
3685
|
+
$("funnel").innerHTML = active ? summary.funnel.map(function (step) {
|
|
3686
|
+
var share = active ? step.installs / active : 0;
|
|
3687
|
+
return '<div class="row"><span>' + esc(step.label) + '</span><span class="n">' + num(step.installs) + " \xB7 " + pct(share) + '</span><div class="track"><div class="fill" style="width:' + (share * 100).toFixed(1) + '%"></div></div></div>';
|
|
3688
|
+
}).join("") : '<div class="empty-state">No active installs in this range.</div>';
|
|
3689
|
+
|
|
3690
|
+
var cohorts = summary.cohorts.filter(function (c) { return c.size > 0; });
|
|
3691
|
+
var weeks = summary.cohorts.length;
|
|
3692
|
+
$("cohorts").innerHTML = cohorts.length
|
|
3693
|
+
? '<table class="heat"><thead><tr><th>Cohort</th><th class="n">Installs</th>' + Array.from({ length: weeks }, function (_, i) { return '<th class="n">W' + i + "</th>"; }).join("") + "</tr></thead><tbody>" +
|
|
3694
|
+
cohorts.map(function (c) {
|
|
3695
|
+
var cells = "";
|
|
3696
|
+
for (var i = 0; i < weeks; i += 1) {
|
|
3697
|
+
var v = c.weeks[i];
|
|
3698
|
+
if (v == null) { cells += '<td class="empty">\xB7</td>'; continue; }
|
|
3699
|
+
var strength = Math.round(12 + v * 88);
|
|
3700
|
+
cells += '<td style="background:color-mix(in oklab, var(--bar) ' + strength + '%, var(--panel));color:' + (v > 0.5 ? "#fff" : "var(--fg)") + '" title="' + esc(c.start + " cohort, week " + i + ": " + pct(v)) + '">' + pct(v) + "</td>";
|
|
3701
|
+
}
|
|
3702
|
+
return '<tr><td class="lab">' + esc(date(c.start)) + '</td><td class="n">' + num(c.size) + "</td>" + cells + "</tr>";
|
|
3703
|
+
}).join("") + "</tbody></table>"
|
|
3704
|
+
: '<div class="empty-state">Cohorts appear once new installs report in.</div>';
|
|
3705
|
+
|
|
3706
|
+
var maxCalls = Math.max.apply(null, summary.features.map(function (f) { return f.calls; }).concat([1]));
|
|
3707
|
+
$("features").innerHTML = summary.features.length
|
|
3708
|
+
? '<table><thead><tr><th>Feature</th><th class="n">Calls</th><th class="n">Installs</th><th class="n">Median</th><th class="n">p95</th><th class="n">Errors</th></tr></thead><tbody>' +
|
|
3709
|
+
summary.features.map(function (f) {
|
|
3710
|
+
return '<tr><td class="mono">' + esc(f.label) + '<span class="inline" style="width:' + Math.max(2, (f.calls / maxCalls) * 120).toFixed(0) + 'px"></span></td><td class="n">' + num(f.calls) + '</td><td class="n">' + num(f.installs) +
|
|
3711
|
+
'</td><td class="n">' + ms(f.p50ms) + '</td><td class="n">' + ms(f.p95ms) + '</td><td class="n' + (f.errorRate > 0.05 ? " bad" : "") + '">' + pct(f.errorRate) + "</td></tr>";
|
|
3712
|
+
}).join("") + "</tbody></table>"
|
|
3713
|
+
: '<div class="empty-state">No events yet. Installs send a batch at most every 10 minutes.</div>';
|
|
3714
|
+
var installs = summary.versions.reduce(function (sum, v) { return sum + v.installs; }, 0);
|
|
3715
|
+
$("clients").innerHTML = rows(summary.clients, installs);
|
|
3716
|
+
$("versions").innerHTML = rows(summary.versions, installs);
|
|
3717
|
+
$("platforms").innerHTML = rows(summary.platforms, installs);
|
|
3718
|
+
renderNpm();
|
|
3581
3719
|
}
|
|
3720
|
+
|
|
3582
3721
|
function npmTotal(n) {
|
|
3583
3722
|
if (!npm || !npm.downloads) return null;
|
|
3584
3723
|
return npm.downloads.slice(-n).reduce(function (sum, d) { return sum + d.downloads; }, 0);
|
|
3585
3724
|
}
|
|
3725
|
+
function renderNpm() {
|
|
3726
|
+
if (!npm) return;
|
|
3727
|
+
if (npm.error) { $("npm").innerHTML = '<div class="empty-state">' + esc(npm.error) + "</div>"; return; }
|
|
3728
|
+
$("npmNote").textContent = npm.package + " \xB7 public npm registry";
|
|
3729
|
+
draw("npm", npm.downloads.slice(-Math.min(days, 365)).map(function (d) { return { date: d.day, value: d.downloads }; }), "downloads", false);
|
|
3730
|
+
}
|
|
3586
3731
|
|
|
3587
|
-
|
|
3588
|
-
|
|
3589
|
-
|
|
3590
|
-
|
|
3591
|
-
|
|
3592
|
-
|
|
3593
|
-
|
|
3594
|
-
|
|
3595
|
-
|
|
3596
|
-
|
|
3597
|
-
|
|
3598
|
-
|
|
3599
|
-
|
|
3600
|
-
|
|
3601
|
-
|
|
3602
|
-
|
|
3603
|
-
|
|
3604
|
-
|
|
3605
|
-
return "<
|
|
3606
|
-
|
|
3607
|
-
|
|
3608
|
-
|
|
3609
|
-
|
|
3610
|
-
|
|
3611
|
-
document.getElementById("versions").innerHTML = rows(summary.versions, installs);
|
|
3612
|
-
document.getElementById("platforms").innerHTML = rows(summary.platforms, installs);
|
|
3613
|
-
document.getElementById("userCount").textContent = num(summary.users.length) + " installs seen in " + summary.days + " days";
|
|
3614
|
-
document.getElementById("users").innerHTML = summary.users.length
|
|
3615
|
-
? "<table><thead><tr><th>Install</th><th>First seen</th><th>Last active</th><th class=\\"n\\">Events</th><th class=\\"n\\">Sessions</th><th class=\\"n\\">Projects</th><th>Client</th><th>Version</th><th>Platform</th><th>Top feature</th></tr></thead><tbody>" +
|
|
3616
|
-
summary.users.map(function (u) {
|
|
3617
|
-
return '<tr><td class="mono">' + esc(u.install) + "</td><td>" + esc(new Date(u.firstSeen).toLocaleDateString()) + "</td><td>" + esc(new Date(u.lastSeen).toLocaleString()) +
|
|
3618
|
-
'</td><td class="n">' + num(u.events) + '</td><td class="n">' + num(u.sessions) + '</td><td class="n">' + num(u.projects) + "</td><td>" + esc(u.client) +
|
|
3619
|
-
'</td><td class="mono">' + esc(u.version) + "</td><td>" + esc(u.platform) + '</td><td class="mono">' + esc(u.topFeature) + "</td></tr>";
|
|
3732
|
+
var USER_COLS = [
|
|
3733
|
+
["install", "Install"], ["firstSeen", "First seen"], ["lastSeen", "Last active"], ["activeDays", "Active days", 1],
|
|
3734
|
+
["sessions", "Sessions", 1], ["events", "Events", 1], ["errors", "Errors", 1], ["client", "Client"], ["version", "Version"], ["platform", "Platform"], ["topFeature", "Top feature"]
|
|
3735
|
+
];
|
|
3736
|
+
function filteredUsers() {
|
|
3737
|
+
var q = $("userSearch").value.trim().toLowerCase();
|
|
3738
|
+
return summary.users.filter(function (u) {
|
|
3739
|
+
return !q || [u.install, u.client, u.version, u.platform, u.topFeature].join(" ").toLowerCase().indexOf(q) !== -1;
|
|
3740
|
+
}).sort(function (a, b) {
|
|
3741
|
+
var x = a[userSort.key], y = b[userSort.key];
|
|
3742
|
+
return (x < y ? -1 : x > y ? 1 : 0) * userSort.dir;
|
|
3743
|
+
});
|
|
3744
|
+
}
|
|
3745
|
+
function renderUsers() {
|
|
3746
|
+
var list = filteredUsers();
|
|
3747
|
+
$("userCount").textContent = num(summary.users.length) + " installs active in the last " + summary.days + " days";
|
|
3748
|
+
$("users").innerHTML = list.length
|
|
3749
|
+
? "<table><thead><tr>" + USER_COLS.map(function (c) {
|
|
3750
|
+
return "<th" + (c[2] ? ' class="n"' : "") + '><button data-sort="' + c[0] + '" data-dir="' + (userSort.key === c[0] ? (userSort.dir > 0 ? "\u2191" : "\u2193") : "") + '">' + c[1] + "</button></th>";
|
|
3751
|
+
}).join("") + "</tr></thead><tbody>" +
|
|
3752
|
+
list.map(function (u) {
|
|
3753
|
+
return '<tr class="link" tabindex="0" data-install="' + esc(u.install) + '"><td class="mono">' + esc(u.install) + "</td><td>" + esc(date(u.firstSeen)) + "</td><td>" + esc(ago(u.lastSeen)) +
|
|
3754
|
+
'</td><td class="n">' + num(u.activeDays) + '</td><td class="n">' + num(u.sessions) + '</td><td class="n">' + num(u.events) + '</td><td class="n' + (u.errors ? " bad" : "") + '">' + num(u.errors) +
|
|
3755
|
+
"</td><td>" + esc(u.client) + '</td><td class="mono">' + esc(u.version) + "</td><td>" + esc(u.platform) + '</td><td class="mono">' + esc(u.topFeature) + "</td></tr>";
|
|
3620
3756
|
}).join("") + "</tbody></table>"
|
|
3621
|
-
: '<div class="empty">No users yet
|
|
3622
|
-
|
|
3623
|
-
|
|
3624
|
-
|
|
3625
|
-
|
|
3626
|
-
|
|
3627
|
-
|
|
3757
|
+
: '<div class="empty-state">' + (summary.users.length ? "No installs match this filter." : "No users yet.") + "</div>";
|
|
3758
|
+
}
|
|
3759
|
+
|
|
3760
|
+
function renderInstall() {
|
|
3761
|
+
var el = $("view-install");
|
|
3762
|
+
if (!install) { el.innerHTML = '<div class="panel empty-state">Loading\u2026</div>'; return; }
|
|
3763
|
+
if (install.missing) { el.innerHTML = '<div class="panel empty-state">No events for this install in the last 90 days. <a href="#users">Back to users</a></div>'; return; }
|
|
3764
|
+
var errors = install.events.filter(function (e) { return !e.ok; }).length;
|
|
3765
|
+
var perDay = {};
|
|
3766
|
+
install.perDay.forEach(function (d) { perDay[d.date] = d.events; });
|
|
3767
|
+
var points = [];
|
|
3768
|
+
for (var i = 89; i >= 0; i -= 1) {
|
|
3769
|
+
var d = new Date(Date.now() - i * 86400000).toISOString().slice(0, 10);
|
|
3770
|
+
points.push({ date: d, value: perDay[d] || 0 });
|
|
3771
|
+
}
|
|
3772
|
+
if (!selectedSession || !install.sessions.some(function (s) { return s.id === selectedSession; })) selectedSession = install.sessions[0] ? install.sessions[0].id : null;
|
|
3773
|
+
el.innerHTML =
|
|
3774
|
+
'<div class="toolbar"><a class="btn" href="#users">\u2190 Users</a><h2 style="font-size:14px;color:var(--fg);text-transform:none;letter-spacing:0">Install <span class="mono">' + esc(install.install) + "</span></h2>" +
|
|
3775
|
+
'<span class="chips"><span class="chip">' + esc(install.client) + '</span><span class="chip">v' + esc(install.version) + '</span><span class="chip">' + esc(install.platform) + '</span><span class="chip">node ' + esc(install.node) + "</span></span></div>" +
|
|
3776
|
+
'<div class="hero" style="padding-top:0">' + kpi("Events \xB7 90 days", num(install.events.length), errors ? '<span class="bad">' + plural(errors, "error") + "</span>" : "no errors") +
|
|
3777
|
+
kpi("Sessions", num(install.sessions.length), "agent sessions with CodeBuddy") + kpi("Active days", num(install.perDay.length), "in the last 90 days") + kpi("Projects", num(install.projects), "hashed, never named") + "</div>" +
|
|
3778
|
+
'<section class="panel" style="margin-top:16px"><div class="head"><h2>Activity</h2><span class="note">events per day, last 90 days</span></div><div class="chart" id="installActivity"></div></section>' +
|
|
3779
|
+
'<section class="panel" style="margin-top:16px"><div class="head"><h2>Session traces</h2><span class="note">each step ends at its recorded time; bar length is its duration</span></div>' +
|
|
3780
|
+
'<div class="split"><div class="sessions" id="sessionList">' + install.sessions.map(function (s) {
|
|
3781
|
+
return '<button class="session" data-session="' + esc(s.id) + '" aria-pressed="' + (s.id === selectedSession) + '"><strong class="mono">' + esc(s.id === "none" ? "no session id" : s.id.slice(0, 8)) + "</strong>" +
|
|
3782
|
+
'<span class="muted">' + esc(new Date(s.start).toLocaleString()) + " \xB7 " + num(s.events) + " steps" + (s.errors ? ' \xB7 <span class="bad">' + plural(s.errors, "error") + "</span>" : "") + "</span></button>";
|
|
3783
|
+
}).join("") + '</div><div id="waterfall"></div></div></section>';
|
|
3784
|
+
draw("installActivity", points, "events", false);
|
|
3785
|
+
renderWaterfall();
|
|
3786
|
+
}
|
|
3787
|
+
|
|
3788
|
+
function renderWaterfall() {
|
|
3789
|
+
var target = $("waterfall");
|
|
3790
|
+
if (!target || !install) return;
|
|
3791
|
+
var steps = install.events.filter(function (e) { return e.session === selectedSession; })
|
|
3792
|
+
.sort(function (a, b) { return a.ts < b.ts ? -1 : a.ts > b.ts ? 1 : 0; }).slice(-200);
|
|
3793
|
+
if (!steps.length) { target.innerHTML = '<div class="empty-state">Pick a session.</div>'; return; }
|
|
3794
|
+
var start = Math.min.apply(null, steps.map(function (e) { return Date.parse(e.ts) - e.ms; }));
|
|
3795
|
+
var end = Math.max.apply(null, steps.map(function (e) { return Date.parse(e.ts); }));
|
|
3796
|
+
var span = Math.max(1, end - start);
|
|
3797
|
+
target.innerHTML = '<div class="legend"><span><i class="sw" style="background:var(--bar)"></i>MCP tool</span><span><i class="sw" style="background:var(--hook)"></i>hook step</span><span><i class="sw" style="background:var(--cli)"></i>CLI command</span><span class="spacer"></span><span>' + esc(ms(span)) + " total</span></div>" +
|
|
3798
|
+
'<div class="wf">' + steps.map(function (e) {
|
|
3799
|
+
var s = Date.parse(e.ts) - e.ms;
|
|
3800
|
+
var left = ((s - start) / span) * 100, width = Math.max(0.8, (e.ms / span) * 100);
|
|
3801
|
+
return '<div class="step"><span class="name' + (e.ok ? "" : " bad") + '" title="' + esc(kindGroup(e.kind) + ": " + e.name) + '">' + (e.ok ? "" : "\u2715 ") + esc(e.feature) + '</span><span class="at">' + esc(offset(s - start)) + '</span><span class="lane"><span class="seg-bar" style="left:' + left.toFixed(2) + "%;width:" + width.toFixed(2) + "%;background:" + kindColor(e.kind) + '" data-tip="' + esc(e.feature + " \xB7 " + ms(e.ms) + (e.ok ? "" : " \xB7 error") + (e.tokensOut ? " \xB7 " + num(e.tokensOut) + " tokens out" : "")) + '"></span></span><span class="t">' + esc(ms(e.ms)) + "</span></div>";
|
|
3802
|
+
}).join("") + "</div>";
|
|
3803
|
+
}
|
|
3804
|
+
|
|
3805
|
+
function renderEvents() {
|
|
3806
|
+
var kinds = ["all"];
|
|
3807
|
+
summary.recent.forEach(function (e) { if (kinds.indexOf(e.kind) === -1) kinds.push(e.kind); });
|
|
3808
|
+
$("kindFilter").innerHTML = '<div class="seg" role="group" aria-label="Event kind">' + kinds.map(function (k) {
|
|
3809
|
+
return '<button data-kind="' + esc(k) + '" aria-pressed="' + (k === kindShown) + '">' + esc(k) + "</button>";
|
|
3810
|
+
}).join("") + "</div>";
|
|
3811
|
+
var list = summary.recent.filter(function (e) { return kindShown === "all" || e.kind === kindShown; });
|
|
3812
|
+
$("recent").innerHTML = list.length
|
|
3813
|
+
? '<table><thead><tr><th>Received</th><th>Install</th><th>Kind</th><th>Name</th><th>Client</th><th>Version</th><th class="n">Time</th><th>Result</th></tr></thead><tbody>' +
|
|
3814
|
+
list.map(function (e) {
|
|
3815
|
+
return '<tr class="link" tabindex="0" data-install="' + esc(e.install) + '"><td>' + esc(new Date(e.receivedAt).toLocaleString()) + '</td><td class="mono">' + esc(e.install) + '</td><td><span class="sw" style="background:' + kindColor(e.kind) + '"></span> ' + esc(e.kind) +
|
|
3816
|
+
'</td><td class="mono">' + esc(e.name) + "</td><td>" + esc(e.client) + '</td><td class="mono">' + esc(e.version) + '</td><td class="n">' + ms(e.ms) + '</td><td class="' + (e.ok ? "" : "bad") + '">' + (e.ok ? "ok" : "\u2715 error") + "</td></tr>";
|
|
3628
3817
|
}).join("") + "</tbody></table>"
|
|
3629
|
-
: '<div class="empty">No events yet.</div>';
|
|
3818
|
+
: '<div class="empty-state">No events yet.</div>';
|
|
3630
3819
|
}
|
|
3631
3820
|
|
|
3632
|
-
function
|
|
3633
|
-
|
|
3821
|
+
function route() {
|
|
3822
|
+
var hash = location.hash.replace("#", "") || "overview";
|
|
3823
|
+
var view = hash.indexOf("install/") === 0 ? "install" : ["overview", "users", "events"].indexOf(hash) !== -1 ? hash : "overview";
|
|
3824
|
+
["overview", "users", "install", "events"].forEach(function (v) { $("view-" + v).hidden = v !== view; });
|
|
3825
|
+
document.querySelectorAll(".tabs a").forEach(function (a) {
|
|
3826
|
+
var current = a.dataset.view === view || (view === "install" && a.dataset.view === "users");
|
|
3827
|
+
if (current) a.setAttribute("aria-current", "page"); else a.removeAttribute("aria-current");
|
|
3828
|
+
});
|
|
3829
|
+
$("range").hidden = view === "install";
|
|
3830
|
+
if (view === "install") {
|
|
3831
|
+
var id = hash.slice(8);
|
|
3832
|
+
if (!install || install.install !== id) {
|
|
3833
|
+
install = null;
|
|
3834
|
+
renderInstall();
|
|
3835
|
+
get("api/install?id=" + encodeURIComponent(id)).then(function (data) { install = data || { install: id, missing: true }; renderInstall(); });
|
|
3836
|
+
} else renderInstall();
|
|
3837
|
+
} else render();
|
|
3634
3838
|
}
|
|
3635
|
-
fetch(base + "api/npm").then(function (r) { return r.json(); }).then(function (n) { npm = n; render(); });
|
|
3636
3839
|
|
|
3637
|
-
|
|
3840
|
+
function render() {
|
|
3841
|
+
if (!summary) return;
|
|
3842
|
+
$("updated").textContent = "updated " + new Date(summary.generatedAt).toLocaleTimeString();
|
|
3843
|
+
var hash = location.hash.replace("#", "") || "overview";
|
|
3844
|
+
if (hash === "users") renderUsers();
|
|
3845
|
+
else if (hash === "events") renderEvents();
|
|
3846
|
+
else if (hash.indexOf("install/") !== 0) renderOverview();
|
|
3847
|
+
}
|
|
3848
|
+
|
|
3849
|
+
function load() { get("api/summary?days=" + days).then(function (s) { if (s) { summary = s; render(); } }); }
|
|
3850
|
+
get("api/npm").then(function (n) { npm = n; if (summary) render(); });
|
|
3851
|
+
|
|
3852
|
+
$("range").addEventListener("click", function (event) {
|
|
3638
3853
|
var button = event.target.closest("button");
|
|
3639
3854
|
if (!button) return;
|
|
3640
3855
|
days = Number(button.dataset.days);
|
|
3641
3856
|
document.querySelectorAll("#range button").forEach(function (b) { b.setAttribute("aria-pressed", String(b === button)); });
|
|
3642
3857
|
load();
|
|
3643
3858
|
});
|
|
3644
|
-
|
|
3859
|
+
$("userSearch").addEventListener("input", function () { if (summary) renderUsers(); });
|
|
3860
|
+
document.addEventListener("click", function (event) {
|
|
3861
|
+
var sort = event.target.closest("[data-sort]");
|
|
3862
|
+
if (sort) {
|
|
3863
|
+
var key = sort.dataset.sort;
|
|
3864
|
+
userSort = { key: key, dir: userSort.key === key ? -userSort.dir : -1 };
|
|
3865
|
+
renderUsers();
|
|
3866
|
+
return;
|
|
3867
|
+
}
|
|
3868
|
+
var row = event.target.closest("tr[data-install]");
|
|
3869
|
+
if (row) { location.hash = "install/" + row.dataset.install; return; }
|
|
3870
|
+
var session = event.target.closest("[data-session]");
|
|
3871
|
+
if (session) {
|
|
3872
|
+
selectedSession = session.dataset.session;
|
|
3873
|
+
document.querySelectorAll("#sessionList .session").forEach(function (b) { b.setAttribute("aria-pressed", String(b === session)); });
|
|
3874
|
+
renderWaterfall();
|
|
3875
|
+
return;
|
|
3876
|
+
}
|
|
3877
|
+
var kind = event.target.closest("[data-kind]");
|
|
3878
|
+
if (kind) { kindShown = kind.dataset.kind; renderEvents(); }
|
|
3879
|
+
});
|
|
3880
|
+
document.addEventListener("keydown", function (event) {
|
|
3881
|
+
var row = event.target.closest && event.target.closest("tr[data-install]");
|
|
3882
|
+
if (row && (event.key === "Enter" || event.key === " ")) { event.preventDefault(); location.hash = "install/" + row.dataset.install; }
|
|
3883
|
+
});
|
|
3884
|
+
$("csv").addEventListener("click", function () {
|
|
3645
3885
|
if (!summary) return;
|
|
3646
|
-
var cols =
|
|
3647
|
-
var lines = [cols.join(",")].concat(
|
|
3886
|
+
var cols = USER_COLS.map(function (c) { return c[0]; });
|
|
3887
|
+
var lines = [cols.join(",")].concat(filteredUsers().map(function (u) {
|
|
3648
3888
|
return cols.map(function (c) { return '"' + String(u[c]).replace(/"/g, '""') + '"'; }).join(",");
|
|
3649
3889
|
}));
|
|
3650
3890
|
var link = document.createElement("a");
|
|
3651
|
-
link.href = URL.createObjectURL(new Blob([lines.join(
|
|
3891
|
+
link.href = URL.createObjectURL(new Blob([lines.join(NL)], { type: "text/csv" }));
|
|
3652
3892
|
link.download = "codebuddy-users-" + new Date().toISOString().slice(0, 10) + ".csv";
|
|
3653
3893
|
link.click();
|
|
3654
3894
|
});
|
|
@@ -3657,10 +3897,16 @@ td.n, th.n { text-align: right; font-family: var(--mono); font-variant-numeric:
|
|
|
3657
3897
|
if (!text) { tip.style.display = "none"; return; }
|
|
3658
3898
|
tip.textContent = text;
|
|
3659
3899
|
tip.style.display = "block";
|
|
3660
|
-
|
|
3661
|
-
tip.style.
|
|
3662
|
-
|
|
3900
|
+
tip.style.left = Math.min(event.pageX + 12, document.documentElement.scrollWidth - tip.offsetWidth - 8) + "px";
|
|
3901
|
+
tip.style.top = (event.pageY - 34) + "px";
|
|
3902
|
+
});
|
|
3903
|
+
var resizeTimer = 0;
|
|
3904
|
+
window.addEventListener("resize", function () {
|
|
3905
|
+
clearTimeout(resizeTimer);
|
|
3906
|
+
resizeTimer = setTimeout(function () { if (location.hash.indexOf("#install/") === 0) renderInstall(); else render(); }, 150);
|
|
3663
3907
|
});
|
|
3908
|
+
window.addEventListener("hashchange", route);
|
|
3909
|
+
route();
|
|
3664
3910
|
load();
|
|
3665
3911
|
setInterval(load, 60000);
|
|
3666
3912
|
})();
|
|
@@ -3700,6 +3946,7 @@ var batchSchema = z2.object({
|
|
|
3700
3946
|
async function startCollector(options) {
|
|
3701
3947
|
const { dataDir } = options;
|
|
3702
3948
|
await mkdir2(dataDir, { recursive: true });
|
|
3949
|
+
const registry = await loadRegistry(dataDir);
|
|
3703
3950
|
let summaryCache;
|
|
3704
3951
|
let npmCache;
|
|
3705
3952
|
const perInstall = /* @__PURE__ */ new Map();
|
|
@@ -3743,6 +3990,14 @@ async function startCollector(options) {
|
|
|
3743
3990
|
return;
|
|
3744
3991
|
}
|
|
3745
3992
|
const receivedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
3993
|
+
if (!registry.has(batch.installId)) {
|
|
3994
|
+
registry.set(batch.installId, receivedAt);
|
|
3995
|
+
await appendFile(
|
|
3996
|
+
join6(dataDir, "installs.jsonl"),
|
|
3997
|
+
`${JSON.stringify({ installId: batch.installId, firstSeen: receivedAt })}
|
|
3998
|
+
`
|
|
3999
|
+
);
|
|
4000
|
+
}
|
|
3746
4001
|
const { events, ...meta } = batch;
|
|
3747
4002
|
const lines = events.map((event) => JSON.stringify({ ...meta, ...event, receivedAt }));
|
|
3748
4003
|
await appendFile(
|
|
@@ -3790,12 +4045,29 @@ async function startCollector(options) {
|
|
|
3790
4045
|
summaryCache = {
|
|
3791
4046
|
at: Date.now(),
|
|
3792
4047
|
days,
|
|
3793
|
-
value: summarize(
|
|
4048
|
+
value: summarize(
|
|
4049
|
+
await readEvents(dataDir, Math.max(days, COHORT_WEEKS * 7)),
|
|
4050
|
+
days,
|
|
4051
|
+
Date.now(),
|
|
4052
|
+
registry
|
|
4053
|
+
)
|
|
3794
4054
|
};
|
|
3795
4055
|
}
|
|
3796
4056
|
json2(summaryCache.value);
|
|
3797
4057
|
return;
|
|
3798
4058
|
}
|
|
4059
|
+
if (url.pathname === "/api/install") {
|
|
4060
|
+
const trace = installTrace(
|
|
4061
|
+
await readEvents(dataDir, 90),
|
|
4062
|
+
(url.searchParams.get("id") ?? "").toLowerCase()
|
|
4063
|
+
);
|
|
4064
|
+
if (!trace) {
|
|
4065
|
+
response.writeHead(404, { "content-type": "application/json" }).end("null");
|
|
4066
|
+
return;
|
|
4067
|
+
}
|
|
4068
|
+
json2(trace);
|
|
4069
|
+
return;
|
|
4070
|
+
}
|
|
3799
4071
|
if (url.pathname === "/api/npm") {
|
|
3800
4072
|
if (!npmCache || Date.now() - npmCache.at > 36e5) {
|
|
3801
4073
|
npmCache = { at: Date.now(), value: await npmDownloads(options.fetchImpl ?? fetch) };
|
|
@@ -3863,64 +4135,125 @@ async function readEvents(dataDir, days, now = Date.now()) {
|
|
|
3863
4135
|
}
|
|
3864
4136
|
return events;
|
|
3865
4137
|
}
|
|
3866
|
-
|
|
3867
|
-
|
|
3868
|
-
|
|
3869
|
-
|
|
3870
|
-
|
|
3871
|
-
|
|
4138
|
+
var DAY_MS = 864e5;
|
|
4139
|
+
var dayOf = (iso) => iso.slice(0, 10);
|
|
4140
|
+
var dayNumber = (iso) => Math.floor(Date.parse(iso) / DAY_MS);
|
|
4141
|
+
var COHORT_WEEKS = 8;
|
|
4142
|
+
function featureOf(event) {
|
|
4143
|
+
if (event.kind === "tool") return event.name;
|
|
4144
|
+
if (event.kind === "cli") return `cli:${event.name}`;
|
|
4145
|
+
return event.kind;
|
|
4146
|
+
}
|
|
4147
|
+
var GOT_CONTEXT = (e) => e.kind === "recall" || e.kind === "tool" && /^(context_(pack|bootstrap|before_edit)|recall)$/.test(e.name);
|
|
4148
|
+
var SAVED_MEMORY = (e) => e.kind === "capture" || e.kind === "tool" && /^(context_after_turn|remember)$/.test(e.name);
|
|
4149
|
+
function summarize(events, days, now = Date.now(), registry = /* @__PURE__ */ new Map()) {
|
|
4150
|
+
const today = Math.floor(now / DAY_MS);
|
|
4151
|
+
const rangeStart = today - days + 1;
|
|
4152
|
+
const firstSeenOf = (install, fallback) => registry.get(install) ?? fallback;
|
|
4153
|
+
const activeDays = /* @__PURE__ */ new Map();
|
|
4154
|
+
for (const event of events) {
|
|
4155
|
+
const set = activeDays.get(event.installId) ?? /* @__PURE__ */ new Set();
|
|
4156
|
+
set.add(dayNumber(event.receivedAt));
|
|
4157
|
+
activeDays.set(event.installId, set);
|
|
4158
|
+
}
|
|
4159
|
+
const activeBetween = (from, to) => {
|
|
4160
|
+
let count = 0;
|
|
4161
|
+
for (const set of activeDays.values()) {
|
|
4162
|
+
for (const d of set) {
|
|
4163
|
+
if (d >= from && d <= to) {
|
|
4164
|
+
count += 1;
|
|
4165
|
+
break;
|
|
4166
|
+
}
|
|
4167
|
+
}
|
|
4168
|
+
}
|
|
4169
|
+
return count;
|
|
3872
4170
|
};
|
|
4171
|
+
const inRange = events.filter((e) => dayNumber(e.receivedAt) >= rangeStart);
|
|
3873
4172
|
const daily = /* @__PURE__ */ new Map();
|
|
3874
|
-
for (let
|
|
3875
|
-
|
|
3876
|
-
installs: /* @__PURE__ */ new Set(),
|
|
3877
|
-
events: 0
|
|
3878
|
-
});
|
|
3879
|
-
}
|
|
3880
|
-
const firstSeen = /* @__PURE__ */ new Map();
|
|
3881
|
-
const tools = /* @__PURE__ */ new Map();
|
|
3882
|
-
const kinds = /* @__PURE__ */ new Map();
|
|
4173
|
+
for (let d = rangeStart; d <= today; d += 1) daily.set(d, { installs: /* @__PURE__ */ new Set(), events: 0 });
|
|
4174
|
+
const features = /* @__PURE__ */ new Map();
|
|
3883
4175
|
const latest = /* @__PURE__ */ new Map();
|
|
3884
4176
|
const users = /* @__PURE__ */ new Map();
|
|
4177
|
+
const funnel = {
|
|
4178
|
+
active: /* @__PURE__ */ new Set(),
|
|
4179
|
+
context: /* @__PURE__ */ new Set(),
|
|
4180
|
+
memory: /* @__PURE__ */ new Set()
|
|
4181
|
+
};
|
|
3885
4182
|
let errors = 0;
|
|
3886
4183
|
let tokensOut = 0;
|
|
3887
|
-
for (const event of
|
|
3888
|
-
const
|
|
4184
|
+
for (const event of inRange) {
|
|
4185
|
+
const d = dayNumber(event.receivedAt);
|
|
4186
|
+
const bucket = daily.get(d);
|
|
3889
4187
|
if (bucket) {
|
|
3890
4188
|
bucket.installs.add(event.installId);
|
|
3891
4189
|
bucket.events += 1;
|
|
3892
4190
|
}
|
|
3893
|
-
const seen = firstSeen.get(event.installId);
|
|
3894
|
-
if (!seen || event.receivedAt < seen) firstSeen.set(event.installId, event.receivedAt);
|
|
3895
4191
|
latest.set(event.installId, event);
|
|
4192
|
+
const feature = featureOf(event);
|
|
3896
4193
|
const user = users.get(event.installId) ?? {
|
|
4194
|
+
firstEvent: event.receivedAt,
|
|
3897
4195
|
lastSeen: event.receivedAt,
|
|
3898
4196
|
events: 0,
|
|
4197
|
+
errors: 0,
|
|
3899
4198
|
sessions: /* @__PURE__ */ new Set(),
|
|
3900
4199
|
projects: /* @__PURE__ */ new Set(),
|
|
3901
|
-
features: /* @__PURE__ */ new Map()
|
|
4200
|
+
features: /* @__PURE__ */ new Map(),
|
|
4201
|
+
days: /* @__PURE__ */ new Set()
|
|
3902
4202
|
};
|
|
3903
4203
|
if (event.receivedAt > user.lastSeen) user.lastSeen = event.receivedAt;
|
|
3904
4204
|
user.events += 1;
|
|
4205
|
+
if (!event.ok) user.errors += 1;
|
|
3905
4206
|
if (event.session) user.sessions.add(event.session);
|
|
3906
4207
|
user.projects.add(event.project);
|
|
3907
|
-
const feature = event.kind === "tool" ? event.name : event.kind;
|
|
3908
4208
|
user.features.set(feature, (user.features.get(feature) ?? 0) + 1);
|
|
4209
|
+
user.days.add(d);
|
|
3909
4210
|
users.set(event.installId, user);
|
|
3910
|
-
|
|
4211
|
+
funnel.active.add(event.installId);
|
|
4212
|
+
if (GOT_CONTEXT(event)) funnel.context.add(event.installId);
|
|
4213
|
+
if (SAVED_MEMORY(event)) funnel.memory.add(event.installId);
|
|
3911
4214
|
if (!event.ok) errors += 1;
|
|
3912
4215
|
tokensOut += event.tokensOut ?? 0;
|
|
3913
|
-
const
|
|
3914
|
-
|
|
3915
|
-
|
|
3916
|
-
|
|
3917
|
-
|
|
3918
|
-
|
|
3919
|
-
|
|
4216
|
+
const stats = features.get(feature) ?? { calls: 0, errors: 0, installs: /* @__PURE__ */ new Set(), ms: [] };
|
|
4217
|
+
stats.calls += 1;
|
|
4218
|
+
if (!event.ok) stats.errors += 1;
|
|
4219
|
+
stats.installs.add(event.installId);
|
|
4220
|
+
stats.ms.push(event.ms);
|
|
4221
|
+
features.set(feature, stats);
|
|
4222
|
+
}
|
|
4223
|
+
const firstSeenDay = /* @__PURE__ */ new Map();
|
|
4224
|
+
for (const [install, seen] of registry) firstSeenDay.set(install, dayNumber(seen));
|
|
4225
|
+
for (const [install, user] of users) {
|
|
4226
|
+
if (!firstSeenDay.has(install)) firstSeenDay.set(install, dayNumber(user.firstEvent));
|
|
4227
|
+
}
|
|
4228
|
+
for (const [install, set] of activeDays) {
|
|
4229
|
+
if (!firstSeenDay.has(install)) firstSeenDay.set(install, Math.min(...set));
|
|
3920
4230
|
}
|
|
3921
4231
|
const newPerDay = /* @__PURE__ */ new Map();
|
|
3922
|
-
for (const
|
|
3923
|
-
|
|
4232
|
+
for (const d of firstSeenDay.values()) newPerDay.set(d, (newPerDay.get(d) ?? 0) + 1);
|
|
4233
|
+
let cumulative = [...firstSeenDay.values()].filter((d) => d < rangeStart).length;
|
|
4234
|
+
const cohorts = [];
|
|
4235
|
+
for (let age = COHORT_WEEKS - 1; age >= 0; age -= 1) {
|
|
4236
|
+
const from = today - (age + 1) * 7 + 1;
|
|
4237
|
+
const members = [...firstSeenDay].filter(([, d]) => d >= from && d < from + 7).map(([install]) => install);
|
|
4238
|
+
const weeks = [];
|
|
4239
|
+
for (let week = 0; week <= age; week += 1) {
|
|
4240
|
+
const wFrom = from + week * 7;
|
|
4241
|
+
const active = members.filter((install) => {
|
|
4242
|
+
const set = activeDays.get(install);
|
|
4243
|
+
if (!set) return week === 0;
|
|
4244
|
+
for (const d of set) if (d >= wFrom && d < wFrom + 7) return true;
|
|
4245
|
+
return false;
|
|
4246
|
+
}).length;
|
|
4247
|
+
weeks.push(members.length ? active / members.length : 0);
|
|
4248
|
+
}
|
|
4249
|
+
cohorts.push({
|
|
4250
|
+
start: new Date(from * DAY_MS).toISOString().slice(0, 10),
|
|
4251
|
+
size: members.length,
|
|
4252
|
+
weeks
|
|
4253
|
+
});
|
|
4254
|
+
}
|
|
4255
|
+
const week1 = cohorts.filter((c) => c.size > 0 && c.weeks.length > 1);
|
|
4256
|
+
const week1Retention = week1.length ? week1.reduce((sum, c) => sum + (c.weeks[1] ?? 0) * c.size, 0) / week1.reduce((sum, c) => sum + c.size, 0) : null;
|
|
3924
4257
|
const share = (pick) => {
|
|
3925
4258
|
const counts = /* @__PURE__ */ new Map();
|
|
3926
4259
|
for (const event of latest.values()) {
|
|
@@ -3932,47 +4265,72 @@ function summarize(events, days, now = Date.now()) {
|
|
|
3932
4265
|
const sorted = [...values].sort((a, b) => a - b);
|
|
3933
4266
|
return sorted[Math.floor(sorted.length / 2)] ?? 0;
|
|
3934
4267
|
};
|
|
4268
|
+
const p95 = (values) => {
|
|
4269
|
+
const sorted = [...values].sort((a, b) => a - b);
|
|
4270
|
+
return sorted[Math.min(sorted.length - 1, Math.floor(sorted.length * 0.95))] ?? 0;
|
|
4271
|
+
};
|
|
4272
|
+
const wau = activeBetween(today - 6, today);
|
|
4273
|
+
const wauPrevious = activeBetween(today - 13, today - 7);
|
|
4274
|
+
const returned = [...users.values()].filter((user) => user.days.size >= 2).length;
|
|
3935
4275
|
return {
|
|
3936
4276
|
generatedAt: new Date(now).toISOString(),
|
|
3937
4277
|
days,
|
|
3938
4278
|
totals: {
|
|
3939
|
-
|
|
3940
|
-
|
|
3941
|
-
|
|
3942
|
-
|
|
3943
|
-
|
|
4279
|
+
allTimeInstalls: firstSeenDay.size,
|
|
4280
|
+
installs: users.size,
|
|
4281
|
+
newInRange: [...firstSeenDay.values()].filter((d) => d >= rangeStart).length,
|
|
4282
|
+
newToday: newPerDay.get(today) ?? 0,
|
|
4283
|
+
projects: new Set(inRange.map((e) => `${e.installId}:${e.project}`)).size,
|
|
4284
|
+
sessions: new Set(inRange.filter((e) => e.session).map((e) => e.session)).size,
|
|
4285
|
+
events: inRange.length,
|
|
4286
|
+
errorRate: inRange.length ? errors / inRange.length : 0,
|
|
3944
4287
|
tokensOut,
|
|
3945
|
-
|
|
3946
|
-
|
|
3947
|
-
|
|
3948
|
-
|
|
4288
|
+
dau: activeBetween(today, today),
|
|
4289
|
+
wau,
|
|
4290
|
+
wauPrevious,
|
|
4291
|
+
mau: activeBetween(today - 29, today),
|
|
4292
|
+
week1Retention
|
|
3949
4293
|
},
|
|
3950
|
-
daily: [...daily].map(([
|
|
3951
|
-
|
|
3952
|
-
|
|
3953
|
-
|
|
3954
|
-
|
|
3955
|
-
|
|
3956
|
-
|
|
4294
|
+
daily: [...daily].map(([d, bucket]) => {
|
|
4295
|
+
cumulative += newPerDay.get(d) ?? 0;
|
|
4296
|
+
return {
|
|
4297
|
+
date: new Date(d * DAY_MS).toISOString().slice(0, 10),
|
|
4298
|
+
installs: bucket.installs.size,
|
|
4299
|
+
events: bucket.events,
|
|
4300
|
+
newInstalls: newPerDay.get(d) ?? 0,
|
|
4301
|
+
totalInstalls: cumulative
|
|
4302
|
+
};
|
|
4303
|
+
}),
|
|
4304
|
+
/** Installs active in the range that reached each step. */
|
|
4305
|
+
funnel: [
|
|
4306
|
+
{ label: "Active", installs: funnel.active.size },
|
|
4307
|
+
{ label: "Received context", installs: funnel.context.size },
|
|
4308
|
+
{ label: "Saved memory", installs: funnel.memory.size },
|
|
4309
|
+
{ label: "Came back another day", installs: returned }
|
|
4310
|
+
],
|
|
4311
|
+
cohorts,
|
|
4312
|
+
features: [...features].map(([label, stats]) => ({
|
|
3957
4313
|
label,
|
|
3958
|
-
calls:
|
|
3959
|
-
installs:
|
|
3960
|
-
errorRate:
|
|
3961
|
-
p50ms: median(
|
|
3962
|
-
|
|
3963
|
-
|
|
4314
|
+
calls: stats.calls,
|
|
4315
|
+
installs: stats.installs.size,
|
|
4316
|
+
errorRate: stats.errors / stats.calls,
|
|
4317
|
+
p50ms: median(stats.ms),
|
|
4318
|
+
p95ms: p95(stats.ms)
|
|
4319
|
+
})).sort((a, b) => b.calls - a.calls).slice(0, 25),
|
|
3964
4320
|
clients: share((e) => e.clients.length ? e.clients : ["unknown"]),
|
|
3965
4321
|
versions: share((e) => [e.version]),
|
|
3966
4322
|
platforms: share((e) => [`${e.platform}/${e.arch}`]),
|
|
3967
4323
|
nodes: share((e) => [`node ${e.node}`]),
|
|
3968
|
-
/** One row per install, most recently active first. */
|
|
4324
|
+
/** One row per install active in the range, most recently active first. */
|
|
3969
4325
|
users: [...users].map(([installId, user]) => {
|
|
3970
4326
|
const last = latest.get(installId);
|
|
3971
4327
|
return {
|
|
3972
4328
|
install: installId.slice(0, 8),
|
|
3973
|
-
firstSeen:
|
|
4329
|
+
firstSeen: firstSeenOf(installId, user.firstEvent),
|
|
3974
4330
|
lastSeen: user.lastSeen,
|
|
4331
|
+
activeDays: user.days.size,
|
|
3975
4332
|
events: user.events,
|
|
4333
|
+
errors: user.errors,
|
|
3976
4334
|
sessions: user.sessions.size,
|
|
3977
4335
|
projects: user.projects.size,
|
|
3978
4336
|
client: last?.clients.join(",") || "unknown",
|
|
@@ -3981,7 +4339,7 @@ function summarize(events, days, now = Date.now()) {
|
|
|
3981
4339
|
topFeature: [...user.features].sort((a, b) => b[1] - a[1])[0]?.[0] ?? ""
|
|
3982
4340
|
};
|
|
3983
4341
|
}).sort((a, b) => b.lastSeen.localeCompare(a.lastSeen)).slice(0, 1e3),
|
|
3984
|
-
recent:
|
|
4342
|
+
recent: inRange.slice(-50).reverse().map((e) => ({
|
|
3985
4343
|
receivedAt: e.receivedAt,
|
|
3986
4344
|
install: e.installId.slice(0, 8),
|
|
3987
4345
|
kind: e.kind,
|
|
@@ -3993,6 +4351,77 @@ function summarize(events, days, now = Date.now()) {
|
|
|
3993
4351
|
}))
|
|
3994
4352
|
};
|
|
3995
4353
|
}
|
|
4354
|
+
function installTrace(events, prefix) {
|
|
4355
|
+
const own = events.filter((e) => e.installId.startsWith(prefix));
|
|
4356
|
+
const first = own[0];
|
|
4357
|
+
if (!first || !/^[0-9a-f]{8}$/.test(prefix)) return null;
|
|
4358
|
+
const sessions = /* @__PURE__ */ new Map();
|
|
4359
|
+
const perDay = /* @__PURE__ */ new Map();
|
|
4360
|
+
for (const event of own) {
|
|
4361
|
+
perDay.set(dayOf(event.receivedAt), (perDay.get(dayOf(event.receivedAt)) ?? 0) + 1);
|
|
4362
|
+
const key = event.session ?? "none";
|
|
4363
|
+
const session = sessions.get(key) ?? {
|
|
4364
|
+
start: event.ts,
|
|
4365
|
+
end: event.ts,
|
|
4366
|
+
events: 0,
|
|
4367
|
+
errors: 0,
|
|
4368
|
+
ms: 0,
|
|
4369
|
+
features: /* @__PURE__ */ new Set()
|
|
4370
|
+
};
|
|
4371
|
+
if (event.ts < session.start) session.start = event.ts;
|
|
4372
|
+
if (event.ts > session.end) session.end = event.ts;
|
|
4373
|
+
session.events += 1;
|
|
4374
|
+
if (!event.ok) session.errors += 1;
|
|
4375
|
+
session.ms += event.ms;
|
|
4376
|
+
session.features.add(featureOf(event));
|
|
4377
|
+
sessions.set(key, session);
|
|
4378
|
+
}
|
|
4379
|
+
const last = own[own.length - 1] ?? first;
|
|
4380
|
+
return {
|
|
4381
|
+
install: prefix,
|
|
4382
|
+
client: last.clients.join(",") || "unknown",
|
|
4383
|
+
version: last.version,
|
|
4384
|
+
platform: `${last.platform}/${last.arch}`,
|
|
4385
|
+
node: last.node,
|
|
4386
|
+
projects: new Set(own.map((e) => e.project)).size,
|
|
4387
|
+
perDay: [...perDay].map(([date, count]) => ({ date, events: count })),
|
|
4388
|
+
sessions: [...sessions].map(([id, session]) => ({ id, ...session, features: [...session.features] })).sort((a, b) => b.end.localeCompare(a.end)).slice(0, 50),
|
|
4389
|
+
events: own.slice(-1e3).map((e) => ({
|
|
4390
|
+
ts: e.ts,
|
|
4391
|
+
kind: e.kind,
|
|
4392
|
+
name: e.name,
|
|
4393
|
+
feature: featureOf(e),
|
|
4394
|
+
ms: e.ms,
|
|
4395
|
+
ok: e.ok,
|
|
4396
|
+
session: e.session ?? "none",
|
|
4397
|
+
tokensOut: e.tokensOut ?? 0
|
|
4398
|
+
}))
|
|
4399
|
+
};
|
|
4400
|
+
}
|
|
4401
|
+
async function loadRegistry(dataDir) {
|
|
4402
|
+
const registry = /* @__PURE__ */ new Map();
|
|
4403
|
+
const path = join6(dataDir, "installs.jsonl");
|
|
4404
|
+
const raw = await readFile4(path, "utf8").catch(() => null);
|
|
4405
|
+
if (raw !== null) {
|
|
4406
|
+
for (const line of raw.split("\n")) {
|
|
4407
|
+
try {
|
|
4408
|
+
const entry = JSON.parse(line);
|
|
4409
|
+
if (!registry.has(entry.installId)) registry.set(entry.installId, entry.firstSeen);
|
|
4410
|
+
} catch {
|
|
4411
|
+
}
|
|
4412
|
+
}
|
|
4413
|
+
return registry;
|
|
4414
|
+
}
|
|
4415
|
+
for (const event of await readEvents(dataDir, 3650)) {
|
|
4416
|
+
if (!registry.has(event.installId)) registry.set(event.installId, event.receivedAt);
|
|
4417
|
+
}
|
|
4418
|
+
const lines = [...registry].map(
|
|
4419
|
+
([installId, firstSeen]) => JSON.stringify({ installId, firstSeen })
|
|
4420
|
+
);
|
|
4421
|
+
await appendFile(path, lines.length ? `${lines.join("\n")}
|
|
4422
|
+
` : "");
|
|
4423
|
+
return registry;
|
|
4424
|
+
}
|
|
3996
4425
|
async function npmDownloads(fetchImpl) {
|
|
3997
4426
|
const to = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
|
|
3998
4427
|
const from = new Date(Date.now() - 364 * 864e5).toISOString().slice(0, 10);
|
|
@@ -4810,7 +5239,12 @@ function createCli() {
|
|
|
4810
5239
|
registerGitHubCommands(program, runSafely);
|
|
4811
5240
|
registerSymbolsCommand(program, runSafely);
|
|
4812
5241
|
registerTelemetryCommands(program, runSafely);
|
|
4813
|
-
|
|
5242
|
+
let started = Date.now();
|
|
5243
|
+
program.hook("preAction", () => {
|
|
5244
|
+
started = Date.now();
|
|
5245
|
+
return showTelemetryNoticeOnce();
|
|
5246
|
+
});
|
|
5247
|
+
program.hook("postAction", (_program, command) => traceCliCommand(command, started));
|
|
4814
5248
|
program.action(async () => {
|
|
4815
5249
|
await runSafely(() => runUseCommand());
|
|
4816
5250
|
});
|
|
@@ -4951,4 +5385,4 @@ function printDbDoctor(report) {
|
|
|
4951
5385
|
export {
|
|
4952
5386
|
createCli
|
|
4953
5387
|
};
|
|
4954
|
-
//# sourceMappingURL=main-
|
|
5388
|
+
//# sourceMappingURL=main-YJKSYFPT.js.map
|