@gamaze/hicortex 0.23.0 → 0.23.2
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/assets/dashboard.html +455 -153
- package/dist/dashboard.d.ts +18 -7
- package/dist/dashboard.js +42 -10
- package/dist/dedup.js +2 -2
- package/dist/index.js +17 -2
- package/dist/learnings-identity.js +20 -1
- package/dist/llm.d.ts +12 -1
- package/dist/llm.js +14 -3
- package/dist/mcp-server.js +6 -2
- package/dist/mcp-stdio.d.ts +61 -3
- package/dist/mcp-stdio.js +272 -51
- package/dist/nightly.js +1 -1
- package/hermes-plugin/hicortex/provider.py +23 -0
- package/opencode-plugin/hicortex/index.ts +24 -1
- package/package.json +2 -1
- package/pi-extension/hicortex/index.ts +24 -1
- package/server.json +2 -2
- package/dist/eval/decay-eval.d.ts +0 -111
- package/dist/eval/decay-eval.js +0 -214
- package/dist/eval/dups.d.ts +0 -100
- package/dist/eval/dups.js +0 -174
- package/dist/eval/eval-clock.d.ts +0 -32
- package/dist/eval/eval-clock.js +0 -47
- package/dist/eval/eval-db.d.ts +0 -25
- package/dist/eval/eval-db.js +0 -67
- package/dist/eval/graph-eval.d.ts +0 -89
- package/dist/eval/graph-eval.js +0 -246
- package/dist/eval/importance-eval.d.ts +0 -85
- package/dist/eval/importance-eval.js +0 -286
- package/dist/eval/planted-eval.d.ts +0 -30
- package/dist/eval/planted-eval.js +0 -122
- package/dist/eval/planted-fixtures.d.ts +0 -107
- package/dist/eval/planted-fixtures.js +0 -283
- package/dist/eval/planted-harness.d.ts +0 -183
- package/dist/eval/planted-harness.js +0 -651
- package/dist/eval/ranking-battery.d.ts +0 -125
- package/dist/eval/ranking-battery.js +0 -289
- package/dist/eval/ranking-eval.d.ts +0 -61
- package/dist/eval/ranking-eval.js +0 -554
- package/dist/eval/ranking-fixtures.d.ts +0 -117
- package/dist/eval/ranking-fixtures.js +0 -485
- package/dist/eval/recall-sweep.d.ts +0 -87
- package/dist/eval/recall-sweep.js +0 -1030
- package/dist/eval/reflection-census.d.ts +0 -19
- package/dist/eval/reflection-census.js +0 -25
- package/dist/eval/relevance-eval.d.ts +0 -178
- package/dist/eval/relevance-eval.js +0 -2240
- package/dist/eval/run-eval.d.ts +0 -20
- package/dist/eval/run-eval.js +0 -299
package/assets/dashboard.html
CHANGED
|
@@ -76,7 +76,7 @@
|
|
|
76
76
|
badged "best match" (server composite order kept — ranking is #425).
|
|
77
77
|
All-below-floor renders "no matches for '<q>'" — NN junk never shows.
|
|
78
78
|
- ONE-SCREEN LAYOUT: header + band + field fit the first viewport at
|
|
79
|
-
~1440×900 (
|
|
79
|
+
~1440×900 (reference resolution; 1280×800 verified). Band compacted; the
|
|
80
80
|
field takes the viewport remainder (clamp with a measured offset —
|
|
81
81
|
see the #brainwrap CSS note). Scroll stays available as overflow.
|
|
82
82
|
|
|
@@ -345,9 +345,8 @@
|
|
|
345
345
|
}
|
|
346
346
|
.acct-btn:hover{ border-color:var(--green-mid); color:var(--green-mid); }
|
|
347
347
|
.acct-btn:disabled{ opacity:0.5; cursor:default; }
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
.acct-menu summary:hover{ color:var(--green-mid); }
|
|
348
|
+
/* the acct-* snippet classes are shared by the account menu's token row
|
|
349
|
+
and the connect overlay's snippet blocks (#506 — one idiom, two hosts) */
|
|
351
350
|
.acct-snippet{ margin-bottom:8px; }
|
|
352
351
|
.acct-snippet pre{
|
|
353
352
|
background:var(--panel); border:1px solid var(--line2); border-radius:7px;
|
|
@@ -363,12 +362,40 @@
|
|
|
363
362
|
}
|
|
364
363
|
.acct-link:hover{ background:var(--panel); }
|
|
365
364
|
.acct-link.acct-danger:hover{ color:var(--red); }
|
|
366
|
-
/* the
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
.acct-link.acct-
|
|
365
|
+
/* the Subscription entry's upgrade pill (#371 round 3, owner: the menu's
|
|
366
|
+
two billing entries merge into ONE "Subscription" item — "it can have
|
|
367
|
+
an upgrade pill/label after it before any subscription is active").
|
|
368
|
+
The ENTRY always renders; the PILL is what the plan-unset eligibility
|
|
369
|
+
gates. Accent badge in the trigger's chip idiom. */
|
|
370
|
+
.acct-link .acct-pill{
|
|
371
|
+
margin-left:8px; font-size:9px; font-weight:640; letter-spacing:.08em; text-transform:uppercase;
|
|
372
|
+
color:var(--green-mid); border:1px solid color-mix(in srgb, var(--green) 35%, transparent);
|
|
373
|
+
background:color-mix(in srgb, var(--green) 8%, transparent);
|
|
374
|
+
border-radius:999px; padding:1px 7px; vertical-align:1px;
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
/* ============ billing-return banner (#371) — the one-line acknowledgment
|
|
378
|
+
for ?billing=success|cancelled (the URLs the router stamps on every
|
|
379
|
+
checkout). A page-level element under the header, deliberately NOT a
|
|
380
|
+
toast: the returning buyer must still find it when they look back at
|
|
381
|
+
the page. Shown once — the param is stripped on show, so neither
|
|
382
|
+
dismissal nor a plain reload can re-show it. Theme tokens only. */
|
|
383
|
+
#billing-banner{
|
|
384
|
+
display:flex; align-items:center; justify-content:center; gap:10px;
|
|
385
|
+
margin:8px 16px 0; padding:7px 12px; border-radius:10px;
|
|
386
|
+
border:1px solid var(--line2); background:var(--panel);
|
|
387
|
+
font-size:12px; color:var(--dim); flex:none;
|
|
388
|
+
}
|
|
389
|
+
/* author display beats the UA's [hidden]{display:none} — without the
|
|
390
|
+
override the "hidden" banner rendered an EMPTY strip under the header
|
|
391
|
+
on every plain load (found live in preview). The #model-modal idiom. */
|
|
392
|
+
#billing-banner[hidden]{ display:none; }
|
|
393
|
+
#billing-banner[data-kind="success"]{
|
|
394
|
+
color:var(--green-mid);
|
|
395
|
+
border-color:color-mix(in srgb, var(--green) 30%, var(--line2));
|
|
396
|
+
}
|
|
397
|
+
#billing-banner button{ color:var(--faint); font-size:11px; padding:2px 4px; }
|
|
398
|
+
#billing-banner button:hover{ color:var(--text); }
|
|
372
399
|
|
|
373
400
|
/* ============ the stats band — a WRAPPING CARD GRID ============
|
|
374
401
|
Cards come in TWO widths only — the row is a positional grid
|
|
@@ -503,8 +530,9 @@
|
|
|
503
530
|
|
|
504
531
|
.cap-of{ font-size:10.5px; color:var(--faint); font-variant-numeric:tabular-nums; }
|
|
505
532
|
.bseg.warn .csub{ color:var(--amber); }
|
|
506
|
-
/* Agents card: a faded amber Connect pill, bottom-right — opens the
|
|
507
|
-
connect
|
|
533
|
+
/* Agents card: a faded amber Connect pill, bottom-right — opens the
|
|
534
|
+
full-cover connect overlay (#506; the phase-1 rail drawer stand-in for
|
|
535
|
+
the seats CTA is retired: no cap exists) */
|
|
508
536
|
#lim-ag .limit-cta{
|
|
509
537
|
display:inline-block; align-self:flex-end;
|
|
510
538
|
color:var(--amber); border-color:color-mix(in srgb, var(--amber) 45%, transparent);
|
|
@@ -546,7 +574,7 @@
|
|
|
546
574
|
|
|
547
575
|
/* ---- the brain-field ---- */
|
|
548
576
|
/* ONE-SCREEN AT-REST LAYOUT (#409 polish, owner ruling 2026-09-13): header
|
|
549
|
-
+ stats band + field fit the first viewport at ~1440×900 (
|
|
577
|
+
+ stats band + field fit the first viewport at ~1440×900 (that is the
|
|
550
578
|
reference; 1280×800 verified too). The field takes the viewport REMAINDER
|
|
551
579
|
— the subtracted constant is the MEASURED header+band+paddings total on
|
|
552
580
|
the reference store (Chrome 152, headless): 412px at the 4-column width,
|
|
@@ -766,24 +794,6 @@
|
|
|
766
794
|
.ctx-detail .ctx-head{ display:flex; align-items:center; gap:8px; margin-bottom:4px; min-width:0; }
|
|
767
795
|
.ctx-detail .ctx-head b{ font-size:12px; white-space:nowrap; overflow:hidden; text-overflow:ellipsis; }
|
|
768
796
|
.ctx-detail .ctx-head small{ color:var(--faint); font-family:var(--mono); font-size:9px; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
|
|
769
|
-
#connect{ flex:none; border-top:1px solid var(--line); padding:8px; }
|
|
770
|
-
#connect-btn{
|
|
771
|
-
display:flex; align-items:center; gap:7px; width:100%; justify-content:center;
|
|
772
|
-
color:var(--green-mid); border:1px dashed color-mix(in srgb, var(--green) 35%, transparent); border-radius:10px;
|
|
773
|
-
padding:7px 10px; font-size:11.5px; transition:box-shadow .15s, background .15s;
|
|
774
|
-
}
|
|
775
|
-
html:not([data-theme="light"]) #connect-btn:hover{ box-shadow:0 0 12px rgba(61,255,122,.18); }
|
|
776
|
-
html[data-theme="light"] #connect-btn:hover{ background:color-mix(in srgb, var(--green) 5%, transparent); }
|
|
777
|
-
#connect-btn .plus{ font-size:14px; line-height:1; }
|
|
778
|
-
#connect-x{ display:none; margin-top:8px; background:var(--elev); border:1px solid var(--line2); border-radius:10px; padding:10px 11px; }
|
|
779
|
-
#connect-x.open{ display:block; }
|
|
780
|
-
#connect-x p{ font-size:10.5px; color:var(--dim); line-height:1.5; margin-bottom:7px; }
|
|
781
|
-
#connect-x .snip{
|
|
782
|
-
font-family:var(--mono); font-size:10px; color:var(--green-mid); background:var(--panel);
|
|
783
|
-
border:1px solid var(--line2); border-radius:7px; padding:7px 9px;
|
|
784
|
-
white-space:nowrap; overflow-x:auto; margin-bottom:7px;
|
|
785
|
-
}
|
|
786
|
-
#connect-x .row{ display:flex; justify-content:flex-end; gap:6px; }
|
|
787
797
|
|
|
788
798
|
/* ---- shared kv rows (band detail groups + memory detail internals) ---- */
|
|
789
799
|
.kv{ display:flex; justify-content:space-between; gap:12px; font-size:11.5px; padding:2.5px 0; min-width:0; }
|
|
@@ -975,9 +985,9 @@
|
|
|
975
985
|
/* toasts + tooltip */
|
|
976
986
|
#toasts{ position:fixed; left:50%; bottom:20px; transform:translateX(-50%); z-index:95; display:flex; flex-direction:column; gap:8px; align-items:center; pointer-events:none; }
|
|
977
987
|
|
|
978
|
-
/* model-settings modal (#422) — the #panel
|
|
979
|
-
|
|
980
|
-
|
|
988
|
+
/* model-settings modal (#422) — the #panel visual language as a centered
|
|
989
|
+
overlay; every color rides the theme vars (zero theme-specific rules,
|
|
990
|
+
both themes work) */
|
|
981
991
|
#model-modal{ position:fixed; inset:0; z-index:90; display:grid; place-items:center; }
|
|
982
992
|
#model-modal[hidden]{ display:none; }
|
|
983
993
|
#model-modal .mm-backdrop{ position:absolute; inset:0; background:rgba(0,0,0,.55); }
|
|
@@ -994,6 +1004,61 @@
|
|
|
994
1004
|
.mm-check small{ color:var(--faint); }
|
|
995
1005
|
#model-modal .mm-note{ padding:4px 14px 8px; font-size:10px; color:var(--faint); font-style:italic; }
|
|
996
1006
|
#model-modal .mm-foot{ display:flex; justify-content:flex-end; gap:7px; padding:10px 14px; border-top:1px solid var(--line); }
|
|
1007
|
+
|
|
1008
|
+
/* plan & billing overlay (#371, owner amendment 2 2026-09-23: the under-
|
|
1009
|
+
graph section was rejected — the upgrade surface must COVER the dash).
|
|
1010
|
+
The #model-modal idiom, widened for two side-by-side option cards; the
|
|
1011
|
+
cover is OPAQUE (var(--bg), not a scrim) — the dash is fully hidden
|
|
1012
|
+
behind it, not dimmed-partial. The [hidden] override is mandatory
|
|
1013
|
+
(author display beats the UA rule — the banner lesson, pinned by tests). */
|
|
1014
|
+
#plan-overlay{ position:fixed; inset:0; z-index:92; display:grid; place-items:center; }
|
|
1015
|
+
#plan-overlay[hidden]{ display:none; }
|
|
1016
|
+
#plan-overlay .po-cover{ position:absolute; inset:0; background:var(--bg); }
|
|
1017
|
+
#plan-overlay .po-card{
|
|
1018
|
+
position:relative; width:min(860px, calc(100vw - 32px)); max-height:calc(100vh - 48px);
|
|
1019
|
+
overflow-y:auto; background:var(--elev); border:1px solid var(--line2); border-radius:14px;
|
|
1020
|
+
box-shadow:var(--shadow-pop);
|
|
1021
|
+
}
|
|
1022
|
+
#plan-overlay .po-head{ display:flex; align-items:center; justify-content:space-between; padding:14px 16px 10px; border-bottom:1px solid var(--line); }
|
|
1023
|
+
#plan-overlay .po-head b{ font-family:var(--serif); font-weight:500; font-size:17px; letter-spacing:.06em; text-transform:uppercase; }
|
|
1024
|
+
#plan-overlay .po-body{ padding:14px 16px 16px; display:flex; flex-direction:column; gap:10px; }
|
|
1025
|
+
#plan-overlay .po-current{ font-size:12.5px; color:var(--dim); }
|
|
1026
|
+
.po-opts{ display:grid; grid-template-columns:1fr 1fr; gap:12px; }
|
|
1027
|
+
#acct-options[hidden]{ display:none; } /* author grid beats [hidden] (renderPlanOverlay hides it when a plan is set) */
|
|
1028
|
+
.po-opt{ background:var(--panel); border:1px solid var(--line2); border-radius:12px; padding:14px 16px 13px; display:flex; flex-direction:column; gap:5px; min-width:0; }
|
|
1029
|
+
.po-opt .po-name{ font-size:10px; color:var(--faint); text-transform:uppercase; letter-spacing:.13em; }
|
|
1030
|
+
.po-opt .po-price{ font-size:26px; font-weight:640; font-variant-numeric:tabular-nums; letter-spacing:.01em; color:var(--green); }
|
|
1031
|
+
html:not([data-theme="light"]) .po-opt .po-price{ text-shadow:0 0 12px rgba(61,255,122,.35); }
|
|
1032
|
+
.po-opt .po-note{ font-size:11px; color:var(--dim); }
|
|
1033
|
+
/* what's included (round 3, owner: "NO info on what is included in the
|
|
1034
|
+
plan") — the marketing site's Cloud feature lines, verbatim, per card. */
|
|
1035
|
+
.po-opt .po-feat{ list-style:none; margin-top:7px; display:flex; flex-direction:column; gap:3px; }
|
|
1036
|
+
.po-opt .po-feat li{ font-size:11px; color:var(--dim); display:flex; gap:7px; align-items:baseline; }
|
|
1037
|
+
.po-opt .po-feat li::before{ content:"✓"; color:var(--green-mid); font-size:10px; }
|
|
1038
|
+
.po-choose{
|
|
1039
|
+
align-self:flex-start; margin-top:8px;
|
|
1040
|
+
color:var(--green-mid); border:1px solid color-mix(in srgb, var(--green) 35%, transparent);
|
|
1041
|
+
border-radius:999px; padding:4px 15px; font-size:11.5px; font-weight:600; white-space:nowrap;
|
|
1042
|
+
}
|
|
1043
|
+
.po-choose:hover{ border-color:var(--green-mid); }
|
|
1044
|
+
.po-choose:disabled{ opacity:.5; cursor:default; }
|
|
1045
|
+
@media (max-width:720px){ .po-opts{ grid-template-columns:1fr; } }
|
|
1046
|
+
|
|
1047
|
+
/* connect overlay (#506) — ONE full-cover surface for every "Connect an
|
|
1048
|
+
agent" entry (the #plan-overlay idiom, owner 2026-09-24: the fleet
|
|
1049
|
+
rail's inline drawer is REMOVED, not kept; the account-menu entry and
|
|
1050
|
+
the Agents-detected pill both open THIS). The cover is OPAQUE
|
|
1051
|
+
(var(--bg), not a scrim) and the [hidden] override is MANDATORY
|
|
1052
|
+
(author display beats the UA rule — the banner lesson, pinned by
|
|
1053
|
+
tests). The snippet blocks reuse the acct-* classes. */
|
|
1054
|
+
#connect-overlay{ position:fixed; inset:0; z-index:92; display:grid; place-items:center; }
|
|
1055
|
+
#connect-overlay[hidden]{ display:none; }
|
|
1056
|
+
#connect-overlay .co-cover{ position:absolute; inset:0; background:var(--bg); }
|
|
1057
|
+
#connect-overlay .co-card{ position:relative; width:min(640px, calc(100vw - 32px)); max-height:calc(100vh - 48px); overflow-y:auto; background:var(--elev); border:1px solid var(--line2); border-radius:14px; box-shadow:var(--shadow-pop); }
|
|
1058
|
+
#connect-overlay .co-head{ display:flex; align-items:center; justify-content:space-between; padding:14px 16px 10px; border-bottom:1px solid var(--line); }
|
|
1059
|
+
#connect-overlay .co-head b{ font-family:var(--serif); font-weight:500; font-size:17px; letter-spacing:.06em; text-transform:uppercase; }
|
|
1060
|
+
#connect-overlay .co-body{ padding:14px 16px 16px; display:flex; flex-direction:column; gap:10px; }
|
|
1061
|
+
#connect-overlay .co-intro{ font-size:12px; color:var(--dim); line-height:1.5; }
|
|
997
1062
|
.toast{
|
|
998
1063
|
background:var(--elev); border:1px solid var(--line2); border-radius:999px; padding:8px 17px;
|
|
999
1064
|
font-size:12px; color:var(--text); box-shadow:var(--shadow-pop);
|
|
@@ -1146,6 +1211,11 @@
|
|
|
1146
1211
|
</div>
|
|
1147
1212
|
</header>
|
|
1148
1213
|
|
|
1214
|
+
<!-- billing-return banner (#371) — filled by wireBillingBanner() when the
|
|
1215
|
+
URL carries ?billing=success|cancelled (the checkout return URLs the
|
|
1216
|
+
router stamps). Dismiss removes it; the param is stripped on show. -->
|
|
1217
|
+
<div id="billing-banner" role="status" aria-live="polite" hidden></div>
|
|
1218
|
+
|
|
1149
1219
|
<!-- ============ the stats band — card grid (two rows of four) ============ -->
|
|
1150
1220
|
<section id="measures" aria-label="Vitals, operator data, and account limits">
|
|
1151
1221
|
<div class="band" id="strip">
|
|
@@ -1164,8 +1234,10 @@
|
|
|
1164
1234
|
<div class="csub" id="ag-sub-band">found on their own</div>
|
|
1165
1235
|
<button class="limit-cta" id="ag-cta">Connect Agent</button>
|
|
1166
1236
|
</div>
|
|
1167
|
-
<!-- #plan-billing: the
|
|
1168
|
-
|
|
1237
|
+
<!-- #plan-billing: the token/cap usage card — this page's
|
|
1238
|
+
billing-adjacent surface. The account-menu item that scrolled
|
|
1239
|
+
here was consolidated into the Subscription entry (#371 round 3);
|
|
1240
|
+
the anchor id stays (diagnostics + tests pin it). -->
|
|
1169
1241
|
<div class="bseg c-red" id="plan-billing">
|
|
1170
1242
|
<div class="gh"><span class="t">Token usage</span></div>
|
|
1171
1243
|
<div class="big"><span class="n" id="tok-n">—</span><span class="l">used</span><span class="cap-of" id="tok-of" hidden></span></div>
|
|
@@ -1284,17 +1356,9 @@
|
|
|
1284
1356
|
<aside id="agentscol" aria-label="Connected agents">
|
|
1285
1357
|
<div class="col-head"><span class="t">Fleet</span><small id="ag-sub">0 · auto-detected</small></div>
|
|
1286
1358
|
<div id="ag-list"></div>
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
<p>Run this on the machine that hosts the agent. It appears here the moment it first talks to the server.</p>
|
|
1291
|
-
<div class="snip" id="connect-snip"></div>
|
|
1292
|
-
<div class="row">
|
|
1293
|
-
<button class="btn sm" id="connect-copy">Copy command</button>
|
|
1294
|
-
<button class="btn sm save" id="connect-close">Done</button>
|
|
1295
|
-
</div>
|
|
1296
|
-
</div>
|
|
1297
|
-
</div>
|
|
1359
|
+
<!-- #506: the fleet rail's + connect-an-agent drawer lived here — REMOVED
|
|
1360
|
+
(owner 2026-09-24). Both surviving connect entries (the account-menu
|
|
1361
|
+
entry + the Agents-detected pill) open the #connect-overlay. -->
|
|
1298
1362
|
</aside>
|
|
1299
1363
|
</main>
|
|
1300
1364
|
|
|
@@ -1331,13 +1395,71 @@
|
|
|
1331
1395
|
</label>
|
|
1332
1396
|
<label class="mm-check"><input type="checkbox" id="mm-think"> enable thinking <small>(local endpoints only)</small></label>
|
|
1333
1397
|
</div>
|
|
1334
|
-
<div class="mm-note">applies on server restart</div>
|
|
1398
|
+
<div class="mm-note" id="mm-note">applies on server restart</div>
|
|
1335
1399
|
<div class="mm-foot">
|
|
1336
1400
|
<button class="btn" id="mm-cancel">Cancel</button>
|
|
1337
1401
|
<button class="btn save" id="mm-save">Save</button>
|
|
1338
1402
|
</div>
|
|
1339
1403
|
</div>
|
|
1340
1404
|
</div>
|
|
1405
|
+
|
|
1406
|
+
<!-- ============ the PLAN & BILLING overlay (#371, owner amendment 2
|
|
1407
|
+
2026-09-23: the under-graph section was rejected after the live look —
|
|
1408
|
+
"the plan and billing needs to open up as overlay, not under the
|
|
1409
|
+
graph, it could not even find it first. needs to cover the normal dash
|
|
1410
|
+
or be a separate tab or view"; the owner picked the full overlay).
|
|
1411
|
+
Opens from the account menu's Subscription entry (round 3: the two
|
|
1412
|
+
billing entries merged into one); the opaque .po-cover fully hides
|
|
1413
|
+
the dash (not dimmed-partial). × / cover-click / ESC close and return
|
|
1414
|
+
where the user was. The option cards are GENERATED from the
|
|
1415
|
+
PLAN_OPTIONS data array (round 3: N-driven — a future plan is a data
|
|
1416
|
+
entry + a router price id, not a page rework); prices and the
|
|
1417
|
+
included-features bullets are display constants aligned with the
|
|
1418
|
+
marketing site (lockstep-pinned by tests). The router deliberately
|
|
1419
|
+
knows only Stripe price ids, so there is no server-side amount to
|
|
1420
|
+
read; the Stripe Checkout page shows the authoritative amount before
|
|
1421
|
+
payment. ============ -->
|
|
1422
|
+
<div id="plan-overlay" hidden>
|
|
1423
|
+
<div class="po-cover" id="po-cover"></div>
|
|
1424
|
+
<div class="po-card" role="dialog" aria-modal="true" aria-labelledby="po-title">
|
|
1425
|
+
<div class="po-head"><b id="po-title">Plan & billing</b><button class="pm-close" id="po-close" aria-label="Close">✕</button></div>
|
|
1426
|
+
<div class="po-body">
|
|
1427
|
+
<div class="po-current" id="po-current"></div>
|
|
1428
|
+
<div class="po-opts" id="acct-options"></div>
|
|
1429
|
+
</div>
|
|
1430
|
+
</div>
|
|
1431
|
+
</div>
|
|
1432
|
+
|
|
1433
|
+
<!-- ============ the CONNECT overlay (#506) — ONE full-cover surface for
|
|
1434
|
+
every "Connect an agent" entry. Before this there were two shapes: the
|
|
1435
|
+
account menu's inline expandable snippets and the fleet rail's +
|
|
1436
|
+
drawer. Owner 2026-09-24: the rail entry is REMOVED, not kept; the
|
|
1437
|
+
account-menu entry and the Agents-detected pill both open THIS
|
|
1438
|
+
(#plan-overlay idiom: opaque cover, × / cover-click / ESC). The
|
|
1439
|
+
snippet <pre>s and copy buttons keep the acct-* ids so
|
|
1440
|
+
renderAcctToken keeps driving them (token-gated: disabled until
|
|
1441
|
+
/account/token answers); the wiring is STATIC so the overlay works
|
|
1442
|
+
even when /account failed and the menu never rendered. The overlay
|
|
1443
|
+
itself lazily fetches the token on open (idempotent). ============ -->
|
|
1444
|
+
<div id="connect-overlay" hidden>
|
|
1445
|
+
<div class="co-cover" id="co-cover"></div>
|
|
1446
|
+
<div class="co-card" role="dialog" aria-modal="true" aria-labelledby="co-title">
|
|
1447
|
+
<div class="co-head"><b id="co-title">Connect an agent</b><button class="pm-close" id="co-close" aria-label="Close">✕</button></div>
|
|
1448
|
+
<div class="co-body">
|
|
1449
|
+
<p class="co-intro">Run this on the machine that hosts the agent. It appears here the moment it first talks to the server.</p>
|
|
1450
|
+
<div class="acct-label">CLI</div>
|
|
1451
|
+
<div class="acct-snippet">
|
|
1452
|
+
<pre id="acct-cli"></pre>
|
|
1453
|
+
<button id="acct-copy-cli" type="button" class="acct-btn" disabled>Copy</button>
|
|
1454
|
+
</div>
|
|
1455
|
+
<div class="acct-label">MCP — ~/.claude.json</div>
|
|
1456
|
+
<div class="acct-snippet">
|
|
1457
|
+
<pre id="acct-mcp"></pre>
|
|
1458
|
+
<button id="acct-copy-mcp" type="button" class="acct-btn" disabled>Copy</button>
|
|
1459
|
+
</div>
|
|
1460
|
+
</div>
|
|
1461
|
+
</div>
|
|
1462
|
+
</div>
|
|
1341
1463
|
<script>
|
|
1342
1464
|
'use strict';
|
|
1343
1465
|
/* =====================================================================
|
|
@@ -2292,7 +2414,7 @@ function draw(now, dt){
|
|
|
2292
2414
|
if(L.rk>=lodK) continue; // zoom LOD (lines only)
|
|
2293
2415
|
const a=mems.get(L.a), b=mems.get(L.b);
|
|
2294
2416
|
if(!a||!b||!aliveAt(a,t)||!aliveAt(b,t)) continue;
|
|
2295
|
-
/* #474 final polish: the both-endpoints-in-window gate killed the mesh — links draw between ANY placed memories (
|
|
2417
|
+
/* #474 final polish: the both-endpoints-in-window gate killed the mesh — links draw between ANY placed memories (reference parity: the full LOD-tamed mesh), endpoints carry positions regardless of window membership */
|
|
2296
2418
|
if(a._emerged===false||b._emerged===false) continue; // thread waits for both endpoints
|
|
2297
2419
|
const [ax,ay]=bpos(a,now,t), [bx,by]=bpos(b,now,t);
|
|
2298
2420
|
const ctrl=segCtrlN(ax,ay,bx,by,(L.use>18?1:-1));
|
|
@@ -2966,9 +3088,12 @@ document.addEventListener('keydown',e=>{
|
|
|
2966
3088
|
else if(e.key==='ArrowRight'){ manualTake(); seek(T+1); }
|
|
2967
3089
|
else if(e.key==='t'||e.key==='T'){ setTheme(document.documentElement.dataset.theme==='dark'?'light':'dark'); }
|
|
2968
3090
|
else if(e.key==='Escape'){
|
|
2969
|
-
|
|
3091
|
+
const po=el('plan-overlay');
|
|
3092
|
+
const co=el('connect-overlay');
|
|
3093
|
+
if(po && !po.hidden) closePlanOverlay(); /* the full-cover overlay is topmost — it closes first (#371) */
|
|
3094
|
+
else if(co && !co.hidden) closeConnectOverlay(); /* #506 — same tier: the connect overlay */
|
|
3095
|
+
else if(document.getElementById('acct-menu') && document.getElementById('acct-menu').classList.contains('open')) closeAcctMenu();
|
|
2970
3096
|
else if(panel.classList.contains('open')) closePanel();
|
|
2971
|
-
else if(el('connect-x').classList.contains('open')) el('connect-x').classList.remove('open');
|
|
2972
3097
|
else if(focusDom!=null) setFocus(null);
|
|
2973
3098
|
}
|
|
2974
3099
|
wake();
|
|
@@ -3273,8 +3398,8 @@ function renderRecBand(ups){
|
|
|
3273
3398
|
/* No trend arrow (owner 2026-09-20: removed — unclear). Its renderer and
|
|
3274
3399
|
the element writes are removed WITH it: a leftover write to a removed
|
|
3275
3400
|
element is a boot-killing TypeError whenever the recall band carries
|
|
3276
|
-
real data (
|
|
3277
|
-
was ever seeded). */
|
|
3401
|
+
real data (a production instance on 0.22.2: "0 memories" — boot died
|
|
3402
|
+
before the counter was ever seeded). */
|
|
3278
3403
|
}
|
|
3279
3404
|
|
|
3280
3405
|
/* ---------- Memory Precision — Level 1, the pushed index (#476). A compact
|
|
@@ -3384,32 +3509,44 @@ function renderModelCard(){
|
|
|
3384
3509
|
only as api_key_set), writes PUT with the subset the user touched. The
|
|
3385
3510
|
api-key input is WRITE-ONLY: prefilled with nothing, sent only when
|
|
3386
3511
|
non-empty ("leave blank to keep"). enable_thinking is sent only when the
|
|
3387
|
-
user toggles the checkbox (tri-state not needed).
|
|
3512
|
+
user toggles the checkbox (tri-state not needed).
|
|
3513
|
+
Hosted mode (#514): the GET carries managed:true — the hosting service
|
|
3514
|
+
owns the endpoint + credentials, so their inputs are disabled and never
|
|
3515
|
+
ride the PUT body (a save cannot hit the server's managed-field 403). */
|
|
3388
3516
|
let mmThinkTouched=false;
|
|
3389
3517
|
function openModelModal(){
|
|
3390
3518
|
if(!M) return; // no settings payload → no dialog (degrade)
|
|
3391
3519
|
const mdl=el('model-modal');
|
|
3520
|
+
const managed=!!M.managed;
|
|
3392
3521
|
el('mm-backend').value=M.backend||''; // '' = auto / OpenAI-compatible
|
|
3393
3522
|
el('mm-baseurl').value=M.base_url||'';
|
|
3394
3523
|
el('mm-model').value=M.model||'';
|
|
3395
3524
|
const ak=el('mm-apikey'); ak.value='';
|
|
3396
|
-
ak.placeholder=
|
|
3525
|
+
ak.placeholder=managed?'managed by the hosting service'
|
|
3526
|
+
:(M.api_key_set?'configured — leave blank to keep':'api key (optional for local endpoints)');
|
|
3397
3527
|
el('mm-maxtok').value=M.max_tokens!=null?String(M.max_tokens):'';
|
|
3398
3528
|
el('mm-think').checked=!!M.enable_thinking;
|
|
3399
3529
|
mmThinkTouched=false;
|
|
3530
|
+
for(const id of ['mm-backend','mm-baseurl','mm-apikey']) el(id).disabled=managed;
|
|
3531
|
+
el('mm-note').textContent=managed
|
|
3532
|
+
?'endpoint and credentials are managed by the hosting service — applies on server restart'
|
|
3533
|
+
:'applies on server restart';
|
|
3400
3534
|
mdl.hidden=false;
|
|
3401
|
-
el('mm-baseurl').focus();
|
|
3535
|
+
(managed?el('mm-model'):el('mm-baseurl')).focus();
|
|
3402
3536
|
}
|
|
3403
3537
|
function closeModelModal(){ el('model-modal').hidden=true; }
|
|
3404
3538
|
async function saveModelSettings(){
|
|
3405
3539
|
/* Build the PUT body from CURRENT field state: empty base_url/model/max
|
|
3406
3540
|
tokens = clear (null server-side); backend '' clears the named-backend
|
|
3407
|
-
key; api_key only rides when typed; enable_thinking only when toggled.
|
|
3541
|
+
key; api_key only rides when typed; enable_thinking only when toggled.
|
|
3542
|
+
Managed (hosted) mode: backend/base_url/api_key never ride the body. */
|
|
3408
3543
|
const body={};
|
|
3409
|
-
|
|
3410
|
-
|
|
3544
|
+
if(!M.managed){
|
|
3545
|
+
body.backend=el('mm-backend').value;
|
|
3546
|
+
const bu=el('mm-baseurl').value.trim(); body.base_url=bu?bu:null;
|
|
3547
|
+
const ak=el('mm-apikey').value; if(ak) body.api_key=ak;
|
|
3548
|
+
}
|
|
3411
3549
|
const mo=el('mm-model').value.trim(); body.model=mo?mo:null;
|
|
3412
|
-
const ak=el('mm-apikey').value; if(ak) body.api_key=ak;
|
|
3413
3550
|
const mt=el('mm-maxtok').value.trim(); body.max_tokens=mt?Number(mt):null;
|
|
3414
3551
|
if(mmThinkTouched) body.enable_thinking=el('mm-think').checked;
|
|
3415
3552
|
try{
|
|
@@ -3784,7 +3921,9 @@ function lastNightByAgent(){
|
|
|
3784
3921
|
function buildAgents(){
|
|
3785
3922
|
const listEl=el('ag-list'); listEl.innerHTML='';
|
|
3786
3923
|
const gs=contextGroups();
|
|
3787
|
-
|
|
3924
|
+
/* #506: the "below" target (the rail drawer) is gone — the empty state
|
|
3925
|
+
points at the one remaining connect surface, the account menu */
|
|
3926
|
+
if(!gs.length){ listEl.innerHTML='<div class="pm-empty" style="padding:10px 6px;">no contexts yet — connect an agent from the account menu (top right)</div>'; return; }
|
|
3788
3927
|
const lastNight=lastNightByAgent();
|
|
3789
3928
|
/* #423 phase 3: presence + pauses ride D.fleet — ABSENT on a pre-phase-3
|
|
3790
3929
|
server → no dots, no toggles, no paused state (phase-2 rendering) */
|
|
@@ -4022,8 +4161,6 @@ async function refreshFleet(){
|
|
|
4022
4161
|
renderBand();
|
|
4023
4162
|
}catch(e){ toast('Refresh failed: '+e.message); }
|
|
4024
4163
|
}
|
|
4025
|
-
el('connect-btn').onclick=()=>{ el('connect-x').classList.toggle('open'); wake(); };
|
|
4026
|
-
|
|
4027
4164
|
/* model-settings modal wiring (#422) */
|
|
4028
4165
|
el('model-cfg').onclick=()=>{ openModelModal(); wake(); };
|
|
4029
4166
|
el('mm-close').onclick=closeModelModal;
|
|
@@ -4031,14 +4168,10 @@ el('mm-cancel').onclick=closeModelModal;
|
|
|
4031
4168
|
el('mm-backdrop').onclick=closeModelModal;
|
|
4032
4169
|
el('mm-save').onclick=()=>{ saveModelSettings(); wake(); };
|
|
4033
4170
|
el('mm-think').addEventListener('change',()=>{ mmThinkTouched=true; });
|
|
4034
|
-
|
|
4035
|
-
|
|
4036
|
-
|
|
4037
|
-
el('
|
|
4038
|
-
el('connect-copy').onclick=()=>{
|
|
4039
|
-
copyToClipboard('npx @gamaze/hicortex init --server '+location.origin, el('connect-copy'));
|
|
4040
|
-
wake();
|
|
4041
|
-
};
|
|
4171
|
+
/* #506: the Agents-detected pill opens the full-cover connect overlay —
|
|
4172
|
+
the rail drawer (+ button, open/scroll mechanics) it used to target is
|
|
4173
|
+
REMOVED (owner 2026-09-24) */
|
|
4174
|
+
el('ag-cta').onclick=()=>{ openConnectOverlay(); wake(); };
|
|
4042
4175
|
|
|
4043
4176
|
/* ---------- the memory browser — dropdown under the search pill ---------- */
|
|
4044
4177
|
const panel=el('panel');
|
|
@@ -4416,8 +4549,9 @@ async function refreshAfterWrite(){
|
|
|
4416
4549
|
|
|
4417
4550
|
/* =====================================================================
|
|
4418
4551
|
ACCOUNT MENU — harvested from the 0.20.4 console (#365): authoritative
|
|
4419
|
-
token via lazy GET /account/token, connect
|
|
4420
|
-
|
|
4552
|
+
token via lazy GET /account/token, connect overlay entry (#506 — the
|
|
4553
|
+
snippets moved to the full-cover overlay), plan & billing, sign-out
|
|
4554
|
+
(client-side only — dropping the stored tokens IS the sign-out).
|
|
4421
4555
|
===================================================================== */
|
|
4422
4556
|
const $ = el; // the harvested block below uses the $ shorthand
|
|
4423
4557
|
/* Pure decision — what the corner renders for an /account payload (#409 fix
|
|
@@ -4441,6 +4575,58 @@ function acctMenuPlan(a) {
|
|
|
4441
4575
|
avatar: ((typeof a.name === "string" && a.name) ? a.name : "?").slice(0, 2).toUpperCase(),
|
|
4442
4576
|
};
|
|
4443
4577
|
}
|
|
4578
|
+
/* Pure decision (#371) — the purchasable periods. Decided pricing (owner
|
|
4579
|
+
2026-08-19): ONE price, TWO periods — €12/mo and €108/yr. ANNUAL FIRST:
|
|
4580
|
+
annual is the stated product default; its note is the numeric savings
|
|
4581
|
+
sub-line (round-4 addendum 2026-09-24: it replaces the retired
|
|
4582
|
+
words-only annual tagline — same fact, numbers not words; effective
|
|
4583
|
+
monthly 108/12 = €9, discount (144−108)/144 = 25%, derived-pinned by
|
|
4584
|
+
tests so a price change forces the sub-line update). Prices are
|
|
4585
|
+
display constants — the
|
|
4586
|
+
router deliberately knows only Stripe price ids (no server-side amount
|
|
4587
|
+
exists to read), and the Stripe Checkout page shows the authoritative
|
|
4588
|
+
amount before payment. Round 3: a PURE DATA ARRAY and the single source
|
|
4589
|
+
of truth — the overlay cards are GENERATED from it (renderPlanCards)
|
|
4590
|
+
and the wiring loops it (wireSubscription); no two-plan hardcoding
|
|
4591
|
+
anywhere. The bullets are the marketing site's Cloud feature lines,
|
|
4592
|
+
verbatim (lockstep-pinned against web/index.html) — the seven
|
|
4593
|
+
owner-decided lines (amendment 4, 2026-09-24: copy-first ruling; the
|
|
4594
|
+
agent-cap enforcement rides its own issue); both periods are the same
|
|
4595
|
+
Cloud product, so both carry the same seven. */
|
|
4596
|
+
const PLAN_OPTIONS = [
|
|
4597
|
+
{ id: "annual", posts: "annual", name: "Annual", price: "€108/year", note: "€9/mo · 25% off", cta: "Choose annual", bullets: ["Fully managed — no API keys needed", "Claude Code, Hermes, OpenClaw, Pi, OpenCode", "10,000 memories included", "Up to 10 agents", "Learnings & nightly reflection", "Semantic search + knowledge graph", "Multi-machine support"] },
|
|
4598
|
+
{ id: "monthly", posts: "monthly", name: "Monthly", price: "€12/mo", note: "", cta: "Choose monthly", bullets: ["Fully managed — no API keys needed", "Claude Code, Hermes, OpenClaw, Pi, OpenCode", "10,000 memories included", "Up to 10 agents", "Learnings & nightly reflection", "Semantic search + knowledge graph", "Multi-machine support"] },
|
|
4599
|
+
];
|
|
4600
|
+
/* Pure decision (#371) — the Stripe return parameter (?billing=…, the
|
|
4601
|
+
success_url/cancel_url the router stamps on every checkout) → the
|
|
4602
|
+
acknowledgment banner spec, or null when there is nothing to acknowledge
|
|
4603
|
+
(unknown or absent → the page shows nothing, never a guess). */
|
|
4604
|
+
function billingBanner(value) {
|
|
4605
|
+
if (value === "success")
|
|
4606
|
+
return { kind: "success", text: "payment received — activation completes within a minute" };
|
|
4607
|
+
if (value === "cancelled")
|
|
4608
|
+
return { kind: "cancelled", text: "checkout cancelled — nothing was charged" };
|
|
4609
|
+
return null;
|
|
4610
|
+
}
|
|
4611
|
+
/* Pure decision (#371, amendment 2) — the overlay's current-plan line.
|
|
4612
|
+
HONEST by construction: /account provides name/org/plan only, so the
|
|
4613
|
+
line shows the plan label verbatim when set, and when unset the
|
|
4614
|
+
documented meaning of a null label (the same eligibility the Upgrade
|
|
4615
|
+
entry uses) — never invented tiers or numbers. */
|
|
4616
|
+
function acctPlanLine(p) {
|
|
4617
|
+
return p.plan
|
|
4618
|
+
? "Current plan — " + p.plan
|
|
4619
|
+
: "Current plan — none yet (hosted trial, or self-hosted install)";
|
|
4620
|
+
}
|
|
4621
|
+
/* Pure decision (#371 round 3) — the Subscription entry's Upgrade pill.
|
|
4622
|
+
The menu's two billing entries (Upgrade + Plan & billing) merged into
|
|
4623
|
+
ONE always-rendered "Subscription" item; the PILL is what the old
|
|
4624
|
+
plan-unset eligibility gates (owner: "it can have an upgrade pill/label
|
|
4625
|
+
after it before any subscription is active"). Returns the pill markup
|
|
4626
|
+
or "" — interpolated into the entry's label. */
|
|
4627
|
+
function acctPill(p) {
|
|
4628
|
+
return p.upgrade ? '<span class="acct-pill">Upgrade</span>' : "";
|
|
4629
|
+
}
|
|
4444
4630
|
function renderAccount(a) {
|
|
4445
4631
|
const host = $("nav-account");
|
|
4446
4632
|
const p = acctMenuPlan(a);
|
|
@@ -4449,9 +4635,7 @@ function renderAccount(a) {
|
|
|
4449
4635
|
const plan = p.plan
|
|
4450
4636
|
? `<span class="mini" style="font-size:10.5px;border:1px solid var(--line2);border-radius:999px;padding:2px 9px;color:var(--dim)">${esc(p.plan)}</span>`
|
|
4451
4637
|
: "";
|
|
4452
|
-
const
|
|
4453
|
-
? '<button id="acct-upgrade" type="button" class="acct-link acct-upgrade">Upgrade</button>'
|
|
4454
|
-
: "";
|
|
4638
|
+
const pill = acctPill(p); // #371 round 3: the entry always renders; the PILL is eligibility-gated
|
|
4455
4639
|
host.innerHTML = `
|
|
4456
4640
|
<button id="acct-trigger" type="button" class="acct-trigger" aria-haspopup="menu" aria-expanded="false">
|
|
4457
4641
|
<span class="avatar">${esc(p.avatar)}</span>${who?'<span class="who">'+who+'</span> ':''}${plan}<span class="acct-caret" aria-hidden="true">▼</span>
|
|
@@ -4463,55 +4647,182 @@ function renderAccount(a) {
|
|
|
4463
4647
|
<button id="acct-reveal" type="button" class="acct-btn" disabled>Reveal</button>
|
|
4464
4648
|
<button id="acct-copy-token" type="button" class="acct-btn" disabled>Copy</button>
|
|
4465
4649
|
</div>
|
|
4466
|
-
<
|
|
4467
|
-
|
|
4468
|
-
<div class="acct-label">CLI</div>
|
|
4469
|
-
<div class="acct-snippet">
|
|
4470
|
-
<pre id="acct-cli"></pre>
|
|
4471
|
-
<button id="acct-copy-cli" type="button" class="acct-btn" disabled>Copy</button>
|
|
4472
|
-
</div>
|
|
4473
|
-
<div class="acct-label">MCP — ~/.claude.json</div>
|
|
4474
|
-
<div class="acct-snippet">
|
|
4475
|
-
<pre id="acct-mcp"></pre>
|
|
4476
|
-
<button id="acct-copy-mcp" type="button" class="acct-btn" disabled>Copy</button>
|
|
4477
|
-
</div>
|
|
4478
|
-
</details>
|
|
4479
|
-
${upgrade}
|
|
4480
|
-
<button id="acct-plan" type="button" class="acct-link">Plan & billing</button>
|
|
4650
|
+
<button id="acct-connect-entry" type="button" class="acct-link">Connect an agent</button>
|
|
4651
|
+
<button id="acct-subscription" type="button" class="acct-link">Subscription${pill}</button>
|
|
4481
4652
|
<button id="acct-signout" type="button" class="acct-link acct-danger">Sign out</button>
|
|
4482
4653
|
</div>`;
|
|
4483
4654
|
wireAcctMenu();
|
|
4484
|
-
|
|
4655
|
+
renderPlanOverlay(p);
|
|
4656
|
+
wireSubscription();
|
|
4657
|
+
wireConnectEntry();
|
|
4658
|
+
}
|
|
4659
|
+
/* The overlay's content (#371, amendment 2): the honest current-plan line
|
|
4660
|
+
from the pure decision, and the purchase options under the SAME
|
|
4661
|
+
plan-label-unset eligibility as the menu entry (a second checkout for an
|
|
4662
|
+
active subscriber would mint a second Stripe subscription — checkout
|
|
4663
|
+
passes customer_email, not a subscription update; switching is Phase 3).
|
|
4664
|
+
/account failed (p null) → nothing fills (fail-soft, like the empty
|
|
4665
|
+
corner); the overlay itself opens only from the menu entry anyway. */
|
|
4666
|
+
function renderPlanOverlay(p) {
|
|
4667
|
+
if (!p) return;
|
|
4668
|
+
const cur = $("po-current");
|
|
4669
|
+
if (cur) cur.textContent = acctPlanLine(p);
|
|
4670
|
+
const opts = $("acct-options");
|
|
4671
|
+
if (opts) opts.hidden = !p.upgrade;
|
|
4672
|
+
}
|
|
4673
|
+
function openPlanOverlay() {
|
|
4674
|
+
const ov = $("plan-overlay");
|
|
4675
|
+
if (!ov || !ov.hidden) return;
|
|
4676
|
+
ov.hidden = false;
|
|
4677
|
+
const close = $("po-close");
|
|
4678
|
+
if (close) close.focus(); // keyboard users land on the primary exit (the modal focus idiom)
|
|
4679
|
+
}
|
|
4680
|
+
function closePlanOverlay() {
|
|
4681
|
+
const ov = $("plan-overlay");
|
|
4682
|
+
if (ov) ov.hidden = true;
|
|
4485
4683
|
}
|
|
4486
|
-
|
|
4487
|
-
|
|
4684
|
+
/* #506 — the connect overlay: ONE full-cover surface for every "Connect an
|
|
4685
|
+
agent" entry (the #plan-overlay idiom). Opening lazily fetches the token
|
|
4686
|
+
(idempotent) because the overlay is reachable WITHOUT the account menu
|
|
4687
|
+
(the Agents-detected pill) — the menu path would otherwise be the only
|
|
4688
|
+
thing seeding the snippets. */
|
|
4689
|
+
function openConnectOverlay() {
|
|
4690
|
+
const ov = $("connect-overlay");
|
|
4691
|
+
if (!ov || !ov.hidden) return;
|
|
4692
|
+
ov.hidden = false;
|
|
4693
|
+
fetchAcctToken(); // lazy + idempotent — one attempt per page load
|
|
4694
|
+
const close = $("co-close");
|
|
4695
|
+
if (close) close.focus(); // keyboard users land on the primary exit (the modal focus idiom)
|
|
4696
|
+
}
|
|
4697
|
+
function closeConnectOverlay() {
|
|
4698
|
+
const ov = $("connect-overlay");
|
|
4699
|
+
if (ov) ov.hidden = true;
|
|
4700
|
+
}
|
|
4701
|
+
/* N-driven card render (#371 round 3): the option cards are GENERATED from
|
|
4702
|
+
PLAN_OPTIONS — N entries, N cards, no per-plan literals anywhere in the
|
|
4703
|
+
markup or this template (a future plan is a data entry + a router price
|
|
4704
|
+
id, not a page rework). Runs once at script init; account data only
|
|
4705
|
+
toggles visibility (renderPlanOverlay). */
|
|
4706
|
+
function renderPlanCards() {
|
|
4707
|
+
const host = $("acct-options");
|
|
4708
|
+
if (!host) return;
|
|
4709
|
+
host.innerHTML = PLAN_OPTIONS.map((o) => `
|
|
4710
|
+
<div class="po-opt" id="acct-opt-${o.id}">
|
|
4711
|
+
<div class="po-name">${esc(o.name)}</div>
|
|
4712
|
+
<div class="po-price">${esc(o.price)}</div>
|
|
4713
|
+
${o.note ? `<div class="po-note">${esc(o.note)}</div>` : ""}
|
|
4714
|
+
<ul class="po-feat">${o.bullets.map((b) => `<li>${esc(b)}</li>`).join("")}</ul>
|
|
4715
|
+
<button id="acct-buy-${o.id}" type="button" class="po-choose">${esc(o.cta)}</button>
|
|
4716
|
+
</div>`).join("");
|
|
4717
|
+
}
|
|
4718
|
+
renderPlanCards();
|
|
4719
|
+
function wireSubscription() {
|
|
4720
|
+
const btn = $("acct-subscription");
|
|
4488
4721
|
if (!btn) return;
|
|
4489
|
-
|
|
4490
|
-
|
|
4491
|
-
|
|
4492
|
-
|
|
4493
|
-
|
|
4494
|
-
|
|
4495
|
-
|
|
4496
|
-
|
|
4497
|
-
|
|
4498
|
-
|
|
4499
|
-
|
|
4500
|
-
|
|
4501
|
-
|
|
4502
|
-
|
|
4503
|
-
|
|
4504
|
-
|
|
4505
|
-
|
|
4506
|
-
|
|
4507
|
-
|
|
4508
|
-
|
|
4509
|
-
|
|
4510
|
-
|
|
4722
|
+
// #371 round 3: the ONE Subscription entry (Upgrade + Plan & billing
|
|
4723
|
+
// merged, owner) opens the FULL-COVER plan & billing overlay; the entry
|
|
4724
|
+
// always renders — the Upgrade pill alone marks the plan-unset state.
|
|
4725
|
+
// Menu closes first; × / cover-click / ESC return.
|
|
4726
|
+
btn.addEventListener("click", () => {
|
|
4727
|
+
closeAcctMenu();
|
|
4728
|
+
openPlanOverlay();
|
|
4729
|
+
});
|
|
4730
|
+
for (const o of PLAN_OPTIONS) {
|
|
4731
|
+
const buy = $("acct-buy-" + o.id);
|
|
4732
|
+
if (!buy) continue;
|
|
4733
|
+
buy.addEventListener("click", () => { startCheckout(o.posts, buy); });
|
|
4734
|
+
}
|
|
4735
|
+
}
|
|
4736
|
+
/* #506: the menu's Connect-an-agent entry opens the full-cover connect
|
|
4737
|
+
overlay — the same launch idiom as wireSubscription (#371): the menu
|
|
4738
|
+
closes first; × / cover-click / ESC close and return where the user
|
|
4739
|
+
was. The inline expandable snippets this entry used to reveal are gone;
|
|
4740
|
+
the overlay owns connecting. */
|
|
4741
|
+
function wireConnectEntry() {
|
|
4742
|
+
const btn = $("acct-connect-entry");
|
|
4743
|
+
if (!btn) return;
|
|
4744
|
+
btn.addEventListener("click", () => {
|
|
4745
|
+
closeAcctMenu();
|
|
4746
|
+
openConnectOverlay();
|
|
4747
|
+
});
|
|
4748
|
+
}
|
|
4749
|
+
/** POST /stripe/checkout for ONE plan, then navigate to the returned Stripe
|
|
4750
|
+
* URL (#371 — each period's action posts its own plan; the hardcoded
|
|
4751
|
+
* annual-only body was the defect). The failure seams are the shipped
|
|
4752
|
+
* checkout-handler contract, kept verbatim for EVERY action: 401 → Google
|
|
4753
|
+
* re-auth where the probe says OAuth exists, else the expired-session
|
|
4754
|
+
* alert; non-ok → the unavailable alert; network error → the unreachable
|
|
4755
|
+
* alert. Each button is disabled independently while its own request is in
|
|
4756
|
+
* flight. */
|
|
4757
|
+
async function startCheckout(plan, btn) {
|
|
4758
|
+
btn.disabled = true;
|
|
4759
|
+
try {
|
|
4760
|
+
const resp = await fetch("/stripe/checkout", {
|
|
4761
|
+
method: "POST",
|
|
4762
|
+
headers: { "content-type": "application/json" },
|
|
4763
|
+
body: JSON.stringify({ plan }),
|
|
4764
|
+
});
|
|
4765
|
+
if (resp.status === 401) {
|
|
4766
|
+
// Never hard-redirect to Google on a daemon (dead end / loop) — only
|
|
4767
|
+
// where the probe says OAuth exists; else surface the expired session.
|
|
4768
|
+
if (await probeGoogleAuth()) { location.href = "/auth/google/start"; return; }
|
|
4769
|
+
alert("Session expired — reload the page to sign in again.");
|
|
4511
4770
|
btn.disabled = false;
|
|
4771
|
+
return;
|
|
4512
4772
|
}
|
|
4513
|
-
|
|
4773
|
+
const body = await resp.json().catch(() => ({}));
|
|
4774
|
+
if (resp.ok && body.url) { location.href = body.url; return; }
|
|
4775
|
+
alert("Checkout unavailable (" + resp.status + ") — try again later.");
|
|
4776
|
+
btn.disabled = false;
|
|
4777
|
+
} catch {
|
|
4778
|
+
alert("Checkout unreachable — try again later.");
|
|
4779
|
+
btn.disabled = false;
|
|
4780
|
+
}
|
|
4781
|
+
}
|
|
4782
|
+
/* The return acknowledgment (#371): read ?billing= once on load and show
|
|
4783
|
+
the one-line banner. The param is STRIPPED via history.replaceState the
|
|
4784
|
+
moment the banner shows — neither dismissal nor a plain reload can
|
|
4785
|
+
re-show it (a reload re-reads a URL that no longer carries the param).
|
|
4786
|
+
Runs at script init, before boot: the acknowledgment must not depend on
|
|
4787
|
+
the data fetches succeeding. */
|
|
4788
|
+
function wireBillingBanner() {
|
|
4789
|
+
const host = $("billing-banner");
|
|
4790
|
+
if (!host) return;
|
|
4791
|
+
const params = new URLSearchParams(location.search);
|
|
4792
|
+
const spec = billingBanner(params.get("billing"));
|
|
4793
|
+
if (!spec) return;
|
|
4794
|
+
params.delete("billing");
|
|
4795
|
+
const qs = params.toString();
|
|
4796
|
+
history.replaceState(null, "", location.pathname + (qs ? "?" + qs : "") + location.hash);
|
|
4797
|
+
host.dataset.kind = spec.kind;
|
|
4798
|
+
host.innerHTML =
|
|
4799
|
+
"<span>" + esc(spec.text) + "</span>" +
|
|
4800
|
+
'<button id="billing-banner-dismiss" type="button" aria-label="Dismiss">✕</button>';
|
|
4801
|
+
host.hidden = false;
|
|
4802
|
+
$("billing-banner-dismiss").addEventListener("click", () => { host.remove(); });
|
|
4514
4803
|
}
|
|
4804
|
+
wireBillingBanner();
|
|
4805
|
+
/* the plan & billing overlay's close affordances (the #model-modal wiring
|
|
4806
|
+
idiom): the × button and a click on the opaque cover both return to the
|
|
4807
|
+
dash; ESC lives in the shared keyboard handler below. */
|
|
4808
|
+
$("po-close").addEventListener("click", closePlanOverlay);
|
|
4809
|
+
$("po-cover").addEventListener("click", closePlanOverlay);
|
|
4810
|
+
/* the connect overlay's close + copy affordances (#506) — the same static
|
|
4811
|
+
wiring idiom. The copy buttons are STATIC overlay elements, wired at
|
|
4812
|
+
script init so they work even when /account failed and the menu never
|
|
4813
|
+
rendered; the payloads are the same byte-for-byte commands the old
|
|
4814
|
+
handlers copied. Token-gated exactly like the menu's buttons: a copy
|
|
4815
|
+
without the token would paste a command that cannot authenticate. */
|
|
4816
|
+
$("co-close").addEventListener("click", closeConnectOverlay);
|
|
4817
|
+
$("co-cover").addEventListener("click", closeConnectOverlay);
|
|
4818
|
+
const copyCliBtn = $("acct-copy-cli");
|
|
4819
|
+
copyCliBtn.addEventListener("click", () => {
|
|
4820
|
+
if (acctTokenState === "ok") copyToClipboard("npx @gamaze/hicortex init --server " + location.origin, copyCliBtn);
|
|
4821
|
+
});
|
|
4822
|
+
const copyMcpBtn = $("acct-copy-mcp");
|
|
4823
|
+
copyMcpBtn.addEventListener("click", () => {
|
|
4824
|
+
if (acctTokenState === "ok") copyToClipboard(acctMcpText(), copyMcpBtn);
|
|
4825
|
+
});
|
|
4515
4826
|
let acctToken = null; // token from /account/token (server truth)
|
|
4516
4827
|
let acctTokenState = "unfetched"; // "unfetched" | "ok" | "unavailable"
|
|
4517
4828
|
let acctTokenFetched = false; // one lazy fetch attempt per page load
|
|
@@ -4539,30 +4850,33 @@ function acctMcpText() {
|
|
|
4539
4850
|
}, null, 2);
|
|
4540
4851
|
}
|
|
4541
4852
|
function renderAcctToken() {
|
|
4853
|
+
/* #506: per-element guards instead of the old bail-early `if (!val)
|
|
4854
|
+
return` — the snippet <pre>s + copy buttons now live in the STATIC
|
|
4855
|
+
connect overlay, so token state must render even when /account failed
|
|
4856
|
+
and the menu (val/reveal/copy-token) never rendered. Gating semantics
|
|
4857
|
+
are unchanged: everything disabled unless ok, "token unavailable"
|
|
4858
|
+
text otherwise, "…" while unfetched, revealed text only in the ok
|
|
4859
|
+
path. */
|
|
4860
|
+
const ok = acctTokenState === "ok";
|
|
4542
4861
|
const val = $("acct-token-val");
|
|
4543
|
-
if (!val) return; // menu not rendered (/account failed — the corner stays empty)
|
|
4544
4862
|
const revealBtn = $("acct-reveal");
|
|
4545
4863
|
const copyToken = $("acct-copy-token");
|
|
4546
4864
|
const copyCli = $("acct-copy-cli");
|
|
4547
4865
|
const copyMcp = $("acct-copy-mcp");
|
|
4548
|
-
if (
|
|
4549
|
-
|
|
4550
|
-
|
|
4551
|
-
|
|
4552
|
-
|
|
4553
|
-
|
|
4554
|
-
|
|
4555
|
-
|
|
4556
|
-
|
|
4557
|
-
|
|
4558
|
-
|
|
4559
|
-
|
|
4560
|
-
|
|
4561
|
-
|
|
4562
|
-
val.textContent = acctRevealed ? acctToken : maskToken(acctToken);
|
|
4563
|
-
revealBtn.textContent = acctRevealed ? "Hide" : "Reveal";
|
|
4564
|
-
$("acct-cli").textContent = acctCliText();
|
|
4565
|
-
$("acct-mcp").textContent = acctMcpText();
|
|
4866
|
+
if (revealBtn) revealBtn.disabled = !ok;
|
|
4867
|
+
if (copyToken) copyToken.disabled = !ok;
|
|
4868
|
+
if (copyCli) copyCli.disabled = !ok;
|
|
4869
|
+
if (copyMcp) copyMcp.disabled = !ok;
|
|
4870
|
+
if (val) {
|
|
4871
|
+
if (!ok) val.textContent = acctTokenState === "unfetched" ? "…" : "token unavailable";
|
|
4872
|
+
else val.textContent = acctRevealed ? acctToken : maskToken(acctToken);
|
|
4873
|
+
}
|
|
4874
|
+
if (revealBtn && ok) revealBtn.textContent = acctRevealed ? "Hide" : "Reveal";
|
|
4875
|
+
const cli = $("acct-cli"); // the connect overlay's snippets (#506) — render regardless of menu presence
|
|
4876
|
+
const mcp = $("acct-mcp");
|
|
4877
|
+
const degraded = acctTokenState === "unfetched" ? "" : "token unavailable";
|
|
4878
|
+
if (cli) cli.textContent = ok ? acctCliText() : degraded;
|
|
4879
|
+
if (mcp) mcp.textContent = ok ? acctMcpText() : degraded;
|
|
4566
4880
|
}
|
|
4567
4881
|
async function fetchAcctToken() {
|
|
4568
4882
|
if (acctTokenFetched) return;
|
|
@@ -4588,8 +4902,6 @@ function closeAcctMenu() {
|
|
|
4588
4902
|
const trigger = document.getElementById("acct-trigger");
|
|
4589
4903
|
if (trigger) trigger.setAttribute("aria-expanded", "false");
|
|
4590
4904
|
acctRevealed = false;
|
|
4591
|
-
const details = document.getElementById("acct-connect");
|
|
4592
|
-
if (details) details.removeAttribute("open");
|
|
4593
4905
|
renderAcctToken();
|
|
4594
4906
|
}
|
|
4595
4907
|
function openAcctMenu() {
|
|
@@ -4652,21 +4964,11 @@ function wireAcctMenu() {
|
|
|
4652
4964
|
copyToken.addEventListener("click", () => {
|
|
4653
4965
|
if (acctTokenState === "ok") copyToClipboard(acctConnectLine(), copyToken);
|
|
4654
4966
|
});
|
|
4655
|
-
|
|
4656
|
-
|
|
4657
|
-
|
|
4658
|
-
|
|
4659
|
-
|
|
4660
|
-
});
|
|
4661
|
-
const copyMcp = $("acct-copy-mcp");
|
|
4662
|
-
copyMcp.addEventListener("click", () => {
|
|
4663
|
-
if (acctTokenState === "ok") copyToClipboard(acctMcpText(), copyMcp);
|
|
4664
|
-
});
|
|
4665
|
-
$("acct-plan").addEventListener("click", () => {
|
|
4666
|
-
closeAcctMenu();
|
|
4667
|
-
const t = document.getElementById("plan-billing");
|
|
4668
|
-
if (t) t.scrollIntoView({ behavior: "smooth", block: "nearest" });
|
|
4669
|
-
});
|
|
4967
|
+
// #506: the copy-cli/copy-mcp wiring moved to the STATIC init block by
|
|
4968
|
+
// the connect overlay — those buttons live in the overlay now, not here.
|
|
4969
|
+
// #371 round 3: the old "Plan & billing" scroll item is gone — its
|
|
4970
|
+
// target tile (#plan-billing) stays in the measures band as the
|
|
4971
|
+
// billing-adjacent surface; the Subscription entry owns the route now.
|
|
4670
4972
|
$("acct-signout").addEventListener("click", signOut);
|
|
4671
4973
|
renderAcctToken();
|
|
4672
4974
|
}
|