toolroll 0.9.85 → 0.9.87
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/CHANGELOG.md +83 -0
- package/dist/agent-fence.d.ts +6 -2
- package/dist/agent-fence.js +3 -2
- package/dist/agents-board.d.ts +12 -0
- package/dist/agents-board.js +26 -0
- package/dist/approval-rules-ui.d.ts +4 -1
- package/dist/approval-rules-ui.js +31 -15
- package/dist/assignment-ui.js +1 -1
- package/dist/assignment.js +4 -3
- package/dist/backup-ui.d.ts +5 -4
- package/dist/backup-ui.js +65 -32
- package/dist/browser/terminal.css +1 -1
- package/dist/browser/terminal.js +57 -54
- package/dist/browser/workspace.css +2 -2
- package/dist/browser/workspace.js +35 -68
- package/dist/browser-crew.js +1 -1
- package/dist/browser-workspace.d.ts +363 -177
- package/dist/browser-workspace.js +10 -9
- package/dist/chat-actions.d.ts +11 -1
- package/dist/chat-actions.js +53 -1
- package/dist/chat-channel.js +1 -0
- package/dist/chat-controls.d.ts +1 -1
- package/dist/chat-controls.js +1 -1
- package/dist/chat-quiet.js +3 -2
- package/dist/chat-settings-page.d.ts +29 -9
- package/dist/chat-settings-page.js +59 -28
- package/dist/check-levels-ui.d.ts +2 -1
- package/dist/check-levels-ui.js +67 -58
- package/dist/claim.d.ts +3 -0
- package/dist/claim.js +4 -1
- package/dist/contracts/chat-actions.d.ts +9 -0
- package/dist/contracts/chat-actions.js +3 -0
- package/dist/contracts/cli.d.ts +43 -0
- package/dist/contracts/cli.js +4 -0
- package/dist/contracts/console-api.d.ts +4 -1
- package/dist/contracts/console-api.js +4 -1
- package/dist/contracts/flow-propose.d.ts +4 -1
- package/dist/contracts/flow-propose.js +1 -1
- package/dist/contracts/flow.d.ts +41 -0
- package/dist/contracts/flow.js +4 -2
- package/dist/contracts/lead-tools.d.ts +21 -0
- package/dist/contracts/lead-tools.js +2 -0
- package/dist/contracts/store-json.d.ts +2 -0
- package/dist/contracts/trigger-payloads.d.ts +20 -0
- package/dist/contracts/trigger-payloads.js +4 -0
- package/dist/coordinator.js +1 -0
- package/dist/credentials-ui.d.ts +5 -3
- package/dist/credentials-ui.js +58 -31
- package/dist/deploy-gate.js +4 -0
- package/dist/design/kit.css +685 -0
- package/dist/design/kit.d.ts +208 -0
- package/dist/design/kit.js +177 -0
- package/dist/design/tokens.css +19 -1
- package/dist/discord-settings.js +8 -4
- package/dist/dispatch.d.ts +3 -0
- package/dist/dispatch.js +4 -1
- package/dist/export-ui.d.ts +6 -4
- package/dist/export-ui.js +18 -8
- package/dist/flow-browser.d.ts +103 -0
- package/dist/flow-browser.js +204 -0
- package/dist/flow-changed.d.ts +14 -0
- package/dist/flow-changed.js +53 -0
- package/dist/flow-code.d.ts +5 -1
- package/dist/flow-code.js +4 -1
- package/dist/flow-engine.js +16 -0
- package/dist/flow-gallery-ui.d.ts +41 -4
- package/dist/flow-gallery-ui.js +95 -63
- package/dist/flow-live.js +9 -1
- package/dist/flow-pull-request.js +15 -7
- package/dist/flow-share.js +3 -1
- package/dist/flow-starters-ui.d.ts +3 -9
- package/dist/flow-starters-ui.js +12 -23
- package/dist/flow-steps.d.ts +9 -1
- package/dist/flow-steps.js +55 -5
- package/dist/flow-triggers.js +55 -0
- package/dist/flow-unattended-words.d.ts +20 -0
- package/dist/flow-unattended-words.js +24 -0
- package/dist/flow-unattended.d.ts +9 -5
- package/dist/flow-unattended.js +45 -23
- package/dist/flows-cli.d.ts +19 -0
- package/dist/flows-cli.js +54 -5
- package/dist/flows-ui.d.ts +12 -6
- package/dist/flows-ui.js +152 -32
- package/dist/flows.js +5 -2
- package/dist/integrations-ui.d.ts +6 -4
- package/dist/integrations-ui.js +34 -40
- package/dist/integrations.d.ts +2 -2
- package/dist/integrations.js +4 -4
- package/dist/kits-ui.d.ts +8 -10
- package/dist/kits-ui.js +29 -33
- package/dist/kits.js +1 -1
- package/dist/knowledge-ui.js +76 -19
- package/dist/lead-tools.js +35 -6
- package/dist/lead-voice.js +16 -5
- package/dist/ledger-view.d.ts +2 -0
- package/dist/ledger-view.js +41 -38
- package/dist/models-ui.d.ts +8 -3
- package/dist/models-ui.js +82 -59
- package/dist/monitoring-ui.d.ts +4 -2
- package/dist/monitoring-ui.js +44 -13
- package/dist/needs-you.d.ts +1 -1
- package/dist/needs-you.js +8 -0
- package/dist/operate-remote-arguments.js +1 -0
- package/dist/operate-remote.js +1 -1
- package/dist/operate.d.ts +1 -1
- package/dist/operate.js +249 -6
- package/dist/passwordless.d.ts +1 -1
- package/dist/passwordless.js +1 -1
- package/dist/people-audit-ui.d.ts +2 -1
- package/dist/people-audit-ui.js +25 -23
- package/dist/policy-ui.d.ts +3 -2
- package/dist/policy-ui.js +27 -21
- package/dist/policy.d.ts +3 -0
- package/dist/policy.js +14 -1
- package/dist/project-delete-ui.d.ts +4 -2
- package/dist/project-delete-ui.js +135 -126
- package/dist/project-delete.js +7 -3
- package/dist/project-knowledge.d.ts +10 -10
- package/dist/publish.d.ts +11 -0
- package/dist/publish.js +16 -3
- package/dist/pull-request-flow.js +35 -4
- package/dist/pull-request-ui.d.ts +2 -1
- package/dist/pull-request-ui.js +39 -28
- package/dist/recipe-ui.d.ts +4 -2
- package/dist/recipe-ui.js +14 -4
- package/dist/release-adapters.d.ts +3 -0
- package/dist/release-adapters.js +22 -2
- package/dist/release-cli.js +81 -9
- package/dist/release.d.ts +21 -0
- package/dist/release.js +40 -4
- package/dist/releases.js +1 -1
- package/dist/request-budget-ui.d.ts +4 -2
- package/dist/request-budget-ui.js +30 -19
- package/dist/result-follow-ups.js +4 -0
- package/dist/retention-ui.d.ts +4 -3
- package/dist/retention-ui.js +15 -12
- package/dist/scope.d.ts +8 -0
- package/dist/scope.js +13 -0
- package/dist/serve.d.ts +0 -1
- package/dist/serve.js +51 -7
- package/dist/server/agents-computers.d.ts +9 -0
- package/dist/server/agents-computers.js +67 -0
- package/dist/server/chat.js +20 -18
- package/dist/server/chrome.d.ts +1 -1
- package/dist/server/chrome.js +19 -9
- package/dist/server/console.css +31 -2
- package/dist/server/flows.js +79 -36
- package/dist/server/home.d.ts +35 -0
- package/dist/server/home.js +104 -0
- package/dist/server/http.d.ts +3 -0
- package/dist/server/http.js +5 -0
- package/dist/server/kit-standalone.d.ts +22 -0
- package/dist/server/kit-standalone.js +69 -0
- package/dist/server/kit.d.ts +405 -0
- package/dist/server/kit.js +361 -0
- package/dist/server/pages.d.ts +2 -1
- package/dist/server/pages.js +213 -174
- package/dist/server/people-tokens.js +26 -85
- package/dist/server/render-history.d.ts +51 -0
- package/dist/server/render-history.js +228 -0
- package/dist/server/render-new-task.d.ts +29 -0
- package/dist/server/render-new-task.js +96 -0
- package/dist/server/render-next.d.ts +63 -0
- package/dist/server/render-next.js +109 -0
- package/dist/server/render-pages.d.ts +35 -190
- package/dist/server/render-pages.js +154 -563
- package/dist/server/render-people.d.ts +24 -3
- package/dist/server/render-people.js +93 -72
- package/dist/server/render-projects.d.ts +103 -0
- package/dist/server/render-projects.js +247 -0
- package/dist/server/render-results.d.ts +5 -24
- package/dist/server/render-results.js +48 -185
- package/dist/server/render-settings.d.ts +5 -74
- package/dist/server/render-settings.js +102 -493
- package/dist/server/render-tasks.d.ts +43 -5
- package/dist/server/render-tasks.js +131 -91
- package/dist/server/route-table.js +31 -5
- package/dist/server/settings.js +276 -255
- package/dist/server/task-views.d.ts +8 -0
- package/dist/server/task-views.js +90 -2
- package/dist/server/tasks.d.ts +18 -0
- package/dist/server/tasks.js +148 -41
- package/dist/server/terminals.js +1 -1
- package/dist/server/welcome.d.ts +22 -0
- package/dist/server/welcome.js +69 -0
- package/dist/settings-groups-ui.d.ts +279 -0
- package/dist/settings-groups-ui.js +615 -0
- package/dist/settings-kit.d.ts +42 -6
- package/dist/settings-kit.js +69 -7
- package/dist/settings-proofs.js +1 -0
- package/dist/setup-guide.d.ts +2 -2
- package/dist/skills-ui.js +59 -7
- package/dist/slack-settings.js +12 -5
- package/dist/spend-ui.d.ts +45 -26
- package/dist/spend-ui.js +124 -56
- package/dist/sso-ui.d.ts +5 -2
- package/dist/sso-ui.js +63 -31
- package/dist/storage-ui.d.ts +3 -2
- package/dist/storage-ui.js +56 -50
- package/dist/store.d.ts +182 -5
- package/dist/store.js +512 -38
- package/dist/surface.js +23 -3
- package/dist/task-archive.d.ts +10 -0
- package/dist/task-archive.js +16 -0
- package/dist/task-control.d.ts +1 -1
- package/dist/teams-settings.d.ts +2 -1
- package/dist/teams-settings.js +24 -41
- package/dist/telegram-settings.d.ts +0 -4
- package/dist/telegram-settings.js +52 -25
- package/dist/tidy.d.ts +130 -0
- package/dist/tidy.js +503 -0
- package/dist/toolroll-update-ui.d.ts +8 -4
- package/dist/toolroll-update-ui.js +55 -45
- package/dist/tools-ui.d.ts +5 -3
- package/dist/tools-ui.js +107 -42
- package/dist/work-index.d.ts +24 -0
- package/dist/work-index.js +56 -30
- package/dist/workspace-ui.d.ts +1 -1
- package/dist/workspace-ui.js +8 -4
- package/package.json +1 -1
|
@@ -1,79 +1,89 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Settings → Updates: the installed version, one of three ways to update
|
|
3
3
|
* (behind the operator's password), the update's steps live while it runs,
|
|
4
|
-
* and a one-time "What's new"
|
|
4
|
+
* and a one-time "What's new" section afterwards. The work itself is the
|
|
5
5
|
* `toolroll update` command (toolroll-update.ts), started as its own job.
|
|
6
|
+
* Built from the revamp kit; the Settings frame adds the title.
|
|
6
7
|
*/
|
|
7
|
-
import { html,
|
|
8
|
+
import { html, joinHtml, postForm } from "./html.js";
|
|
8
9
|
import { STEP_WORDS, UPDATE_STEPS, runtimeUpdateTerminal } from "./toolroll-update.js";
|
|
10
|
+
import { btn, chip, codeLine, disclose, formRow, infoLine, note, notice, radios, settingsRecord, settingsSection } from "./server/kit.js";
|
|
11
|
+
import { kitSectionForm } from "./settings-kit.js";
|
|
9
12
|
const clock = (iso) => { const d = new Date(iso); return `${String(d.getHours()).padStart(2, "0")}:${String(d.getMinutes()).padStart(2, "0")}`; };
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
`.updates ol.update-steps li[data-state=done]{color:var(--so-ink)}.updates ol.update-steps li[data-state=done]::before{background:var(--so-success);border-color:var(--so-success)}` +
|
|
17
|
-
`.updates ol.update-steps li[data-state=now]{color:var(--so-ink);font-weight:600}.updates ol.update-steps li[data-state=now]::before{background:var(--so-info);border-color:var(--so-info);box-shadow:0 0 0 4px color-mix(in srgb,var(--so-info) 16%,transparent)}` +
|
|
18
|
-
`.updates ol.update-steps li[data-state=failed]{color:var(--so-danger);font-weight:600}.updates ol.update-steps li[data-state=failed]::before{background:var(--so-danger);border-color:var(--so-danger)}` +
|
|
19
|
-
`.updates ol.update-steps .step-detail{grid-column:2;font-weight:400;color:var(--so-muted);font-size:.8125rem;margin-top:2px}` +
|
|
20
|
-
`.updates .whats-new ul{margin:10px 0 0;padding-left:18px}.updates .whats-new li{margin:4px 0}.updates .whats-new form{margin:14px 0 0}` +
|
|
21
|
-
`.updates .update-problem{color:var(--so-danger)}.updates .stamp{margin:10px 0 0}.updates code{white-space:nowrap}.updates [data-update-outcome=stopped] code,.updates .step-detail code{white-space:normal;overflow-wrap:anywhere}` +
|
|
22
|
-
`@media (prefers-reduced-motion:no-preference){.updates ol.update-steps li[data-state=now]::before{animation:update-pulse 1.6s ease-in-out infinite}}@keyframes update-pulse{50%{box-shadow:0 0 0 7px color-mix(in srgb,var(--so-info) 6%,transparent)}}` +
|
|
23
|
-
`@media (max-width:600px){.updates .update-actions{display:grid}.updates .update-actions button{width:100%;min-height:44px}.updates .step-up{max-width:none}.updates .step-up input{min-height:44px}}`;
|
|
13
|
+
/** The page is kit pieces now; nothing of the old look is left. Kept as an export for the shared stylesheet. */
|
|
14
|
+
export const UPDATES_CSS = "";
|
|
15
|
+
/** A step's state as one of the kit's status words. */
|
|
16
|
+
const STEP_CHIP = {
|
|
17
|
+
done: chip("Finished", "done"), now: chip("Running", "live"), failed: chip("Failed", "failed"), waiting: chip("Queued", ""),
|
|
18
|
+
};
|
|
24
19
|
/** The steps, each done, current, failed or waiting. Also the live region's fragment. */
|
|
25
|
-
export function updateStepsHtml(j, running) {
|
|
20
|
+
export function updateStepsHtml(j, running, options = {}) {
|
|
26
21
|
const finished = runtimeUpdateTerminal(j.phase);
|
|
27
22
|
const failed = ["restored", "refused", "needs-attention", "rolling-back"].includes(j.phase);
|
|
28
23
|
const last = UPDATE_STEPS.indexOf([...j.steps].reverse().find(s => UPDATE_STEPS.includes(s.phase))?.phase);
|
|
29
24
|
const items = UPDATE_STEPS.map((step, i) => {
|
|
30
25
|
const state = j.phase === "complete" || i < last ? "done" : i > last ? "waiting" : failed ? "failed" : finished ? "waiting" : "now";
|
|
31
26
|
// Only where it adds something: which work it waits for, or what went wrong.
|
|
32
|
-
const detail = state === "now" && step === "draining" && j.waiting ? html
|
|
33
|
-
: (state === "now" && step === "draining") || (state === "failed" && j.phase === "rolling-back") ? html
|
|
34
|
-
return
|
|
27
|
+
const detail = state === "now" && step === "draining" && j.waiting ? html `${j.waiting.on}.${j.waiting.action ? html ` If nothing of it is running, run <code class="k-mono">${j.waiting.action}</code>.` : ""}`
|
|
28
|
+
: (state === "now" && step === "draining") || (state === "failed" && j.phase === "rolling-back") ? html `${j.detail}` : "";
|
|
29
|
+
return settingsRecord({ title: STEP_WORDS[step], sub: detail, when: STEP_CHIP[state], attrs: { "data-step": step, "data-state": state } });
|
|
35
30
|
});
|
|
36
31
|
const title = j.phase === "scheduled" ? `Update to ${j.to.version} scheduled for ${clock(j.at ?? j.startedAt)}`
|
|
37
32
|
: j.phase === "rolling-back" ? `Restoring ${j.from.version}`
|
|
38
33
|
: j.kind === "rollback" ? `Rolling back to ${j.to.version}` : `Updating to ${j.to.version}`;
|
|
39
|
-
const stalled = !running && !finished ? html
|
|
40
|
-
return html `<div id="update-live" data-phase="${j.phase}" data-done="${finished ? 1 : 0}"><
|
|
34
|
+
const stalled = !running && !finished ? notice(html `The updater stopped. Run <code class="k-mono">toolroll update --resume</code> to continue it.`) : "";
|
|
35
|
+
return html `<div id="update-live" data-phase="${j.phase}" data-done="${finished ? 1 : 0}">${options.title === false ? "" : html `<p><b>${title}</b></p>`}${j.phase === "scheduled" ? note("Running work finishes first. You can close this page.") : html `<div aria-label="Update steps">${items}</div>`}${stalled}</div>`;
|
|
41
36
|
}
|
|
42
37
|
function outcomeHtml(j) {
|
|
43
|
-
if (j.phase === "complete")
|
|
44
|
-
return j.kind === "rollback" ?
|
|
38
|
+
if (j.phase === "complete") {
|
|
39
|
+
return j.kind === "rollback" ? settingsSection({ id: "update-outcome", title: `Back on ${j.to.version}`, body: html `<div data-update-outcome="rolled-back">${note(j.detail)}</div>` }) : "";
|
|
40
|
+
}
|
|
45
41
|
if (j.phase === "cancelled")
|
|
46
|
-
return html `<div
|
|
42
|
+
return settingsSection({ id: "update-outcome", title: "Update cancelled", body: html `<div data-update-outcome="cancelled">${note("Nothing changed.")}</div>` });
|
|
47
43
|
// Stopped on a finished run it can't show has ended: what is in the way and the one command, nothing more.
|
|
48
|
-
if (j.phase === "refused" && j.waiting)
|
|
49
|
-
return
|
|
50
|
-
|
|
51
|
-
|
|
44
|
+
if (j.phase === "refused" && j.waiting) {
|
|
45
|
+
return settingsSection({ id: "update-outcome", title: `Update to ${j.to.version} stopped: run #${j.waiting.run} is in the way`,
|
|
46
|
+
body: html `<div data-update-outcome="stopped">${infoLine(`${j.waiting.on}. New work resumed.`)}${j.waiting.action ? note(html `If nothing of it is running, run <code class="k-mono">${j.waiting.action}</code>, then update again.`) : note("Update again once it has stopped.")}</div>` });
|
|
47
|
+
}
|
|
48
|
+
const title = j.phase === "refused" ? `Didn't update to ${j.to.version}` : j.phase === "restored" ? `Update to ${j.to.version} didn't finish` : "The update needs attention";
|
|
49
|
+
return settingsSection({ id: "update-outcome", title, state: html ` ${chip("Failed", "failed")}`,
|
|
50
|
+
body: html `<div data-update-outcome="${j.phase}">${j.phase === "needs-attention" ? notice(j.detail) : html `<p class="k-note" role="alert">${j.detail}</p>`}${disclose("Steps", updateStepsHtml(j, false, { title: false }))}</div>` });
|
|
52
51
|
}
|
|
53
|
-
export function updatesHtml(view,
|
|
54
|
-
const
|
|
52
|
+
export function updatesHtml(view, said) {
|
|
53
|
+
const answer = said.problem ? notice(said.problem) : said.said ? infoLine(said.said) : "";
|
|
55
54
|
const j = view.journal;
|
|
56
55
|
const active = j !== null && !runtimeUpdateTerminal(j.phase);
|
|
57
|
-
const whatsNew = view.whatsNew === null ? "" :
|
|
56
|
+
const whatsNew = view.whatsNew === null ? "" : settingsSection({ id: "whats-new", title: `What’s new in ${view.whatsNew.version}`,
|
|
57
|
+
right: postForm("/settings/updates/seen", btn("Got it", { sm: true, type: "submit" })),
|
|
58
|
+
body: html `<div data-whats-new="${view.whatsNew.version}">${view.whatsNew.notes.length > 0 ? view.whatsNew.notes.map(line => note(line)) : ""}${note(html `<a class="k-link" href="https://github.com/ap9000/toolroll/releases/tag/v${view.whatsNew.version}" rel="noreferrer">Full release notes</a>`)}</div>` });
|
|
58
59
|
if (active) {
|
|
59
|
-
const cancel = ["scheduled", "draining"].includes(j.phase) ? postForm("/settings/updates/cancel",
|
|
60
|
-
return
|
|
60
|
+
const cancel = ["scheduled", "draining"].includes(j.phase) ? postForm("/settings/updates/cancel", btn("Cancel update", { sm: true, type: "submit" })) : "";
|
|
61
|
+
return settingsSection({ id: "update", title: "Update", state: html ` ${chip("Running", "live")}`, ...(cancel === "" ? {} : { right: cancel }),
|
|
62
|
+
body: joinHtml([answer, html `<div id="update-region">${updateStepsHtml(j, view.running)}</div>`, html `<p class="k-note" id="update-region-stamp" aria-live="polite"></p>`]) });
|
|
61
63
|
}
|
|
62
64
|
const latest = "version" in view.latest ? view.latest.version : null;
|
|
63
65
|
const newer = latest !== null && newerThan(latest, view.current);
|
|
64
|
-
const
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
66
|
+
const check = postForm("/settings/updates/check", btn("Check now", { sm: true, type: "submit" }));
|
|
67
|
+
const state = view.method.kind === "npx" ? [`Toolroll ${view.current}`, "npx runs the latest release each time, so this is current.", ""]
|
|
68
|
+
: view.method.kind === "source" ? [`Toolroll ${view.current}`, "This runs from a source checkout. Update it with git.", ""]
|
|
69
|
+
: newer && view.method.kind === "desktop" ? [`Toolroll ${latest} is available`, `You have ${view.current}. Update it from the Toolroll app.`, ""]
|
|
70
|
+
: newer ? [`Toolroll ${latest} is available`, `You have ${view.current}. Running work finishes first, and your current version is kept so you can go back.`, ""]
|
|
71
|
+
: "off" in view.latest ? [`Toolroll ${view.current}`, "Update checks are off.", check]
|
|
72
|
+
: "pending" in view.latest ? [`Toolroll ${view.current}`, "Not checked for a newer release yet.", check]
|
|
73
|
+
: latest === null ? [`Toolroll ${view.current}`, `Couldn’t check for a newer release: ${"problem" in view.latest ? view.latest.problem : ""}`, ""]
|
|
74
|
+
: [`Toolroll ${view.current} is up to date`, "", ""];
|
|
72
75
|
const form = newer && !["npx", "source", "desktop"].includes(view.method.kind)
|
|
73
|
-
?
|
|
76
|
+
? kitSectionForm({ proof: "updates", action: "/settings/updates", id: "update-form", hidden: { version: latest }, save: { label: `Update to ${latest}` }, confirm: `Update Toolroll to ${latest}`,
|
|
77
|
+
body: formRow("When", null, radios({ name: "when", legend: "When", current: "now", items: [
|
|
78
|
+
{ value: "now", label: "Now", text: "Only when no work is running." },
|
|
79
|
+
{ value: "when-idle", label: "When idle", text: "Once nothing is running." },
|
|
80
|
+
{ value: "tonight", label: "Tonight (03:00)", text: "At 03:00 on the computer running Toolroll." }
|
|
81
|
+
] }), "wide") })
|
|
74
82
|
: "";
|
|
75
|
-
const rollback = view.rollbackTo !== null ?
|
|
76
|
-
|
|
83
|
+
const rollback = view.rollbackTo !== null ? joinHtml([note(`To go back to ${view.rollbackTo}:`), codeLine("toolroll update --rollback")]) : "";
|
|
84
|
+
const version = settingsSection({ id: "update", title: state[0], ...(state[2] === "" ? {} : { right: state[2] }),
|
|
85
|
+
body: html `<div data-update-state="${newer ? "available" : "current"}">${joinHtml([answer, state[1] === "" ? "" : note(state[1]), form, rollback])}</div>` });
|
|
86
|
+
return joinHtml([whatsNew, j ? outcomeHtml(j) : "", version]);
|
|
77
87
|
}
|
|
78
88
|
export function newerThan(a, b) {
|
|
79
89
|
const [x, y] = [a, b].map(v => v.split(".").map(Number));
|
package/dist/tools-ui.d.ts
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
/** Settings → Tools: one project's MCP servers, in plain words. Secrets are
|
|
2
|
-
* entered here and nowhere else, and never shown again.
|
|
2
|
+
* entered here and nowhere else, and never shown again. Built from the revamp kit (src/design/KIT.md): each tool and
|
|
3
|
+
* each service is a record with one action; the password is asked only once a tool or service is picked. */
|
|
3
4
|
import { type Html } from "./html.js";
|
|
4
5
|
import { type FoundTool, type ProjectTool, type ToolSpec } from "./project-tools.js";
|
|
5
|
-
|
|
6
|
+
/** Kept for the console's style bundle (server/chrome.ts): the page is built from the kit and needs none of its own. */
|
|
7
|
+
export declare const TOOLS_CSS = "";
|
|
6
8
|
export type ToolsView = {
|
|
7
9
|
repo: string;
|
|
8
10
|
project: string;
|
|
@@ -16,7 +18,7 @@ export type ToolsView = {
|
|
|
16
18
|
research?: string;
|
|
17
19
|
}[];
|
|
18
20
|
kit: string | null;
|
|
19
|
-
/** The service a link asked for (a kit's checklist, the lead):
|
|
21
|
+
/** The service a link asked for (a kit's checklist, the lead): listed first, its Connect the page's one primary. */
|
|
20
22
|
wanted: string | null;
|
|
21
23
|
tools: {
|
|
22
24
|
tool: ProjectTool;
|
package/dist/tools-ui.js
CHANGED
|
@@ -1,48 +1,97 @@
|
|
|
1
1
|
/** Settings → Tools: one project's MCP servers, in plain words. Secrets are
|
|
2
|
-
* entered here and nowhere else, and never shown again.
|
|
3
|
-
|
|
4
|
-
import {
|
|
2
|
+
* entered here and nowhere else, and never shown again. Built from the revamp kit (src/design/KIT.md): each tool and
|
|
3
|
+
* each service is a record with one action; the password is asked only once a tool or service is picked. */
|
|
4
|
+
import { html, joinHtml, postForm } from "./html.js";
|
|
5
|
+
import { brandMarkHtml } from "./brand-mark.js";
|
|
5
6
|
import { signedIn } from "./oauth-run-token.js";
|
|
6
7
|
import { credentialModeOf, toolCommandLine, toolStanding } from "./project-tools.js";
|
|
8
|
+
import { acts, btn, chip, codeLine, disclose, facts, formRow, icon, infoLine, input, note, notice, select, settingsRecord, settingsSection } from "./server/kit.js";
|
|
9
|
+
import { kitSectionForm, SECRET_OPT_OUTS } from "./settings-kit.js";
|
|
7
10
|
const hidden = (data) => Object.entries(data).map(([k, v]) => html `<input type="hidden" name="${k}" value="${v}">`);
|
|
8
|
-
|
|
11
|
+
/** Kept for the console's style bundle (server/chrome.ts): the page is built from the kit and needs none of its own. */
|
|
12
|
+
export const TOOLS_CSS = "";
|
|
9
13
|
/** The project chooser: it opens the chosen project at once; without scripts, its button does. */
|
|
10
14
|
export function toolsProjectPicker(projects, chosen, keep = {}) {
|
|
11
15
|
if (projects.length < 2)
|
|
12
16
|
return "";
|
|
13
|
-
const
|
|
14
|
-
return html `<form
|
|
17
|
+
const items = projects.map(p => [p, p.split(/[\\/]/).filter(Boolean).at(-1) ?? p]);
|
|
18
|
+
return html `<form method="get" action="/settings/tools" class="k-acts k-picker" data-autosave>${hidden(keep)}\
|
|
19
|
+
${select({ name: "repo", label: "Project", current: chosen, items, attrs: { "data-tools-project": true } })}${btn("Show project", { sm: true, type: "submit" })}</form>`;
|
|
15
20
|
}
|
|
16
21
|
/** Choosing another project marks every form on the page as stale before it reloads, so a tap racing the reload is refused. */
|
|
17
22
|
export const TOOLS_PROJECT_SCRIPT = `(function(){var s=document.querySelector('select[data-tools-project]');if(!s)return;s.addEventListener('change',function(){document.querySelectorAll('input[name=shown]').forEach(function(i){i.value=s.value;});});})();`;
|
|
18
|
-
const
|
|
19
|
-
|
|
23
|
+
const slug = (text) => text.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "");
|
|
24
|
+
/** A tool secret's own field: masked, never given a value, kept out of password managers. */
|
|
25
|
+
function secretField(id, label) {
|
|
26
|
+
return html `<input class="k-input" id="${id}" type="password" name="value" autocomplete="off" spellcheck="false" autocapitalize="none" data-so-secret \
|
|
27
|
+
data-1p-ignore data-lpignore="${SECRET_OPT_OUTS["data-lpignore"]}" data-bwignore data-form-type="other" required aria-label="${label}">`;
|
|
28
|
+
}
|
|
29
|
+
function toolRecord(one, base, manage, project, research = null) {
|
|
20
30
|
const { tool, secretsSet } = one;
|
|
21
31
|
const standing = toolStanding(tool, secretsSet);
|
|
22
32
|
// How its runs get credentials, in one line; what that means is in Details.
|
|
23
33
|
const mode = credentialModeOf(tool.spec, secretsSet);
|
|
24
|
-
const
|
|
25
|
-
// One
|
|
26
|
-
// in gets its tokens only by signing in again (its Connect
|
|
34
|
+
const sub = html `${tool.spec.about}${mode === null ? "" : html ` · <span data-credential="${mode.mode}">${mode.words}</span>`}`;
|
|
35
|
+
// One form per secret: the disclosure is both where it stands and where it is set. A service connected by signing
|
|
36
|
+
// in gets its tokens only by signing in again (its Connect): no token of it is ever pasted here.
|
|
27
37
|
const secrets = manage && !signedIn(tool.spec) ? tool.spec.secrets.map(secret => {
|
|
28
38
|
const set = secretsSet.includes(secret.name);
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
39
|
+
const id = `secret-${slug(tool.name)}-${slug(secret.name)}`;
|
|
40
|
+
return disclose(set ? `Replace ${secret.name}` : `Set ${secret.name}${secret.optional ? " (optional)" : ""}`, kitSectionForm({
|
|
41
|
+
proof: "tools-change", action: "/settings/tools/change", id, gate: true, hidden: { ...base, action: "secret", name: tool.name, secret: secret.name },
|
|
42
|
+
save: { label: `Save ${secret.name}` }, confirm: `Set ${secret.name} for ${tool.name} in ${project}`,
|
|
43
|
+
body: formRow(secret.name, "Stored privately on this computer and never shown again.", secretField(`${id}-value`, secret.name)),
|
|
44
|
+
}));
|
|
45
|
+
}) : [];
|
|
46
|
+
const tested = tool.lastTest?.ok ? `${tool.lastTest.at.slice(0, 16).replace("T", " ")} UTC: ${tool.lastTest.tools.join(", ") || "it lists no tools"}` : null;
|
|
47
|
+
const details = disclose("Details", facts([
|
|
48
|
+
...(mode?.detail == null ? [] : [["Keys", mode.detail]]),
|
|
49
|
+
["Starts", codeLine(toolCommandLine(tool.spec))],
|
|
50
|
+
["Added", `From ${tool.source} · by ${tool.createdBy}`],
|
|
51
|
+
...(tested === null ? [] : [["Last test", tested]]),
|
|
52
|
+
]));
|
|
53
|
+
const failed = tool.lastTest !== null && !tool.lastTest.ok ? notice(tool.lastTest.problem ?? "The last test failed.") : "";
|
|
34
54
|
// Connected, but research can't use it (its grant writes): said once, with the one way to fix it.
|
|
35
|
-
const reconnect = research === null || !manage ? "" :
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
55
|
+
const reconnect = research === null || !manage ? "" : kitSectionForm({
|
|
56
|
+
proof: "tools-connect", action: "/settings/tools/connect", id: `reconnect-${slug(tool.name)}`, native: true, hidden: { ...base, service: tool.name, access: "read" },
|
|
57
|
+
save: { label: "Reconnect read-only", variant: "outline" }, confirm: `Reconnect ${tool.name} read-only for ${project}`, body: html ``,
|
|
58
|
+
});
|
|
59
|
+
const limited = research === null ? "" : html `${infoLine(research)}${reconnect}`;
|
|
60
|
+
const test = manage ? postForm("/settings/tools/change", btn("Test", { sm: true, type: "submit", attrs: { "aria-label": `Test ${tool.name} in ${project}` } }), { hidden: { ...base, action: "test", name: tool.name } }) : "";
|
|
61
|
+
const remove = manage ? postForm("/settings/tools/change", acts(btn(`Remove from ${project}`, { sm: true, variant: "danger", type: "submit" })), { hidden: { ...base, action: "remove", name: tool.name } }) : "";
|
|
62
|
+
return settingsRecord({
|
|
63
|
+
title: html `${brandMarkHtml(tool.source === "custom" ? null : tool.name, tool.name, standing.ready)}<span>${tool.name}</span>${chip(standing.words, standing.ready ? "done" : "")}`,
|
|
64
|
+
sub, action: test, attrs: { id: `tool-${tool.name}`, "data-ready": String(standing.ready) },
|
|
65
|
+
more: joinHtml([failed, limited, ...secrets, details, remove]),
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
/** One service to connect by signing in on its own page: one record, one Connect. The password is asked after it is picked. */
|
|
69
|
+
function connectRecord(one, view, base, problem) {
|
|
70
|
+
const mark = brandMarkHtml(one.id.replace(/-desktop$/, ""), one.label, one.state === "connected");
|
|
71
|
+
const attrs = { id: `connect-${one.id}`, "data-state": one.state };
|
|
72
|
+
if (one.state === "taken")
|
|
73
|
+
return settingsRecord({ title: html `${brandMarkHtml(one.id, one.label, false)}<span>${one.label}</span>`, sub: "Added another way", attrs });
|
|
74
|
+
const to = ` to ${view.project}`;
|
|
75
|
+
const connected = one.state === "connected";
|
|
76
|
+
const label = connected ? (one.local ? "Connect again" : "Sign in again") : "Connect";
|
|
77
|
+
const confirm = connected && !one.local ? `Sign in to ${one.label} again for ${view.project}` : `Connect ${one.label}${to}`;
|
|
78
|
+
const action = kitSectionForm({
|
|
79
|
+
proof: "tools-connect", action: "/settings/tools/connect", id: `connect-${one.id}-form`, native: true,
|
|
80
|
+
hidden: { ...base, ...(view.kit === null ? {} : { kit: view.kit }), service: one.id },
|
|
81
|
+
save: { label, variant: one.id === view.wanted ? "primary" : "outline" }, confirm, body: html ``,
|
|
82
|
+
});
|
|
83
|
+
return settingsRecord({ title: html `${mark}<span>${one.label}</span>${connected ? chip("Connected", "done") : ""}`, sub: connected ? "" : one.about, action, attrs, more: problem });
|
|
39
84
|
}
|
|
40
|
-
/** A service connected here, offered to the person's other projects: each
|
|
85
|
+
/** A service connected here, offered to the person's other projects: each act signs in again for that project (tokens are never copied). */
|
|
41
86
|
function elsewhere(view, base) {
|
|
42
|
-
const offers = view.connections.filter(one => one.state === "connected").flatMap(one => (view.others ?? []).filter(other => other.open.includes(one.id)).map(other =>
|
|
87
|
+
const offers = view.connections.filter(one => one.state === "connected").flatMap(one => (view.others ?? []).filter(other => other.open.includes(one.id)).map(other => kitSectionForm({
|
|
88
|
+
proof: "tools-connect", action: "/settings/tools/connect", id: `also-${slug(one.id)}-${slug(other.project)}`, native: true,
|
|
89
|
+
hidden: { ...base, ...(view.kit === null ? {} : { kit: view.kit }), also: `${one.id}:${other.repo}` },
|
|
90
|
+
save: { label: `Also connect ${one.label} to ${other.project}`, variant: "outline" }, confirm: `Connect ${one.label} to ${other.project}`, body: html ``,
|
|
91
|
+
})));
|
|
43
92
|
if (offers.length === 0)
|
|
44
93
|
return "";
|
|
45
|
-
return
|
|
94
|
+
return settingsSection({ id: "connect-elsewhere", title: "Also connect to another project", lead: "Signs in again for that project. Tokens are never copied between projects.", body: joinHtml(offers) });
|
|
46
95
|
}
|
|
47
96
|
export function toolsHtml(view, csrf, canManage, message = {}) {
|
|
48
97
|
const manage = canManage && csrf !== "";
|
|
@@ -51,25 +100,41 @@ export function toolsHtml(view, csrf, canManage, message = {}) {
|
|
|
51
100
|
const to = ` to ${view.project}`;
|
|
52
101
|
const keptIn = view.storage === undefined || view.storage.filesOnly ? "Secrets stay on this computer" : view.storage.words.replace(/\.$/, "");
|
|
53
102
|
// No keychain here: said once, plainly, where it can't be missed.
|
|
54
|
-
const storage = view.storage?.problem !== undefined ?
|
|
55
|
-
: view.storage?.filesOnly === true ?
|
|
56
|
-
const
|
|
57
|
-
const
|
|
58
|
-
|
|
59
|
-
const
|
|
60
|
-
// One click: sign in on the service's own page. The password is the same as for adding any tool.
|
|
61
|
-
const tiles = view.connections.map(one => one.state === "taken"
|
|
62
|
-
? html `<button type="button" class="connect-tile" id="connect-${one.id}" data-state="taken" disabled>${brandMarkHtml(one.id, one.label, false)}<strong>${one.label}</strong><span>Added another way</span></button>`
|
|
63
|
-
: html `<button name="service" value="${one.id}" class="connect-tile${one.local ? " connect-local" : ""}" id="connect-${one.id}" data-state="${one.state}" aria-label="${one.state === "connected" ? `${one.label}, connected${to}. ${one.local ? "Connect again" : "Sign in again"}` : `Connect ${one.label}${to}`}">${brandMarkHtml(one.id.replace(/-desktop$/, ""), one.label, one.state === "connected")}<strong>${one.label}</strong><span>${one.state === "connected" ? "Connected" : one.about}</span></button>`);
|
|
64
|
-
const wanted = view.connections.find(one => one.id === view.wanted && one.state !== "taken");
|
|
65
|
-
const first = wanted === undefined ? "" : html `<button name="service" value="${wanted.id}" class="connect-wanted">${wanted.state === "connected" && !wanted.local ? `Sign in to ${wanted.label} again for ${view.project}` : `Connect ${wanted.label}${to}`}</button>`;
|
|
103
|
+
const storage = view.storage?.problem !== undefined ? notice(view.storage.problem)
|
|
104
|
+
: view.storage?.filesOnly === true ? infoLine(view.storage.words) : "";
|
|
105
|
+
const how = disclose("How tools work", html `<ul class="k-note"><li>A tool you add reaches work you approve from now on. Approve a task again to give it a new tool.</li><li>Removing a tool takes it away from every build right away.</li><li>${keptIn}, are never shown again, and go only to the tool that needs them.</li><li>Reviewers and the lead chat never get tools. Gemini builds don't use tools yet.</li></ul>`);
|
|
106
|
+
const records = view.tools.length === 0 ? note("No tools yet. Builds in this project get none.")
|
|
107
|
+
: joinHtml(view.tools.map(one => toolRecord(one, base, manage, view.project, view.connections.find(c => c.id === one.tool.name && c.state === "connected")?.research ?? null)));
|
|
108
|
+
const tools = settingsSection({ id: "tools", title: `Tools in ${view.project}`, lead: `Builds in ${view.project} use exactly these tools. MCP servers set up elsewhere on this computer aren't used.`, body: joinHtml([storage, records, how]) });
|
|
66
109
|
const connecting = manage && view.connections.length > 0;
|
|
67
|
-
// A Connect that failed says why at its
|
|
110
|
+
// A Connect that failed says why at its service, focused so it is heard and seen there (not only at the top).
|
|
68
111
|
const atButton = connecting && view.wanted !== null && view.connections.some(one => one.id === view.wanted) && Boolean(message.problem);
|
|
69
|
-
const failed = atButton ? html `<p class="
|
|
70
|
-
|
|
71
|
-
const
|
|
72
|
-
const
|
|
73
|
-
|
|
74
|
-
|
|
112
|
+
const failed = atButton ? html `<p class="k-notice" id="connect-problem" role="alert" tabindex="-1" autofocus>${icon("alert", true)}<span>${message.problem}</span></p>` : "";
|
|
113
|
+
// The service a link asked for comes first; its Connect is the page's one primary.
|
|
114
|
+
const ordered = [...view.connections.filter(one => one.id === view.wanted), ...view.connections.filter(one => one.id !== view.wanted)];
|
|
115
|
+
const connect = connecting ? settingsSection({ id: "connect", title: `Connect${to}`, lead: "Sign in on the service's own page. Toolroll asks for your password once you pick one.",
|
|
116
|
+
body: joinHtml(ordered.map(one => connectRecord(one, view, base, one.id === view.wanted ? failed : ""))) }) : "";
|
|
117
|
+
const fromList = view.catalog.length === 0 ? "" : kitSectionForm({
|
|
118
|
+
proof: "tools-change", action: "/settings/tools/change", id: "tools-add-catalog", hidden: { ...base, action: "add-catalog" }, save: { label: `Add${to}`, variant: "outline" }, confirm: `Add a tool${to}`,
|
|
119
|
+
body: formRow("Common tools", null, select({ id: "tools-catalog", name: "catalog", label: "Common tools", wide: true, items: view.catalog.map(one => [one.name, `${one.label}: ${one.about}`]) }), "wide"),
|
|
120
|
+
});
|
|
121
|
+
const custom = disclose("Add your own", kitSectionForm({
|
|
122
|
+
proof: "tools-change", action: "/settings/tools/change", id: "tools-add-custom", hidden: { ...base, action: "add-custom" }, save: { label: `Add${to}`, variant: "outline" }, confirm: `Add your own tool${to}`,
|
|
123
|
+
body: joinHtml([
|
|
124
|
+
formRow("Name", "Lowercase letters, numbers, - and _.", input({ id: "tools-custom-name", name: "name", attrs: { pattern: "[a-z0-9][a-z0-9_\\-]{0,39}", maxlength: 40, required: true, placeholder: "for example: postgres", "aria-label": "Name" } })),
|
|
125
|
+
formRow("It runs as", null, select({ id: "tools-custom-transport", name: "transport", label: "It runs as", items: [["stdio", "A program on this computer"], ["http", "A web address"]] })),
|
|
126
|
+
formRow("Command or address", null, input({ id: "tools-custom-target", name: "target", attrs: { required: true, placeholder: "npx -y some-mcp-server@1.2.3, or https://…", "aria-label": "Command or address" } }), "wide"),
|
|
127
|
+
formRow("Secrets it needs", "Names, comma separated. For a web address, the first one is sent as its sign-in token. Set each value after adding.", input({ id: "tools-custom-secrets", name: "secrets", attrs: { placeholder: "for example: DATABASE_URL", "aria-label": "Secrets it needs" } })),
|
|
128
|
+
]),
|
|
129
|
+
}));
|
|
130
|
+
const add = manage ? settingsSection({ id: "add", title: "Add a tool", body: joinHtml([fromList, custom]) }) : "";
|
|
131
|
+
const found = manage && view.found.length > 0 ? settingsSection({ id: "found", title: "Found on this computer", state: ` · ${view.found.length}`,
|
|
132
|
+
lead: "MCP servers your other apps use. Adding one copies its settings, and any secrets it has, into this project.",
|
|
133
|
+
body: kitSectionForm({
|
|
134
|
+
proof: "tools-change", action: "/settings/tools/change", id: "tools-import", hidden: { ...base, action: "import" }, save: { label: `Add selected${to}`, variant: "outline" }, confirm: `Add tools found on this computer${to}`,
|
|
135
|
+
body: html `<fieldset class="k-sopts"><legend class="k-sr">Found on this computer</legend>${view.found.map(one => html `<label class="k-sopt"><input type="checkbox" name="import" value="${one.spec.name}">\
|
|
136
|
+
<span><b>${one.spec.name}</b><span>${one.source} · <span class="k-mono">${toolCommandLine(one.spec)}</span>${one.spec.secrets.length === 0 ? "" : ` · brings ${one.spec.secrets.map(s => s.name).join(", ")}`}</span></span></label>`)}</fieldset>`,
|
|
137
|
+
}) }) : "";
|
|
138
|
+
const answer = atButton ? "" : message.problem ? notice(message.problem) : message.said ? infoLine(message.said) : "";
|
|
139
|
+
return joinHtml([answer, tools, connect, connecting ? elsewhere(view, base) : "", add, found]);
|
|
75
140
|
}
|
package/dist/work-index.d.ts
CHANGED
|
@@ -37,6 +37,13 @@ export type WorkIndexItem = {
|
|
|
37
37
|
evidence: 'recorded';
|
|
38
38
|
/** The viewer's own lead took it on (lead-voice.ts): "<name> is on it.", by the name they gave it, read now. */
|
|
39
39
|
lead?: string;
|
|
40
|
+
/** Put away (v145, task-tidy.md): by whom (a person's name, or tidy), why and when. Only the Archived view lists it. */
|
|
41
|
+
archived?: {
|
|
42
|
+
by: string;
|
|
43
|
+
byKind: 'person' | 'tidy';
|
|
44
|
+
reason: string;
|
|
45
|
+
at: string;
|
|
46
|
+
};
|
|
40
47
|
};
|
|
41
48
|
/** `root`: the evidence root, so a result whose checks were Off can read the release check that covers it. */
|
|
42
49
|
export type WorkIndexOptions = {
|
|
@@ -129,3 +136,20 @@ export declare function workReadyForReview(store: Store, now: Date, access: Work
|
|
|
129
136
|
run: number | null;
|
|
130
137
|
}[];
|
|
131
138
|
};
|
|
139
|
+
/** One row per task family for tidy (docs/plans/task-tidy.md): the Tasks list's own projection, so tidy's "unfinished",
|
|
140
|
+
* "live" and "complete" are the words the list shows. `members` are every version's task_ref ids. */
|
|
141
|
+
export type WorkTidyRow = {
|
|
142
|
+
rootId: string;
|
|
143
|
+
currentId: string;
|
|
144
|
+
title: string;
|
|
145
|
+
repo: string | null;
|
|
146
|
+
state: TaskState;
|
|
147
|
+
code: string;
|
|
148
|
+
live: boolean;
|
|
149
|
+
archived: boolean;
|
|
150
|
+
broken: boolean;
|
|
151
|
+
updatedAt: string;
|
|
152
|
+
members: number[];
|
|
153
|
+
memberIds: string[];
|
|
154
|
+
};
|
|
155
|
+
export declare function workTidyRows(store: Store, now: Date, access: WorkSummaryAccess): WorkTidyRow[];
|