kryptheon 0.1.31 → 0.1.32
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/kryptheon-dashboard-page.js +91 -14
- package/kryptheon-dashboard.html +17 -3
- package/kryptheon-dashboard.js +42 -0
- package/package.json +1 -1
|
@@ -103,10 +103,24 @@
|
|
|
103
103
|
busy = on;
|
|
104
104
|
Array.prototype.forEach.call(document.querySelectorAll('[data-action]'), function (b) { b.disabled = on; });
|
|
105
105
|
}
|
|
106
|
+
// The clipboard, and when a browser refuses it, the older way: the text
|
|
107
|
+
// put in a box off the screen, selected and copied.
|
|
106
108
|
function copy(text, done) {
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
109
|
+
function older() {
|
|
110
|
+
var box = el('textarea');
|
|
111
|
+
box.value = text;
|
|
112
|
+
box.setAttribute('readonly', '');
|
|
113
|
+
box.style.position = 'fixed';
|
|
114
|
+
box.style.left = '-9999px';
|
|
115
|
+
document.body.appendChild(box);
|
|
116
|
+
box.select();
|
|
117
|
+
var ok = false;
|
|
118
|
+
try { ok = document.execCommand('copy'); } catch (e) { ok = false; }
|
|
119
|
+
document.body.removeChild(box);
|
|
120
|
+
toast(ok ? done : 'Could not copy - select the text instead');
|
|
121
|
+
}
|
|
122
|
+
if (!navigator.clipboard || !navigator.clipboard.writeText) return older();
|
|
123
|
+
navigator.clipboard.writeText(text).then(function () { toast(done); }, older);
|
|
110
124
|
}
|
|
111
125
|
function button(cls, iconName, text, onClick) {
|
|
112
126
|
var b = el('button', 'btn' + (cls ? ' ' + cls : ''));
|
|
@@ -155,33 +169,70 @@
|
|
|
155
169
|
// lines are what the command printed, already scrubbed of any secret.
|
|
156
170
|
var job = null;
|
|
157
171
|
var lastRecheck = null;
|
|
158
|
-
|
|
172
|
+
// Whether the printed lines are shown: null follows the job - shown while it
|
|
173
|
+
// runs or when it failed, folded away once it finished well - until the
|
|
174
|
+
// person opens or folds them.
|
|
175
|
+
var linesOpen = null;
|
|
176
|
+
// Whether the one prompt for all findings is shown in full.
|
|
177
|
+
var allPromptOpen = false;
|
|
178
|
+
|
|
179
|
+
// The panel sits in the corner of the window, over the page; the page gets
|
|
180
|
+
// room at its foot so nothing stays hidden under it.
|
|
159
181
|
function renderJob() {
|
|
160
182
|
var box = byId('job');
|
|
161
183
|
clear(box);
|
|
162
|
-
|
|
184
|
+
var main = document.querySelector('main');
|
|
185
|
+
var root = document.documentElement;
|
|
186
|
+
if (!job) { box.hidden = true; linesOpen = null; main.style.paddingBottom = ''; root.style.removeProperty('--job-room'); return; }
|
|
163
187
|
box.hidden = false;
|
|
164
|
-
|
|
188
|
+
var ok = !!(job.result && job.result.ok);
|
|
189
|
+
box.className = 'job' + (job.running ? ' on' : ok ? ' ok' : ' bad');
|
|
165
190
|
var head = el('div', 'job-head');
|
|
166
191
|
if (job.running) head.appendChild(el('span', 'spin'));
|
|
167
192
|
head.appendChild(el('b', null, job.title));
|
|
168
|
-
head.appendChild(el('span', 'tag ' + (job.running ? 'sun' :
|
|
169
|
-
job.running ? 'running' :
|
|
193
|
+
head.appendChild(el('span', 'tag ' + (job.running ? 'sun' : ok ? 'leaf' : 'ember'),
|
|
194
|
+
job.running ? 'running' : ok ? 'finished' : 'did not finish'));
|
|
170
195
|
if (!job.running) {
|
|
171
|
-
var close = el('button', 'job-close', '
|
|
196
|
+
var close = el('button', 'job-close', 'Close');
|
|
172
197
|
close.type = 'button';
|
|
173
198
|
close.addEventListener('click', function () { job = null; renderJob(); });
|
|
174
199
|
head.appendChild(close);
|
|
175
200
|
}
|
|
176
201
|
box.appendChild(head);
|
|
177
|
-
var
|
|
178
|
-
|
|
202
|
+
var open = linesOpen === null ? job.running || !ok : linesOpen;
|
|
203
|
+
var lines = null;
|
|
204
|
+
if (open) {
|
|
205
|
+
lines = el('pre', 'job-lines', (job.lines || []).filter(function (l, i, all) { return l || (i && all[i - 1]); }).slice(-60).join('\n') || 'Starting...');
|
|
206
|
+
box.appendChild(lines);
|
|
207
|
+
}
|
|
179
208
|
if (!job.running && job.result) {
|
|
180
209
|
var r = job.result;
|
|
181
|
-
box.appendChild(el('div', 'job-result',
|
|
210
|
+
box.appendChild(el('div', 'job-result', ok ? 'Result: ' + (r.summary || 'done') : 'It did not finish: ' + (r.why || 'see what it printed')));
|
|
211
|
+
}
|
|
212
|
+
if (!job.running) {
|
|
213
|
+
var acts = el('div', 'job-acts');
|
|
214
|
+
// What it found is on Findings; a recording is on Runs. Setting the
|
|
215
|
+
// nightly check up, or removing it, finds nothing to go and see.
|
|
216
|
+
var setUp = job.kind === 'db' && / (un)?install$/.test(job.command || '');
|
|
217
|
+
var next = !ok || setUp ? null : job.kind === 'record' ? { id: 'runs', label: 'See runs' } : { id: 'findings', label: 'See findings' };
|
|
218
|
+
if (next && currentView() !== next.id) {
|
|
219
|
+
var goBtn = el('button', 'go', next.label + ' →');
|
|
220
|
+
goBtn.type = 'button';
|
|
221
|
+
goBtn.addEventListener('click', function () { go(next.id); });
|
|
222
|
+
acts.appendChild(goBtn);
|
|
223
|
+
}
|
|
224
|
+
var fold = el('button', null, open ? 'Hide what it printed' : 'Show what it printed');
|
|
225
|
+
fold.type = 'button';
|
|
226
|
+
fold.setAttribute('aria-expanded', open ? 'true' : 'false');
|
|
227
|
+
fold.addEventListener('click', function () { linesOpen = !open; renderJob(); });
|
|
228
|
+
acts.appendChild(fold);
|
|
229
|
+
box.appendChild(acts);
|
|
182
230
|
}
|
|
183
231
|
if (job.command) box.appendChild(el('div', 'fallback', 'The same in a terminal: ' + job.command));
|
|
184
|
-
lines.scrollTop = lines.scrollHeight;
|
|
232
|
+
if (lines) lines.scrollTop = lines.scrollHeight;
|
|
233
|
+
main.style.paddingBottom = (box.offsetHeight + 48) + 'px';
|
|
234
|
+
// A message pops up above the panel, never over it.
|
|
235
|
+
root.style.setProperty('--job-room', (box.offsetHeight + 12) + 'px');
|
|
185
236
|
}
|
|
186
237
|
|
|
187
238
|
function getJob() {
|
|
@@ -194,6 +245,7 @@
|
|
|
194
245
|
setBusy(true);
|
|
195
246
|
return post(path, body).then(function (started) {
|
|
196
247
|
job = started.job;
|
|
248
|
+
linesOpen = null;
|
|
197
249
|
renderJob();
|
|
198
250
|
return follow();
|
|
199
251
|
}).then(function (done) {
|
|
@@ -255,6 +307,8 @@
|
|
|
255
307
|
byId('viewTitle').textContent = view.title;
|
|
256
308
|
document.title = (id === 'dashboard' ? '' : view.title + ' - ') + 'Kryptheon';
|
|
257
309
|
if (state) renderSide();
|
|
310
|
+
// Its "See findings" goes once Findings is the page on screen.
|
|
311
|
+
if (job && !job.running) renderJob();
|
|
258
312
|
window.scrollTo(0, 0);
|
|
259
313
|
}
|
|
260
314
|
window.addEventListener('hashchange', showView);
|
|
@@ -528,7 +582,7 @@
|
|
|
528
582
|
if (!connection) { toast('Paste your connection string in "Connect your database" first.'); dbInput.focus(); return; }
|
|
529
583
|
runJob('/api/db', { action: 'night', connection: connection }).then(function (done) {
|
|
530
584
|
var r = done && done.result;
|
|
531
|
-
toast(r && r.ok ? 'Read back - see
|
|
585
|
+
toast(r && r.ok ? 'Read back - see Findings' : 'Nothing to read back' + (r && r.why ? ' - ' + r.why : ''));
|
|
532
586
|
}, function () {});
|
|
533
587
|
});
|
|
534
588
|
see.setAttribute('data-action', 'db');
|
|
@@ -724,6 +778,28 @@
|
|
|
724
778
|
if (lastRecheck.newProblems.length) gone.appendChild(el('span', 'tag ember', plural(lastRecheck.newProblems.length, 'problem') + ' newly broken - marked below'));
|
|
725
779
|
body.appendChild(gone);
|
|
726
780
|
}
|
|
781
|
+
// Every fix in one paste, with the rules that keep the AI from breaking
|
|
782
|
+
// something else - whatever the filter shows, it is all of them.
|
|
783
|
+
if (state.allFixPrompt) {
|
|
784
|
+
var many = state.findings.filter(function (f) { return f.fixPrompt; }).length;
|
|
785
|
+
var all = el('div', 'fix-all');
|
|
786
|
+
var words = el('div', 'fix-all-words');
|
|
787
|
+
words.appendChild(el('b', null, 'Fix all ' + many + ' at once'));
|
|
788
|
+
words.appendChild(el('span', null, 'One prompt with every fix, and rules that stop your AI from changing anything else. Then Re-check each one.'));
|
|
789
|
+
all.appendChild(words);
|
|
790
|
+
all.appendChild(button('sun', 'copy', 'Copy one prompt for all', function () {
|
|
791
|
+
copy(state.allFixPrompt, 'One prompt for all ' + many + ' copied - paste it into your AI tool');
|
|
792
|
+
}));
|
|
793
|
+
// To read before pasting, or to select by hand. It stays open across
|
|
794
|
+
// the page's own refresh every few seconds.
|
|
795
|
+
var whole = el('details', 'fix-all-text');
|
|
796
|
+
whole.open = allPromptOpen;
|
|
797
|
+
whole.addEventListener('toggle', function () { allPromptOpen = whole.open; });
|
|
798
|
+
whole.appendChild(el('summary', null, 'See the prompt'));
|
|
799
|
+
whole.appendChild(el('pre', null, state.allFixPrompt));
|
|
800
|
+
all.appendChild(whole);
|
|
801
|
+
body.appendChild(all);
|
|
802
|
+
}
|
|
727
803
|
if (!shown.length) {
|
|
728
804
|
body.appendChild(el('p', 'empty', state.findings.length ? 'Nothing matches.' : 'Nothing found by the checks that are on and have run. That is not the same as nothing wrong - see which checks are not built yet.'));
|
|
729
805
|
return;
|
|
@@ -1012,6 +1088,7 @@
|
|
|
1012
1088
|
return getJob().then(function (now) {
|
|
1013
1089
|
if (!now || !now.running) return;
|
|
1014
1090
|
job = now;
|
|
1091
|
+
linesOpen = null;
|
|
1015
1092
|
setBusy(true);
|
|
1016
1093
|
renderJob();
|
|
1017
1094
|
return follow().then(function () { setBusy(false); return load(); });
|
package/kryptheon-dashboard.html
CHANGED
|
@@ -265,14 +265,22 @@
|
|
|
265
265
|
@media (max-width: 600px) { .files div { grid-template-columns: minmax(0, 1fr); gap: 2px; } }
|
|
266
266
|
|
|
267
267
|
/* --- a run in progress, and what it said ---------------------------------- */
|
|
268
|
-
|
|
268
|
+
/* Pinned to the corner of the window, so it is seen from wherever the button
|
|
269
|
+
was pressed - never at the top of a page scrolled away from. */
|
|
270
|
+
.job { position: fixed; right: 24px; bottom: 24px; z-index: 9; display: flex; flex-direction: column; width: min(520px, calc(100vw - 48px)); max-height: calc(100vh - 48px); padding: 16px 18px 18px; color: var(--on-dark); background: var(--ink); border-left: 3px solid var(--orange); box-shadow: 0 18px 50px rgba(11, 14, 20, 0.35); animation: job-in 0.35s var(--ease); }
|
|
271
|
+
.job[hidden] { display: none; }
|
|
269
272
|
.job.ok { border-left-color: var(--on-dark); }
|
|
273
|
+
@keyframes job-in { from { opacity: 0; transform: translateY(16px); } }
|
|
274
|
+
@media (max-width: 600px) { .job { right: 16px; bottom: 16px; width: calc(100vw - 32px); } }
|
|
270
275
|
.job-head { display: flex; align-items: center; gap: 12px; flex-wrap: wrap; }
|
|
271
276
|
.job-head b { font-weight: 800; font-stretch: 74%; font-size: 1.1rem; text-transform: uppercase; overflow-wrap: anywhere; }
|
|
272
277
|
.job-head .tag { color: var(--on-dark); border-color: var(--line-dark); }
|
|
273
278
|
.job-close { margin-left: auto; padding: 4px 10px; font: 10px var(--mono); letter-spacing: 0.12em; text-transform: uppercase; border: 1px solid var(--line-dark); }
|
|
274
|
-
.job-lines { max-height:
|
|
279
|
+
.job-lines { min-height: 0; max-height: 220px; margin: 12px 0 0; overflow: auto; font: 12px/1.6 var(--mono); white-space: pre-wrap; overflow-wrap: anywhere; opacity: 0.85; }
|
|
275
280
|
.job-result { margin-top: 12px; font-weight: 700; }
|
|
281
|
+
.job-acts { display: flex; flex-wrap: wrap; gap: 10px; margin-top: 12px; }
|
|
282
|
+
.job-acts button { padding: 6px 12px; font: 10px var(--mono); letter-spacing: 0.12em; text-transform: uppercase; color: var(--on-dark); border: 1px solid var(--line-dark); }
|
|
283
|
+
.job-acts button.go { color: var(--ink); background: var(--on-dark); border-color: var(--on-dark); }
|
|
276
284
|
.job .fallback { color: rgba(233, 237, 242, 0.6); }
|
|
277
285
|
|
|
278
286
|
/* --- boxes to type into, the words around them, and the yes -------------- */
|
|
@@ -293,9 +301,15 @@
|
|
|
293
301
|
.fallback { font: 10.5px/1.4 var(--mono); color: var(--muted); overflow-wrap: anywhere; }
|
|
294
302
|
.tag.when { color: var(--on-light); border-style: dotted; }
|
|
295
303
|
.f-quick { display: flex; flex-wrap: wrap; align-items: flex-start; gap: 10px; padding: 0 8px 16px 20px; margin-top: -6px; }
|
|
304
|
+
.fix-all { display: flex; flex-wrap: wrap; align-items: center; gap: 14px 20px; padding: 16px 8px 16px 20px; border-bottom: 1px solid var(--line); box-shadow: inset 3px 0 0 var(--orange); }
|
|
305
|
+
.fix-all-words { flex: 1 1 260px; display: grid; gap: 4px; font-size: 14px; }
|
|
306
|
+
.fix-all-words b { font-weight: 800; font-stretch: 74%; font-size: 1.05rem; text-transform: uppercase; }
|
|
307
|
+
.fix-all-text { flex: 1 1 100%; min-width: 0; }
|
|
308
|
+
.fix-all-text summary { width: max-content; font: 11px var(--mono); letter-spacing: 0.12em; text-transform: uppercase; cursor: pointer; opacity: 0.75; }
|
|
309
|
+
.fix-all-text pre { max-height: 320px; margin: 10px 0 0; padding: 14px 16px; overflow: auto; font: 12px/1.6 var(--mono); white-space: pre-wrap; overflow-wrap: anywhere; border: 1px solid var(--line); }
|
|
296
310
|
.rechecked { display: flex; flex-wrap: wrap; align-items: center; gap: 10px; padding: 14px 8px 14px 20px; font-size: 14px; border-bottom: 1px solid var(--line); box-shadow: inset 3px 0 0 var(--on-light); }
|
|
297
311
|
|
|
298
|
-
.toast { position: fixed; left: 50%; bottom: 24px; z-index: 10; max-width: calc(100vw - 32px); padding: 14px 20px; font-size: 14px; color: var(--on-dark); background: var(--ink); border-left: 2px solid var(--orange); opacity: 0; transform: translate(-50%, 20px); transition: opacity 0.35s var(--ease), transform 0.35s var(--ease); pointer-events: none; }
|
|
312
|
+
.toast { position: fixed; left: 50%; bottom: calc(24px + var(--job-room, 0px)); z-index: 10; max-width: calc(100vw - 32px); padding: 14px 20px; font-size: 14px; color: var(--on-dark); background: var(--ink); border-left: 2px solid var(--orange); opacity: 0; transform: translate(-50%, 20px); transition: opacity 0.35s var(--ease), transform 0.35s var(--ease); pointer-events: none; }
|
|
299
313
|
.toast.show { opacity: 1; transform: translate(-50%, 0); }
|
|
300
314
|
|
|
301
315
|
@media (prefers-reduced-motion: reduce) {
|
package/kryptheon-dashboard.js
CHANGED
|
@@ -284,6 +284,44 @@ function nextAction(findings, steps) {
|
|
|
284
284
|
return { kind: 'quiet', title: 'Nothing open' };
|
|
285
285
|
}
|
|
286
286
|
|
|
287
|
+
/**
|
|
288
|
+
* One prompt that fixes every open finding at once, for pasting into an AI
|
|
289
|
+
* tool, or '' when fewer than two findings have a prompt (one is its own).
|
|
290
|
+
* Each finding's own prompt goes in as it is, in the order the page shows
|
|
291
|
+
* them, most severe first. Around them are the rules that keep a fix from
|
|
292
|
+
* breaking something else: only what is listed, the smallest change, one at
|
|
293
|
+
* a time with the app checked between, nothing weakened to make a finding go
|
|
294
|
+
* away, ask rather than guess. The re-checks come once, at the end.
|
|
295
|
+
*
|
|
296
|
+
* items: [{ id, severity, headline, where, prompt }] - prompt without its
|
|
297
|
+
* re-check line.
|
|
298
|
+
*/
|
|
299
|
+
function combinedPrompt(items) {
|
|
300
|
+
const withPrompt = items.filter((f) => f.prompt);
|
|
301
|
+
if (withPrompt.length < 2) return '';
|
|
302
|
+
const n = withPrompt.length;
|
|
303
|
+
const lines = [
|
|
304
|
+
'Kryptheon checked this project and found ' + n + ' problems. Fix all ' + n + ', carefully - not breaking anything matters more than speed.',
|
|
305
|
+
'',
|
|
306
|
+
'Rules for every fix:',
|
|
307
|
+
'1. Fix only the problems listed below. Do not refactor, rename, reformat or "improve" anything else, and do not touch a file a fix does not need.',
|
|
308
|
+
'2. Make the smallest change that fixes each one. Every feature must keep working exactly as it does now for the people meant to use it.',
|
|
309
|
+
'3. Never delete or weaken a check, test, validation or security rule to make a problem go away, and never delete or rewrite existing data.',
|
|
310
|
+
'4. Do them one at a time, in the order below. After each one, make sure the app still builds and its existing tests still pass before starting the next.',
|
|
311
|
+
'5. If a fix needs a decision only I can make, or you are not sure a change is safe, stop and ask me instead of guessing.',
|
|
312
|
+
'6. When you are done, list every file you changed and why, one line each.',
|
|
313
|
+
];
|
|
314
|
+
withPrompt.forEach((f, i) => {
|
|
315
|
+
lines.push('', '======== Problem ' + (i + 1) + ' of ' + n + ' - ' + f.severity + ': ' + f.headline + (f.where ? ' (' + f.where + ')' : ''), '', f.prompt.trim());
|
|
316
|
+
});
|
|
317
|
+
lines.push('', '======== When all ' + n + ' are fixed',
|
|
318
|
+
'',
|
|
319
|
+
'Prove each fix: run these one at a time (or press Re-check on each finding in the Kryptheon dashboard). A fix counts only when it says FIXED, and a re-check also says if anything else newly broke - if it does, fix that before anything else.',
|
|
320
|
+
'');
|
|
321
|
+
withPrompt.forEach((f) => lines.push(' npx kryptheon recheck ' + f.id));
|
|
322
|
+
return lines.join('\n');
|
|
323
|
+
}
|
|
324
|
+
|
|
287
325
|
/**
|
|
288
326
|
* One sentence about the nightly run inside the database, or null when
|
|
289
327
|
* kryptheon-night has never been asked. Its four states are four different
|
|
@@ -433,9 +471,11 @@ function buildState(root, env) {
|
|
|
433
471
|
// prove the fix. Added here, where the loop lives, so every check's prompt
|
|
434
472
|
// ends the same way and the detection code itself stays untouched.
|
|
435
473
|
const attempts = readHistory(paths.fixes, 500);
|
|
474
|
+
const forAll = [];
|
|
436
475
|
for (const f of findings) {
|
|
437
476
|
f.id = changes.findingId(f);
|
|
438
477
|
f.attempts = attempts.filter((a) => a.id === f.id).map((a) => ({ at: a.at, verdict: a.verdict, why: a.why || '', newProblems: (a.newProblems || []).length }));
|
|
478
|
+
forAll.push({ id: f.id, severity: f.severity, headline: f.headline, where: f.where, prompt: f.fixPrompt || '' });
|
|
439
479
|
if (f.fixPrompt) {
|
|
440
480
|
f.fixPrompt += '\n\nWhen you have fixed it, prove it: run npx kryptheon recheck ' + f.id +
|
|
441
481
|
' (or press Re-check on this finding in the Kryptheon dashboard). It is fixed only when that says FIXED.';
|
|
@@ -497,6 +537,7 @@ function buildState(root, env) {
|
|
|
497
537
|
proven: proven,
|
|
498
538
|
checks: checks,
|
|
499
539
|
findings: findings,
|
|
540
|
+
allFixPrompt: combinedPrompt(forAll),
|
|
500
541
|
runs: history.slice().reverse().map((r) => ({ runAt: r.runAt, status: r.status, passed: r.passed, failed: r.failed, durationMs: r.durationMs })),
|
|
501
542
|
recordings: recordings,
|
|
502
543
|
steps: steps,
|
|
@@ -858,6 +899,7 @@ module.exports = {
|
|
|
858
899
|
databaseVerdict: databaseVerdict,
|
|
859
900
|
startSteps: startSteps,
|
|
860
901
|
nextAction: nextAction,
|
|
902
|
+
combinedPrompt: combinedPrompt,
|
|
861
903
|
readConfig: readConfig,
|
|
862
904
|
setCheck: setCheck,
|
|
863
905
|
createServer: createServer,
|