@enigmax/dashboard 0.1.0 → 0.1.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/README.md +8 -14
- package/assets/index.html +273 -102
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,22 +1,16 @@
|
|
|
1
1
|
# @enigmax/dashboard
|
|
2
2
|
|
|
3
3
|
The browser UI for [enigma](https://github.com/FJRG2007/enigma)'s local savings
|
|
4
|
-
dashboard: a single static page
|
|
5
|
-
(`assets/lib/chart.min.js`).
|
|
4
|
+
dashboard: a single static page plus a vendored charting library.
|
|
6
5
|
|
|
7
|
-
You do not install this directly. enigma
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
`enigma update`. enigma's loopback HTTP server serves these files and provides the
|
|
11
|
-
`/api/stats` and `/api/settings` endpoints the page talks to.
|
|
6
|
+
You do not install this directly. enigma fetches it on demand the first time you open the
|
|
7
|
+
dashboard (`enigma dashboard`) or enable it (`enigma config dashboard on`), and keeps it up
|
|
8
|
+
to date on `enigma update`.
|
|
12
9
|
|
|
13
|
-
|
|
14
|
-
dashboard never download it
|
|
15
|
-
|
|
16
|
-
The page is served only on `127.0.0.1` (never network-facing) and the settings write
|
|
17
|
-
endpoint is origin-guarded by enigma. The chart library retains its upstream Apache-2.0
|
|
18
|
-
license header as required; its attribution logo is suppressed in enigma's own CSS.
|
|
10
|
+
Shipping the UI separately keeps it out of the base `enigma-cli` package, so people who
|
|
11
|
+
never open the dashboard never download it. The dashboard runs only on your own machine
|
|
12
|
+
(`127.0.0.1`); it is never exposed to the network.
|
|
19
13
|
|
|
20
14
|
## License
|
|
21
15
|
|
|
22
|
-
Apache-2.0
|
|
16
|
+
Apache-2.0. The bundled charting library retains its upstream Apache-2.0 license header.
|
package/assets/index.html
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
<head>
|
|
4
4
|
<meta charset="utf-8">
|
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
6
|
-
<title>Enigma -
|
|
6
|
+
<title>Enigma - Dashboard</title>
|
|
7
7
|
<style>
|
|
8
8
|
:root {
|
|
9
9
|
--bg: #0b0e14; --surface: #151a23; --surface2: #1b2230; --border: #232a36;
|
|
@@ -13,13 +13,21 @@
|
|
|
13
13
|
body {
|
|
14
14
|
margin: 0; background: var(--bg); color: var(--text);
|
|
15
15
|
font-family: ui-sans-serif, system-ui, -apple-system, Segoe UI, Roboto, sans-serif;
|
|
16
|
-
-webkit-font-smoothing: antialiased; padding: 24px; max-width:
|
|
16
|
+
-webkit-font-smoothing: antialiased; padding: 24px; max-width: 1040px; margin: 0 auto;
|
|
17
17
|
}
|
|
18
18
|
a { color: var(--accent2); }
|
|
19
|
-
header { display: flex; align-items: center; gap:
|
|
19
|
+
header { display: flex; align-items: center; gap: 20px; margin-bottom: 24px; flex-wrap: wrap; }
|
|
20
|
+
.brand { display: flex; flex-direction: column; }
|
|
20
21
|
.wordmark { font-size: 20px; font-weight: 700; letter-spacing: 0.5px; }
|
|
21
22
|
.wordmark span { color: var(--accent); }
|
|
22
23
|
.sub { color: var(--muted); font-size: 13px; }
|
|
24
|
+
.tabs { display: flex; gap: 4px; }
|
|
25
|
+
.tabs .tab {
|
|
26
|
+
text-decoration: none; color: var(--muted); font-size: 13px; font-weight: 600;
|
|
27
|
+
padding: 7px 16px; border-radius: 8px; border: 1px solid transparent; transition: background .15s, color .15s, border-color .15s;
|
|
28
|
+
}
|
|
29
|
+
.tabs .tab:hover { color: var(--text); background: var(--surface); }
|
|
30
|
+
.tabs .tab.active { color: var(--accent); background: var(--surface); border-color: var(--border); }
|
|
23
31
|
.live { display: flex; align-items: center; gap: 8px; color: var(--muted); font-size: 12px; }
|
|
24
32
|
.dot { width: 8px; height: 8px; border-radius: 50%; background: var(--good); box-shadow: 0 0 8px var(--good); }
|
|
25
33
|
.dot.stale { background: var(--muted); box-shadow: none; }
|
|
@@ -76,6 +84,9 @@
|
|
|
76
84
|
.stat-line b { color: var(--text); font-weight: 600; font-variant-numeric: tabular-nums; }
|
|
77
85
|
footer { color: var(--muted); font-size: 12px; line-height: 1.6; margin-top: 8px; }
|
|
78
86
|
code { background: var(--surface2); padding: 1px 6px; border-radius: 5px; color: var(--accent2); }
|
|
87
|
+
.page-head { margin-bottom: 18px; }
|
|
88
|
+
.page-head h1 { font-size: 18px; font-weight: 700; margin: 0 0 4px; }
|
|
89
|
+
.page-head p { color: var(--muted); font-size: 13px; margin: 0; max-width: 720px; line-height: 1.5; }
|
|
79
90
|
.set-cat { margin-bottom: 18px; }
|
|
80
91
|
.set-cat:last-child { margin-bottom: 0; }
|
|
81
92
|
.set-cat-h { color: var(--accent); font-size: 12px; text-transform: uppercase; letter-spacing: 0.6px; margin-bottom: 4px; }
|
|
@@ -97,15 +108,36 @@
|
|
|
97
108
|
border-radius: 7px; padding: 5px 10px; font-size: 12px; font-family: inherit; cursor: pointer;
|
|
98
109
|
}
|
|
99
110
|
.set-note { color: var(--accent); font-size: 12px; min-height: 16px; margin-top: 10px; }
|
|
100
|
-
|
|
111
|
+
.tag {
|
|
112
|
+
display: inline-block; font-size: 11px; color: var(--muted); background: var(--surface2);
|
|
113
|
+
border: 1px solid var(--border); border-radius: 6px; padding: 1px 7px; margin-left: 6px; vertical-align: middle;
|
|
114
|
+
}
|
|
115
|
+
.tag.ext { color: var(--accent2); }
|
|
116
|
+
.tag.warn { color: var(--accent); border-color: var(--accent); }
|
|
117
|
+
.editor {
|
|
118
|
+
width: 100%; min-height: 360px; resize: vertical; background: var(--surface2); color: var(--text);
|
|
119
|
+
border: 1px solid var(--border); border-radius: 8px; padding: 12px;
|
|
120
|
+
font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; font-size: 12px; line-height: 1.5;
|
|
121
|
+
}
|
|
122
|
+
.btn-danger {
|
|
123
|
+
background: var(--surface2); color: var(--accent2); border: 1px solid var(--border);
|
|
124
|
+
border-radius: 7px; padding: 5px 14px; font-size: 12px; cursor: pointer; font-family: inherit;
|
|
125
|
+
}
|
|
126
|
+
.btn-danger:hover { border-color: var(--accent2); }
|
|
127
|
+
@media (max-width: 720px) { .grid { grid-template-columns: repeat(2, 1fr); } header { gap: 12px; } }
|
|
101
128
|
</style>
|
|
102
129
|
</head>
|
|
103
130
|
<body>
|
|
104
131
|
<header>
|
|
105
|
-
<div>
|
|
132
|
+
<div class="brand">
|
|
106
133
|
<div class="wordmark">Enigma<span>.</span></div>
|
|
107
|
-
<div class="sub">
|
|
134
|
+
<div class="sub">Savings dashboard</div>
|
|
108
135
|
</div>
|
|
136
|
+
<nav class="tabs">
|
|
137
|
+
<a href="#/" data-view="savings" class="tab active">Savings</a>
|
|
138
|
+
<a href="#/skills" data-view="skills" class="tab">Skills</a>
|
|
139
|
+
<a href="#/settings" data-view="settings" class="tab">Settings</a>
|
|
140
|
+
</nav>
|
|
109
141
|
<div class="nav">
|
|
110
142
|
<span class="live"><span id="dot" class="dot stale"></span><span id="updated">Connecting...</span></span>
|
|
111
143
|
<nav class="links">
|
|
@@ -116,112 +148,140 @@
|
|
|
116
148
|
<svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><path d="M20.317 4.3698a19.7913 19.7913 0 0 0-4.8851-1.5152.0741.0741 0 0 0-.0785.0371c-.211.3753-.4447.8648-.6083 1.2495-1.8447-.2762-3.68-.2762-5.4868 0-.1636-.3933-.4058-.8742-.6177-1.2495a.077.077 0 0 0-.0785-.037 19.7363 19.7363 0 0 0-4.8852 1.515.0699.0699 0 0 0-.0321.0277C.5334 9.0458-.319 13.5799.0992 18.0578a.0824.0824 0 0 0 .0312.0561c2.0528 1.5076 4.0413 2.4228 5.9929 3.0294a.0777.0777 0 0 0 .0842-.0276c.4616-.6304.8731-1.2952 1.226-1.9942a.076.076 0 0 0-.0416-.1057c-.6528-.2476-1.2743-.5495-1.8722-.8923a.077.077 0 0 1-.0076-.1277c.1258-.0943.2517-.1923.3718-.2914a.0743.0743 0 0 1 .0776-.0105c3.9278 1.7933 8.18 1.7933 12.0614 0a.0739.0739 0 0 1 .0785.0095c.1202.099.246.1981.3728.2924a.077.077 0 0 1-.0066.1276 12.2986 12.2986 0 0 1-1.873.8914.0766.0766 0 0 0-.0407.1067c.3604.698.7719 1.3628 1.225 1.9932a.076.076 0 0 0 .0842.0286c1.961-.6067 3.9495-1.5219 6.0023-3.0294a.077.077 0 0 0 .0313-.0552c.5004-5.177-.8382-9.6739-3.5485-13.6604a.061.061 0 0 0-.0312-.0286ZM8.02 15.3312c-1.1825 0-2.1569-1.0857-2.1569-2.419 0-1.3332.9555-2.4189 2.157-2.4189 1.2108 0 2.1757 1.0952 2.1568 2.419 0 1.3332-.9555 2.4189-2.1569 2.4189Zm7.9748 0c-1.1825 0-2.1569-1.0857-2.1569-2.419 0-1.3332.9554-2.4189 2.1569-2.4189 1.2108 0 2.1757 1.0952 2.1568 2.419 0 1.3332-.9461 2.4189-2.1568 2.4189Z"/></svg>
|
|
117
149
|
</a>
|
|
118
150
|
<a href="https://ko-fi.com/fjrg2007" target="_blank" rel="noopener noreferrer" title="Support on Ko-fi" aria-label="Support on Ko-fi">
|
|
119
|
-
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M17 8h1a4 4 0 1 1 0 8h-1"/><path d="M3 8h14v9a4 4 0 0 1-4 4H7a4 4 0 0 1-4-4Z"/><line x1="6" y1="2" x2="6" y2="4"/><line x1="
|
|
151
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M17 8h1a4 4 0 1 1 0 8h-1"/><path d="M3 8h14v9a4 4 0 0 1-4 4H7a4 4 0 0 1-4-4Z"/><line x1="6" y1="2" x2="6" y2="4"/><line x1="14" y1="2" x2="14" y2="4"/></svg>
|
|
120
152
|
</a>
|
|
121
153
|
</nav>
|
|
122
154
|
</div>
|
|
123
155
|
</header>
|
|
124
156
|
|
|
125
|
-
<
|
|
126
|
-
<div class="
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
<div class="panel
|
|
137
|
-
<
|
|
138
|
-
|
|
139
|
-
<div class="
|
|
140
|
-
<
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
<
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
<
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
157
|
+
<main id="view-savings">
|
|
158
|
+
<div class="grid">
|
|
159
|
+
<div class="card"><div class="label">Money Saved</div><div id="money" class="value good">-</div><div id="moneyFoot" class="foot">Estimated, by source</div></div>
|
|
160
|
+
<div class="card"><div class="label">Time Saved</div><div id="time" class="value accent">-</div><div id="timeFoot" class="foot">Estimated prefill time</div></div>
|
|
161
|
+
<div class="card"><div class="label">Tokens Saved</div><div id="saved" class="value">-</div><div id="savedFoot" class="foot">Across all compressions</div></div>
|
|
162
|
+
<div class="card"><div class="label">Savings Rate</div><div id="rate" class="value accent">-</div><div class="foot">Of input tokens removed</div></div>
|
|
163
|
+
<div class="card"><div class="label">Compressions</div><div id="calls" class="value">-</div><div class="foot">Reversible via CCR</div></div>
|
|
164
|
+
<div class="card"><div class="label">Avg / Compression</div><div id="avg" class="value">-</div><div class="foot">Tokens saved per call</div></div>
|
|
165
|
+
<div class="card"><div class="label">Best Compression</div><div id="best" class="value">-</div><div class="foot">Most saved in one call</div></div>
|
|
166
|
+
</div>
|
|
167
|
+
|
|
168
|
+
<div class="panel">
|
|
169
|
+
<div class="panel-head">
|
|
170
|
+
<h2>Savings Per Day <small id="rangeLabel"></small></h2>
|
|
171
|
+
<div class="ctrls">
|
|
172
|
+
<div class="ranges" id="metricToggle">
|
|
173
|
+
<button type="button" data-m="tokens" class="active">Tokens</button>
|
|
174
|
+
<button type="button" data-m="usd">$</button>
|
|
175
|
+
</div>
|
|
176
|
+
<div class="ranges" id="modeToggle">
|
|
177
|
+
<button type="button" data-o="daily" class="active">Daily</button>
|
|
178
|
+
<button type="button" data-o="cumulative">Cumulative</button>
|
|
179
|
+
</div>
|
|
180
|
+
<div class="ranges" id="ranges">
|
|
181
|
+
<button type="button" data-d="7">7D</button>
|
|
182
|
+
<button type="button" data-d="30" class="active">30D</button>
|
|
183
|
+
<button type="button" data-d="90">90D</button>
|
|
184
|
+
<button type="button" data-d="0">All</button>
|
|
185
|
+
</div>
|
|
152
186
|
</div>
|
|
153
187
|
</div>
|
|
188
|
+
<div id="chartWrap" class="chartwrap"></div>
|
|
189
|
+
<div id="chartEmpty" class="empty" style="display:none">No compression activity recorded yet. Run <code>enigma compress <file></code> or enable the compression MCP (<code>enigma config compress on</code>).</div>
|
|
190
|
+
</div>
|
|
191
|
+
|
|
192
|
+
<div class="panel" id="usagePanel" style="display:none">
|
|
193
|
+
<h2 style="margin-bottom:4px">Real Tool Usage <small id="usageSub">Claude Code transcripts</small></h2>
|
|
194
|
+
<div class="sub" style="margin-bottom:14px">Measured token consumption and real prompt-cache savings, read-only from your own session logs. Deliberately not attributed to skills or output-style: a transcript has no counterfactual baseline, so any such number would be invented.</div>
|
|
195
|
+
<div class="grid" style="margin-bottom:16px">
|
|
196
|
+
<div class="card"><div class="label">Cache Saved</div><div id="uCacheMoney" class="value good">-</div><div class="foot">Est. from prompt caching</div></div>
|
|
197
|
+
<div class="card"><div class="label">Cache Reads</div><div id="uCacheRead" class="value accent">-</div><div class="foot">Tokens served from cache</div></div>
|
|
198
|
+
<div class="card"><div class="label">Input Tokens</div><div id="uInput" class="value">-</div><div class="foot">Consumed across sessions</div></div>
|
|
199
|
+
<div class="card"><div class="label">Output Tokens</div><div id="uOutput" class="value">-</div><div class="foot">Generated by the agent</div></div>
|
|
200
|
+
<div class="card"><div class="label">Sessions</div><div id="uSessions" class="value">-</div><div id="uSessionsFoot" class="foot">Transcripts scanned</div></div>
|
|
201
|
+
</div>
|
|
202
|
+
<h2 style="margin:0 0 10px">By Model</h2>
|
|
203
|
+
<div id="uByModel"></div>
|
|
204
|
+
</div>
|
|
205
|
+
|
|
206
|
+
<div class="panel" id="usageHint" style="display:none">
|
|
207
|
+
<h2 style="margin-bottom:8px">Real Tool Usage <small>opt-in</small></h2>
|
|
208
|
+
<div class="sub">Show real token consumption and prompt-cache savings from your Claude Code sessions in <a href="#/settings">Settings</a> (Real tool-usage stats). enigma reads only your local session transcripts; nothing is sent anywhere.</div>
|
|
209
|
+
</div>
|
|
210
|
+
|
|
211
|
+
<div class="panel">
|
|
212
|
+
<h2 style="margin-bottom:14px">Savings By Source <small>which app/CLI compressed</small></h2>
|
|
213
|
+
<div id="sources"></div>
|
|
214
|
+
</div>
|
|
215
|
+
|
|
216
|
+
<div class="panel">
|
|
217
|
+
<h2 style="margin-bottom:14px">Savings By Content Type <small>what enigma compressed</small></h2>
|
|
218
|
+
<div id="types"></div>
|
|
154
219
|
</div>
|
|
155
|
-
|
|
156
|
-
<div
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
<div class="sub" style="margin-bottom:14px">Measured token consumption and real prompt-cache savings, read-only from your own session logs. Deliberately not attributed to skills or output-style: a transcript has no counterfactual baseline, so any such number would be invented.</div>
|
|
162
|
-
<div class="grid" style="margin-bottom:16px">
|
|
163
|
-
<div class="card"><div class="label">Cache Saved</div><div id="uCacheMoney" class="value good">-</div><div class="foot">Est. from prompt caching</div></div>
|
|
164
|
-
<div class="card"><div class="label">Cache Reads</div><div id="uCacheRead" class="value accent">-</div><div class="foot">Tokens served from cache</div></div>
|
|
165
|
-
<div class="card"><div class="label">Input Tokens</div><div id="uInput" class="value">-</div><div class="foot">Consumed across sessions</div></div>
|
|
166
|
-
<div class="card"><div class="label">Output Tokens</div><div id="uOutput" class="value">-</div><div class="foot">Generated by the agent</div></div>
|
|
167
|
-
<div class="card"><div class="label">Sessions</div><div id="uSessions" class="value">-</div><div id="uSessionsFoot" class="foot">Transcripts scanned</div></div>
|
|
220
|
+
|
|
221
|
+
<div class="panel">
|
|
222
|
+
<h2 style="margin-bottom:14px">Before vs. After <small id="ratioLabel">cumulative tokens</small></h2>
|
|
223
|
+
<div class="bar-row"><span class="name">Before</span><div class="bar-track"><div id="barBefore" class="bar-fill" style="background:var(--accent2)"></div></div><span id="numBefore" class="num">-</span></div>
|
|
224
|
+
<div class="bar-row"><span class="name">After</span><div class="bar-track"><div id="barAfter" class="bar-fill" style="background:var(--good)"></div></div><span id="numAfter" class="num">-</span></div>
|
|
225
|
+
<div class="stat-line" style="margin:14px 0 0"><div>Removed <b id="removed">-</b></div><div>Reduction <b id="reduction">-</b></div><div>Compression ratio <b id="ratio">-</b></div></div>
|
|
168
226
|
</div>
|
|
169
|
-
|
|
170
|
-
<div
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
<h2 style="margin-bottom:14px">Savings By Source <small>which app/CLI compressed</small></h2>
|
|
180
|
-
<div id="sources"></div>
|
|
181
|
-
</div>
|
|
182
|
-
|
|
183
|
-
<div class="panel">
|
|
184
|
-
<h2 style="margin-bottom:14px">Savings By Content Type <small>what enigma compressed</small></h2>
|
|
185
|
-
<div id="types"></div>
|
|
186
|
-
</div>
|
|
187
|
-
|
|
188
|
-
<div class="panel">
|
|
189
|
-
<h2 style="margin-bottom:14px">Before vs. After <small id="ratioLabel">cumulative tokens</small></h2>
|
|
190
|
-
<div class="bar-row"><span class="name">Before</span><div class="bar-track"><div id="barBefore" class="bar-fill" style="background:var(--accent2)"></div></div><span id="numBefore" class="num">-</span></div>
|
|
191
|
-
<div class="bar-row"><span class="name">After</span><div class="bar-track"><div id="barAfter" class="bar-fill" style="background:var(--good)"></div></div><span id="numAfter" class="num">-</span></div>
|
|
192
|
-
<div class="stat-line" style="margin:14px 0 0"><div>Removed <b id="removed">-</b></div><div>Reduction <b id="reduction">-</b></div><div>Compression ratio <b id="ratio">-</b></div></div>
|
|
193
|
-
</div>
|
|
194
|
-
|
|
195
|
-
<div class="panel">
|
|
196
|
-
<div class="panel-head">
|
|
197
|
-
<h2>Savings History <small id="histStats"></small></h2>
|
|
198
|
-
<div class="ctrls">
|
|
199
|
-
<div class="ranges" id="histToggle">
|
|
200
|
-
<button type="button" data-p="day" class="active">Daily</button>
|
|
201
|
-
<button type="button" data-p="week">Weekly</button>
|
|
202
|
-
<button type="button" data-p="month">Monthly</button>
|
|
227
|
+
|
|
228
|
+
<div class="panel">
|
|
229
|
+
<div class="panel-head">
|
|
230
|
+
<h2>Savings History <small id="histStats"></small></h2>
|
|
231
|
+
<div class="ctrls">
|
|
232
|
+
<div class="ranges" id="histToggle">
|
|
233
|
+
<button type="button" data-p="day" class="active">Daily</button>
|
|
234
|
+
<button type="button" data-p="week">Weekly</button>
|
|
235
|
+
<button type="button" data-p="month">Monthly</button>
|
|
236
|
+
</div>
|
|
203
237
|
</div>
|
|
204
238
|
</div>
|
|
239
|
+
<div class="tbl-wrap"><div id="histBody"></div></div>
|
|
240
|
+
</div>
|
|
241
|
+
|
|
242
|
+
<div class="panel">
|
|
243
|
+
<h2 style="margin-bottom:14px">Recent Compressions <small>newest first</small></h2>
|
|
244
|
+
<div class="tbl-wrap"><div id="recent"></div></div>
|
|
245
|
+
</div>
|
|
246
|
+
|
|
247
|
+
<div class="panel">
|
|
248
|
+
<h2 style="margin-bottom:14px">Reversible Cache (CCR) <small>recoverable originals on disk</small></h2>
|
|
249
|
+
<div id="cache"></div>
|
|
250
|
+
</div>
|
|
251
|
+
</main>
|
|
252
|
+
|
|
253
|
+
<section id="view-skills" style="display:none">
|
|
254
|
+
<div class="page-head" style="display:flex;align-items:flex-start;gap:16px">
|
|
255
|
+
<div style="flex:1">
|
|
256
|
+
<h1>Skills</h1>
|
|
257
|
+
<p>The skills installed in your agents - enigma's own and any external ones you added. Edit a skill's content, disable/enable or remove it, and check whether enigma's skills are up to date.</p>
|
|
258
|
+
</div>
|
|
259
|
+
<button id="skillsCheck" type="button" class="toggle on" style="white-space:nowrap">Check for updates</button>
|
|
260
|
+
</div>
|
|
261
|
+
<div id="skillEditor" class="panel" style="display:none">
|
|
262
|
+
<div class="panel-head"><h2>Editing <span id="seName"></span> <small id="seWarn"></small></h2></div>
|
|
263
|
+
<textarea id="seText" class="editor" spellcheck="false"></textarea>
|
|
264
|
+
<div style="margin-top:10px;display:flex;gap:8px">
|
|
265
|
+
<button id="seSave" type="button" class="toggle on">Save</button>
|
|
266
|
+
<button id="seCancel" type="button" class="toggle">Cancel</button>
|
|
267
|
+
</div>
|
|
205
268
|
</div>
|
|
206
|
-
<div class="
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
<div id="settingsBody"><div class="empty" style="padding:24px 0">Loading settings...</div></div>
|
|
223
|
-
<div id="settingsNote" class="set-note"></div>
|
|
224
|
-
</div>
|
|
269
|
+
<div class="panel">
|
|
270
|
+
<div id="skillsBody"><div class="empty" style="padding:24px 0">Loading skills...</div></div>
|
|
271
|
+
<div id="skillsNote" class="set-note"></div>
|
|
272
|
+
</div>
|
|
273
|
+
</section>
|
|
274
|
+
|
|
275
|
+
<section id="view-settings" style="display:none">
|
|
276
|
+
<div class="page-head">
|
|
277
|
+
<h1>Settings</h1>
|
|
278
|
+
<p>Everything you can configure with <code>enigma config</code> or the terminal UI, editable here. Changes apply at global scope and take effect immediately; toggles that change agent memory need an agent restart.</p>
|
|
279
|
+
</div>
|
|
280
|
+
<div class="panel">
|
|
281
|
+
<div id="settingsBody"><div class="empty" style="padding:24px 0">Loading settings...</div></div>
|
|
282
|
+
<div id="settingsNote" class="set-note"></div>
|
|
283
|
+
</div>
|
|
284
|
+
</section>
|
|
225
285
|
|
|
226
286
|
<footer>
|
|
227
287
|
Polling pauses automatically when this tab is hidden, and the server caches each snapshot - so an open dashboard costs almost nothing.
|
|
@@ -579,7 +639,7 @@
|
|
|
579
639
|
}
|
|
580
640
|
}
|
|
581
641
|
|
|
582
|
-
// --- settings
|
|
642
|
+
// --- settings subpage (mirrors the TUI registry over /api/settings) ---
|
|
583
643
|
function esc(s) { return String(s).replace(/[&<>"]/g, (c) => ({ "&": "&", "<": "<", ">": ">", '"': """ }[c])); }
|
|
584
644
|
|
|
585
645
|
function settingRow(s) {
|
|
@@ -627,6 +687,7 @@
|
|
|
627
687
|
});
|
|
628
688
|
}
|
|
629
689
|
|
|
690
|
+
let settingsLoaded = false;
|
|
630
691
|
async function loadSettings() {
|
|
631
692
|
try {
|
|
632
693
|
const res = await fetch("/api/settings", { cache: "no-store" });
|
|
@@ -634,12 +695,122 @@
|
|
|
634
695
|
} catch { $("settingsBody").innerHTML = '<div class="empty" style="padding:24px 0">Settings unavailable.</div>'; }
|
|
635
696
|
}
|
|
636
697
|
|
|
698
|
+
// --- skills subpage (lists enigma + external skills over /api/skills) ---
|
|
699
|
+
function skillRow(s) {
|
|
700
|
+
const ver = s.version ? '<span class="tag">v' + esc(s.version) + "</span>" : "";
|
|
701
|
+
const where = s.agents && s.agents.length ? '<span class="tag">' + s.agents.map(esc).join(", ") + "</span>"
|
|
702
|
+
: (s.discarded ? '<span class="tag">disabled</span>' : '<span class="tag">not deployed</span>');
|
|
703
|
+
const ext = s.source === "external" ? '<span class="tag ext">external</span>' : "";
|
|
704
|
+
const upd = s.source !== "enigma" ? ""
|
|
705
|
+
: s.update === "update" ? '<span class="tag warn">update available</span>'
|
|
706
|
+
: s.update === "modified" ? '<span class="tag ext">modified</span>' : "";
|
|
707
|
+
const edit = '<button type="button" class="toggle" data-name="' + esc(s.name) + '" data-action="edit" data-enigma="' + (s.source === "enigma" ? 1 : 0) + '">Edit</button>';
|
|
708
|
+
const main = s.source === "enigma"
|
|
709
|
+
? '<button type="button" class="toggle' + (s.discarded ? "" : " on") + '" data-name="' + esc(s.name)
|
|
710
|
+
+ '" data-action="' + (s.discarded ? "enable" : "disable") + '">' + (s.discarded ? "Disabled" : "Enabled") + "</button>"
|
|
711
|
+
: '<button type="button" class="btn-danger" data-name="' + esc(s.name) + '" data-action="remove">Remove</button>';
|
|
712
|
+
return '<div class="set-row"><div class="set-meta"><div class="set-name">' + esc(s.name) + ext + ver + upd + where + "</div>"
|
|
713
|
+
+ '<div class="set-hint">' + esc(s.description || "") + '</div></div><div class="set-ctl" style="display:flex;gap:8px">' + edit + main + "</div></div>";
|
|
714
|
+
}
|
|
715
|
+
|
|
716
|
+
function renderSkills(skills) {
|
|
717
|
+
const enigma = skills.filter((s) => s.source === "enigma");
|
|
718
|
+
const ext = skills.filter((s) => s.source === "external");
|
|
719
|
+
const section = (title, blurb, list, emptyText) => '<div class="set-cat"><div class="set-cat-h">' + title + "</div>"
|
|
720
|
+
+ '<div class="set-cat-b">' + blurb + "</div>"
|
|
721
|
+
+ (list.length ? list.map(skillRow).join("") : '<div class="empty" style="padding:16px 0">' + emptyText + "</div>") + "</div>";
|
|
722
|
+
$("skillsBody").innerHTML =
|
|
723
|
+
section("Enigma skills", "Distributed and kept current by enigma. Disable to remove from your agents; enable to restore.", enigma, "No enigma skills.")
|
|
724
|
+
+ section("External skills", "Skills enigma did not author, found in your agents. Remove deletes the skill from every agent that has it.", ext, "No external skills found.");
|
|
725
|
+
}
|
|
726
|
+
|
|
727
|
+
async function postSkill(name, action) {
|
|
728
|
+
$("skillsNote").textContent = "Working...";
|
|
729
|
+
try {
|
|
730
|
+
const res = await fetch("/api/skills", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ name, action }) });
|
|
731
|
+
const out = await res.json();
|
|
732
|
+
if (!out.ok) { $("skillsNote").textContent = "Could not " + action + " " + name + ": " + (out.error || "error"); return; }
|
|
733
|
+
if (out.skills) renderSkills(out.skills);
|
|
734
|
+
$("skillsNote").textContent = out.note || ("Done: " + name);
|
|
735
|
+
} catch { $("skillsNote").textContent = "Could not reach the server to change " + name + "."; }
|
|
736
|
+
}
|
|
737
|
+
|
|
738
|
+
// Inline SKILL.md editor (one skill at a time).
|
|
739
|
+
let editing = null;
|
|
740
|
+
async function openEditor(name, isEnigma) {
|
|
741
|
+
$("skillsNote").textContent = "Loading " + name + "...";
|
|
742
|
+
try {
|
|
743
|
+
const res = await fetch("/api/skills", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ name, action: "read" }) });
|
|
744
|
+
const out = await res.json();
|
|
745
|
+
if (!out.ok) { $("skillsNote").textContent = "Could not open " + name + ": " + (out.error || "error"); return; }
|
|
746
|
+
editing = name;
|
|
747
|
+
$("seName").textContent = name;
|
|
748
|
+
$("seWarn").textContent = isEnigma ? "managed by enigma - local edits revert on the next update/sync" : "external skill";
|
|
749
|
+
$("seText").value = out.content || "";
|
|
750
|
+
$("skillEditor").style.display = "";
|
|
751
|
+
$("skillsNote").textContent = "";
|
|
752
|
+
window.scrollTo({ top: 0, behavior: "smooth" });
|
|
753
|
+
$("seText").focus();
|
|
754
|
+
} catch { $("skillsNote").textContent = "Could not reach the server."; }
|
|
755
|
+
}
|
|
756
|
+
function closeEditor() { editing = null; $("skillEditor").style.display = "none"; }
|
|
757
|
+
async function saveEditor() {
|
|
758
|
+
if (!editing) return;
|
|
759
|
+
$("skillsNote").textContent = "Saving " + editing + "...";
|
|
760
|
+
try {
|
|
761
|
+
const res = await fetch("/api/skills", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ name: editing, action: "save", content: $("seText").value }) });
|
|
762
|
+
const out = await res.json();
|
|
763
|
+
if (!out.ok) { $("skillsNote").textContent = "Could not save " + editing + ": " + (out.error || "error"); return; }
|
|
764
|
+
if (out.skills) renderSkills(out.skills);
|
|
765
|
+
$("skillsNote").textContent = out.note || "Saved.";
|
|
766
|
+
closeEditor();
|
|
767
|
+
} catch { $("skillsNote").textContent = "Could not reach the server."; }
|
|
768
|
+
}
|
|
769
|
+
|
|
770
|
+
function wireSkills() {
|
|
771
|
+
$("skillsBody").addEventListener("click", (e) => {
|
|
772
|
+
const b = e.target.closest("button[data-action]");
|
|
773
|
+
if (!b) return;
|
|
774
|
+
const { name, action } = b.dataset;
|
|
775
|
+
if (action === "edit") { openEditor(name, b.dataset.enigma === "1"); return; }
|
|
776
|
+
if (action === "remove" && !confirm('Remove the external skill "' + name + '" from all agents? This deletes it from disk.')) return;
|
|
777
|
+
postSkill(name, action);
|
|
778
|
+
});
|
|
779
|
+
$("seSave").addEventListener("click", saveEditor);
|
|
780
|
+
$("seCancel").addEventListener("click", closeEditor);
|
|
781
|
+
$("skillsCheck").addEventListener("click", () => postSkill("*", "check-updates"));
|
|
782
|
+
}
|
|
783
|
+
|
|
784
|
+
let skillsLoaded = false;
|
|
785
|
+
async function loadSkills() {
|
|
786
|
+
try {
|
|
787
|
+
const res = await fetch("/api/skills", { cache: "no-store" });
|
|
788
|
+
renderSkills((await res.json()).skills || []);
|
|
789
|
+
} catch { $("skillsBody").innerHTML = '<div class="empty" style="padding:24px 0">Skills unavailable.</div>'; }
|
|
790
|
+
}
|
|
791
|
+
|
|
792
|
+
// --- hash routing between the Savings, Skills and Settings subpages ---
|
|
793
|
+
function route() {
|
|
794
|
+
const h = (location.hash || "").replace(/^#\/?/, "");
|
|
795
|
+
const v = (h === "settings" || h === "skills") ? h : "savings";
|
|
796
|
+
$("view-savings").style.display = v === "savings" ? "" : "none";
|
|
797
|
+
$("view-skills").style.display = v === "skills" ? "" : "none";
|
|
798
|
+
$("view-settings").style.display = v === "settings" ? "" : "none";
|
|
799
|
+
document.querySelectorAll(".tab").forEach((t) => t.classList.toggle("active", t.dataset.view === v));
|
|
800
|
+
if (v === "settings" && !settingsLoaded) { settingsLoaded = true; loadSettings(); }
|
|
801
|
+
if (v === "skills" && !skillsLoaded) { skillsLoaded = true; loadSkills(); }
|
|
802
|
+
// The chart was sized while its view may have been hidden; nudge it on return.
|
|
803
|
+
if (v === "savings" && chart) { try { applyRange(); } catch { /* not ready */ } }
|
|
804
|
+
}
|
|
805
|
+
|
|
637
806
|
initChart();
|
|
638
807
|
poll();
|
|
639
808
|
setInterval(poll, POLL_MS);
|
|
640
809
|
document.addEventListener("visibilitychange", () => { if (!document.hidden) poll(); });
|
|
641
810
|
wireSettings();
|
|
642
|
-
|
|
811
|
+
wireSkills();
|
|
812
|
+
window.addEventListener("hashchange", route);
|
|
813
|
+
route();
|
|
643
814
|
</script>
|
|
644
815
|
</body>
|
|
645
816
|
</html>
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@enigmax/dashboard",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.2",
|
|
4
4
|
"description": "Local browser dashboard UI for enigma: the static page and chart assets enigma serves on its loopback dashboard (savings, real tool usage, in-browser settings). Installed on demand by enigma-cli; not a runtime dependency.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"files": [
|