@runuai/host 0.8.37 → 0.8.38
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/package.json +1 -1
- package/ui/app.js +108 -21
- package/ui/style.css +10 -0
package/package.json
CHANGED
package/ui/app.js
CHANGED
|
@@ -368,33 +368,120 @@ function engineAccounts(e, accounts, canAdd) {
|
|
|
368
368
|
add.className = "link-btn";
|
|
369
369
|
add.type = "button";
|
|
370
370
|
add.textContent = "+ Add account";
|
|
371
|
-
add.addEventListener("click", () =>
|
|
371
|
+
add.addEventListener("click", () => openAddAccount(e));
|
|
372
372
|
box.append(add);
|
|
373
373
|
}
|
|
374
374
|
return box;
|
|
375
375
|
}
|
|
376
376
|
|
|
377
|
-
/**
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
377
|
+
/**
|
|
378
|
+
* Add-account modal (ADR-076). Replaces the old back-to-back window.prompt()
|
|
379
|
+
* flow: a single-line prompt hid what you pasted and silently truncated long
|
|
380
|
+
* `claude setup-token` values, which then surfaced downstream as a 401 on the
|
|
381
|
+
* rotated account. A real form (visible multi-line textarea + inline
|
|
382
|
+
* validation) makes a bad paste obvious before it's saved.
|
|
383
|
+
*/
|
|
384
|
+
function openAddAccount(e) {
|
|
385
|
+
$("engine-modal-title").textContent = `Add ${e.label} account`;
|
|
386
|
+
const body = $("engine-modal-body");
|
|
387
|
+
body.replaceChildren();
|
|
388
|
+
body.append(addAccountForm(e));
|
|
389
|
+
openModal();
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
function addAccountForm(e) {
|
|
393
|
+
const form = document.createElement("div");
|
|
394
|
+
form.className = "engine-setup";
|
|
395
|
+
|
|
396
|
+
const note = document.createElement("p");
|
|
397
|
+
note.className = "setup-note";
|
|
398
|
+
note.textContent =
|
|
388
399
|
e.kind === "claude"
|
|
389
|
-
?
|
|
390
|
-
:
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
400
|
+
? "Paste a token from `claude setup-token` (a long value, usually starting with sk-ant-oat…) or an API key. This account rotates in automatically when another Claude account hits its limit."
|
|
401
|
+
: "Paste an OpenAI API key (starts with sk-…). This account rotates in automatically when another account of this kind hits its limit.";
|
|
402
|
+
form.append(note);
|
|
403
|
+
|
|
404
|
+
const labelField = document.createElement("div");
|
|
405
|
+
labelField.className = "field";
|
|
406
|
+
const labelInput = document.createElement("input");
|
|
407
|
+
labelInput.type = "text";
|
|
408
|
+
labelInput.className = "text-input";
|
|
409
|
+
labelInput.placeholder = "Label (e.g. work)";
|
|
410
|
+
labelInput.autocomplete = "off";
|
|
411
|
+
labelInput.spellcheck = false;
|
|
412
|
+
labelField.append(labelInput);
|
|
413
|
+
form.append(labelField);
|
|
414
|
+
|
|
415
|
+
const secretField = document.createElement("div");
|
|
416
|
+
secretField.className = "field";
|
|
417
|
+
const secretInput = document.createElement("textarea");
|
|
418
|
+
secretInput.className = "text-input token-area";
|
|
419
|
+
secretInput.rows = 3;
|
|
420
|
+
secretInput.placeholder =
|
|
421
|
+
e.kind === "claude" ? "Claude token or API key" : "OpenAI API key";
|
|
422
|
+
secretInput.autocomplete = "off";
|
|
423
|
+
secretInput.spellcheck = false;
|
|
424
|
+
secretField.append(secretInput);
|
|
425
|
+
form.append(secretField);
|
|
426
|
+
|
|
427
|
+
const status = document.createElement("div");
|
|
428
|
+
status.className = "setup-status";
|
|
429
|
+
form.append(status);
|
|
430
|
+
|
|
431
|
+
const fail = (msg) => {
|
|
432
|
+
status.className = "setup-status err";
|
|
433
|
+
status.textContent = msg;
|
|
434
|
+
};
|
|
435
|
+
|
|
436
|
+
const actions = document.createElement("div");
|
|
437
|
+
actions.className = "setup-actions";
|
|
438
|
+
const save = document.createElement("button");
|
|
439
|
+
save.className = "btn";
|
|
440
|
+
save.type = "button";
|
|
441
|
+
save.textContent = "Add account";
|
|
442
|
+
save.addEventListener("click", async () => {
|
|
443
|
+
const label = labelInput.value.trim();
|
|
444
|
+
const secret = secretInput.value.trim();
|
|
445
|
+
if (!label) return fail("Give the account a label.");
|
|
446
|
+
if (!secret) return fail("Paste a token or API key.");
|
|
447
|
+
// Mirrors the host-side check — a token with internal whitespace/newlines is
|
|
448
|
+
// a mangled paste, not a value; catch it here so it never becomes a 401.
|
|
449
|
+
if (/\s/.test(secret)) {
|
|
450
|
+
return fail("That value has spaces or line breaks — paste just the token.");
|
|
451
|
+
}
|
|
452
|
+
save.disabled = true;
|
|
453
|
+
save.textContent = "Adding…";
|
|
454
|
+
const payload =
|
|
455
|
+
e.kind === "claude"
|
|
456
|
+
? { kind: e.kind, label, token: secret }
|
|
457
|
+
: { kind: e.kind, label, apiKey: secret };
|
|
458
|
+
let result;
|
|
459
|
+
try {
|
|
460
|
+
result = await postJSON("/api/engines/accounts/add", payload);
|
|
461
|
+
} catch {
|
|
462
|
+
result = { ok: false, message: "Couldn't reach the host." };
|
|
463
|
+
}
|
|
464
|
+
if (result && result.ok) {
|
|
465
|
+
await poll();
|
|
466
|
+
closeModal();
|
|
467
|
+
return;
|
|
468
|
+
}
|
|
469
|
+
save.disabled = false;
|
|
470
|
+
save.textContent = "Add account";
|
|
471
|
+
fail((result && result.message) || "Couldn't add the account.");
|
|
472
|
+
});
|
|
473
|
+
actions.append(save);
|
|
474
|
+
|
|
475
|
+
const cancel = document.createElement("button");
|
|
476
|
+
cancel.className = "link-btn";
|
|
477
|
+
cancel.type = "button";
|
|
478
|
+
cancel.textContent = "Cancel";
|
|
479
|
+
cancel.addEventListener("click", closeModal);
|
|
480
|
+
actions.append(cancel);
|
|
481
|
+
|
|
482
|
+
form.append(actions);
|
|
483
|
+
setTimeout(() => labelInput.focus(), 0);
|
|
484
|
+
return form;
|
|
398
485
|
}
|
|
399
486
|
|
|
400
487
|
// --- add-engine modal -------------------------------------------------------
|
package/ui/style.css
CHANGED
|
@@ -604,6 +604,16 @@ code,
|
|
|
604
604
|
border-color: var(--fg-muted);
|
|
605
605
|
}
|
|
606
606
|
|
|
607
|
+
/* Add-account token field: long secrets must be fully visible (no truncation)
|
|
608
|
+
and wrap so a bad paste is obvious before saving. */
|
|
609
|
+
textarea.token-area {
|
|
610
|
+
resize: vertical;
|
|
611
|
+
min-height: 3.2rem;
|
|
612
|
+
line-height: 1.35;
|
|
613
|
+
word-break: break-all;
|
|
614
|
+
white-space: pre-wrap;
|
|
615
|
+
}
|
|
616
|
+
|
|
607
617
|
.setup-actions {
|
|
608
618
|
display: flex;
|
|
609
619
|
align-items: center;
|