toolroll 0.9.86 → 0.9.88
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 +86 -0
- package/dist/acp-check.d.ts +9 -0
- package/dist/acp-check.js +40 -28
- 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/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 -70
- package/dist/browser-crew.js +1 -1
- package/dist/browser-workspace.d.ts +329 -183
- package/dist/browser-workspace.js +10 -9
- package/dist/chat-controls.d.ts +1 -1
- package/dist/chat-controls.js +1 -1
- 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/contracts/cli.d.ts +43 -0
- package/dist/contracts/cli.js +4 -0
- package/dist/contracts/console-api.d.ts +1 -1
- package/dist/contracts/console-api.js +1 -1
- package/dist/credentials-ui.d.ts +5 -3
- package/dist/credentials-ui.js +58 -31
- package/dist/design/kit.css +722 -0
- package/dist/design/kit.d.ts +208 -0
- package/dist/design/kit.js +177 -0
- package/dist/design/tokens.css +31 -1
- package/dist/discord-settings.js +8 -4
- package/dist/export-ui.d.ts +6 -4
- package/dist/export-ui.js +18 -8
- package/dist/flow-changed.d.ts +14 -0
- package/dist/flow-changed.js +53 -0
- package/dist/flow-gallery-ui.d.ts +41 -4
- package/dist/flow-gallery-ui.js +95 -63
- package/dist/flow-starters-ui.d.ts +3 -9
- package/dist/flow-starters-ui.js +12 -23
- 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.js +7 -3
- package/dist/flows-ui.d.ts +12 -6
- package/dist/flows-ui.js +141 -30
- 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/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 +38 -4
- 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-knowledge.d.ts +10 -10
- package/dist/publish.d.ts +11 -0
- package/dist/publish.js +16 -3
- 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 +23 -0
- package/dist/release.js +46 -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/retention-ui.d.ts +4 -3
- package/dist/retention-ui.js +15 -12
- package/dist/scope.d.ts +3 -0
- package/dist/scope.js +6 -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 +16 -16
- package/dist/server/chrome.d.ts +1 -1
- package/dist/server/chrome.js +19 -9
- package/dist/server/console.css +24 -12
- package/dist/server/flows.js +64 -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 +70 -0
- package/dist/server/kit.d.ts +419 -0
- package/dist/server/kit.js +369 -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 +22 -190
- package/dist/server/render-pages.js +123 -575
- 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 +35 -5
- package/dist/server/render-tasks.js +114 -85
- package/dist/server/route-table.js +20 -5
- package/dist/server/settings.js +276 -255
- package/dist/server/task-views.d.ts +8 -0
- package/dist/server/task-views.js +85 -2
- package/dist/server/tasks.d.ts +8 -0
- package/dist/server/tasks.js +95 -40
- 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/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 +112 -3
- package/dist/store.js +292 -20
- package/dist/surface.js +5 -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/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.js +11 -7
- package/dist/workspace-ui.d.ts +1 -1
- package/dist/workspace-ui.js +7 -4
- package/package.json +1 -1
|
@@ -1,13 +1,23 @@
|
|
|
1
1
|
import { type GalleryAnswers, type GalleryPreview, type GalleryTemplate, type GalleryTool } from "./flow-gallery.js";
|
|
2
2
|
import { type FlowDefinition, type FlowStage } from "./flows.js";
|
|
3
|
-
import { type Html } from "./html.js";
|
|
3
|
+
import { type Html, type HtmlValue } from "./html.js";
|
|
4
4
|
export declare const GALLERY_CSS: string;
|
|
5
5
|
/** A flow's main path, as a person reads it: from the start, each step's first way on (a sort's first answer, a choose's
|
|
6
6
|
* first option that goes somewhere, else next; a check's failure when passing just ends), never back to a step shown. */
|
|
7
7
|
export declare function mainPath(definition: FlowDefinition): FlowStage[];
|
|
8
|
-
/** A flow's steps in order, joined by arrows
|
|
8
|
+
/** A flow's steps in order, joined by arrows. */
|
|
9
9
|
export declare function stepStrip(definition: FlowDefinition): Html;
|
|
10
|
-
/**
|
|
10
|
+
/** One row of New flow: name · promise · one line · one outline action (the same shape for templates, kits, recipes and starters). */
|
|
11
|
+
export declare function templateRow(options: {
|
|
12
|
+
name: string;
|
|
13
|
+
promise: string;
|
|
14
|
+
line?: HtmlValue;
|
|
15
|
+
action?: HtmlValue;
|
|
16
|
+
attrs?: Html;
|
|
17
|
+
}): Html;
|
|
18
|
+
/** The groups New flow filters by: the template groups, then kits, recipes and starter flows. */
|
|
19
|
+
export declare const NEW_FLOW_GROUPS: ReadonlyArray<readonly [key: string, label: string]>;
|
|
20
|
+
/** The templates as rows, grouped (or one group): each with one "Use this". `repo` carries a chosen project to the template's page. */
|
|
11
21
|
export declare function galleryHtml(input: {
|
|
12
22
|
repo: string | null;
|
|
13
23
|
canUse: boolean;
|
|
@@ -15,8 +25,35 @@ export declare function galleryHtml(input: {
|
|
|
15
25
|
id: string;
|
|
16
26
|
state: "connected" | "open" | "taken";
|
|
17
27
|
}[] | null;
|
|
28
|
+
group?: string;
|
|
29
|
+
}): Html;
|
|
30
|
+
/**
|
|
31
|
+
* New flow: describe it (the shared composer; the words go to the lead as a chat draft, the project on its bar), then
|
|
32
|
+
* start from a template, filtered by group. Kits, recipes and starter flows arrive as their own rows.
|
|
33
|
+
*/
|
|
34
|
+
export declare function newFlowHtml(input: {
|
|
35
|
+
repo: string | null;
|
|
36
|
+
projects: readonly {
|
|
37
|
+
path: string;
|
|
38
|
+
name: string;
|
|
39
|
+
}[];
|
|
40
|
+
canUse: boolean;
|
|
41
|
+
group: string;
|
|
42
|
+
connections: readonly {
|
|
43
|
+
id: string;
|
|
44
|
+
state: "connected" | "open" | "taken";
|
|
45
|
+
}[] | null;
|
|
46
|
+
ask: {
|
|
47
|
+
value: string;
|
|
48
|
+
error: string | null;
|
|
49
|
+
};
|
|
50
|
+
kits: Html;
|
|
51
|
+
recipes: Html;
|
|
52
|
+
starters: Html;
|
|
53
|
+
said: string | null;
|
|
54
|
+
problem: string | null;
|
|
18
55
|
}): Html;
|
|
19
|
-
/** A template's page: its questions,
|
|
56
|
+
/** A template's page, a reading page: its steps and tools, its questions, what it will do and never do, then Create. */
|
|
20
57
|
export declare function galleryUseHtml(input: {
|
|
21
58
|
template: GalleryTemplate;
|
|
22
59
|
projects: readonly {
|
package/dist/flow-gallery-ui.js
CHANGED
|
@@ -1,40 +1,30 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
* its promise, its steps in order, what it needs and "Use this"
|
|
4
|
-
*
|
|
2
|
+
* New flow (Flows → New): describe it to the lead, or start from a template. Templates are rows grouped by what
|
|
3
|
+
* they're for, each with its promise, its steps in order, what it needs and one "Use this"; the starter kits ("With
|
|
4
|
+
* an agent"), the recipes ("One-time work") and the starter flows join them as groups. A template's page asks only
|
|
5
|
+
* what it needs, previews in plain words what it will do and never do, and creates it: the same answers it previewed.
|
|
5
6
|
* A template's tools show their marks and whether the project has them; one it lacks offers Connect first.
|
|
6
7
|
*/
|
|
7
8
|
import { brandMarkHtml } from "./brand-mark.js";
|
|
8
9
|
import { BLANK, GALLERY, GALLERY_GROUPS, galleryAdmitsAnyone, galleryDiagram, galleryToolsOf, OUTDATED_COMMANDS, SEND_RESULT, sendsAlready } from "./flow-gallery.js";
|
|
9
10
|
import { choiceTargets } from "./flows.js";
|
|
10
11
|
import { html, joinHtml, postForm, reauth } from "./html.js";
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
`.
|
|
14
|
-
`.
|
|
15
|
-
|
|
16
|
-
`.
|
|
17
|
-
|
|
18
|
-
`.gallery-steps
|
|
19
|
-
`.gallery-
|
|
20
|
-
|
|
21
|
-
`.
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
`.
|
|
25
|
-
`.
|
|
26
|
-
|
|
27
|
-
`.gallery-use{max-width:720px;min-width:0}.gallery-use .gallery-steps{margin:4px 0 12px}.gallery-use form{display:grid;gap:12px;margin:0}.gallery-use label{display:grid;gap:4px;font-weight:500}` +
|
|
28
|
-
`.gallery-use label small{font-weight:400;color:var(--so-muted)}.gallery-use label.gallery-check{display:flex;align-items:flex-start;gap:8px;font-weight:500}.gallery-use label.gallery-check input{margin-top:3px;width:16px;height:16px;min-height:0;flex:none}.gallery-use label.gallery-check small{display:block}` +
|
|
29
|
-
`.gallery-use input:not([type=hidden]),.gallery-use select{box-sizing:border-box;width:100%;max-width:100%;min-height:32px;padding:0 10px;border:1px solid var(--so-input-line);border-radius:8px;background:var(--so-paper);color:var(--so-ink);font:inherit}` +
|
|
30
|
-
`.gallery-preview{display:grid;gap:8px;padding:16px;border:1px solid var(--so-line);border-radius:10px;background:var(--so-paper);min-width:0}.gallery-preview h2{margin:0;font-size:.9375rem}` +
|
|
31
|
-
`.gallery-preview ul{margin:0;padding-left:18px;line-height:1.55}.gallery-preview p{margin:0;overflow-wrap:anywhere}.gallery-preview .gallery-never{font-weight:600}` +
|
|
32
|
-
`.gallery-preview details ol{margin:6px 0 0;padding-left:20px;white-space:pre-line;font-size:.875rem;line-height:1.5;color:var(--so-muted)}.gallery-preview details li{margin:0 0 6px;overflow-wrap:anywhere}` +
|
|
33
|
-
`.gallery-preview summary{cursor:pointer;min-height:36px;display:flex;align-items:center;font-weight:500}` +
|
|
34
|
-
`.gallery-actions{display:flex;flex-wrap:wrap;gap:8px;margin:0}.gallery-actions button{min-height:36px;white-space:nowrap}` +
|
|
35
|
-
`.gallery-use .gallery-actions button[value=create]:not(.secondary){background:var(--primary);color:var(--primary-foreground);border-color:var(--primary);font-weight:600}.gallery-heading{margin:36px 0 0;font-size:1.05rem}` +
|
|
36
|
-
`@media(max-width:760px){.gallery-grid{grid-template-columns:minmax(0,1fr)}.gallery-connect button{min-height:44px;justify-self:stretch}.gallery .gallery-card a.button-link{min-height:44px;align-self:stretch;justify-content:center}` +
|
|
37
|
-
`.gallery-use input:not([type=hidden]),.gallery-use select{font-size:16px;min-height:44px}.gallery-actions button{flex:1 1 auto;min-height:44px}}`;
|
|
12
|
+
import { acts, btn, chip, composer, count, disclose, doc, docHeader, dock, field, group, infoLine, input as kInput, note, notice, phoneHeader, segLinks, select, sendButton } from "./server/kit.js";
|
|
13
|
+
export const GALLERY_CSS = `.fl-new .k-sec{gap:12px}.fl-th{display:grid;justify-items:start;gap:10px}.fl-th .k-seg{flex-wrap:wrap}.fl-tsel{justify-self:stretch}.fl-tsel form{display:flex;gap:8px;margin:0}.fl-tsel .k-selw{flex:1}` +
|
|
14
|
+
`.fl-tlist{display:grid}.fl-tlist .k-grp{padding:0;margin-top:16px}.fl-tlist .k-grp:first-child{margin-top:0}` +
|
|
15
|
+
`.fl-trow{display:grid;grid-template-columns:minmax(0,1fr) auto;gap:16px;align-items:center;padding:12px 0;border-bottom:1px solid var(--so-line)}` +
|
|
16
|
+
`.fl-tmain{display:grid;gap:3px;min-width:0}.fl-tmain>b{font:500 13.5px/1.35 var(--so-font-sans);color:var(--so-ink)}.fl-tp{color:var(--so-muted);font-size:12.5px;line-height:1.45;overflow-wrap:anywhere}` +
|
|
17
|
+
`.fl-tline{display:flex;flex-wrap:wrap;align-items:center;gap:4px 8px;font-size:12px;margin-top:3px;color:var(--so-muted);min-width:0;overflow-wrap:anywhere}.fl-ta .k-btn{min-width:108px}.fl-ta form{margin:0}` +
|
|
18
|
+
// A template's steps in order, joined by arrows.
|
|
19
|
+
`.gallery-steps{display:inline-flex;flex-wrap:wrap;align-items:center;gap:4px 6px;list-style:none;padding:0;margin:0;font-size:12px;line-height:1.5;color:var(--so-ink)}` +
|
|
20
|
+
`.gallery-steps li{display:inline-flex;align-items:center;gap:6px;min-width:0;overflow-wrap:anywhere}.gallery-steps li>span[aria-hidden]{color:var(--so-muted)}.gallery-steps b{font-weight:500}` +
|
|
21
|
+
`.gallery-tools{display:inline-flex;flex-wrap:wrap;gap:4px 10px;list-style:none;padding:0;margin:0}.gallery-tools li{display:inline-flex;align-items:center;gap:6px;min-width:0}` +
|
|
22
|
+
`.fl-blank{padding-top:4px}.fl-describe-note{margin-top:2px}` +
|
|
23
|
+
// A template's page: the tools to connect, the form, and what it will do.
|
|
24
|
+
`.fl-use form{display:grid;gap:18px;margin:0}.fl-tool{display:grid;gap:10px;padding:12px 0;border-top:1px solid var(--so-line)}.fl-tool:last-child{border-bottom:1px solid var(--so-line)}.fl-tool form{gap:10px}` +
|
|
25
|
+
`.fl-tool-h{display:flex;align-items:center;gap:8px;flex-wrap:wrap;margin:0;font-size:13px;font-weight:500}.fl-check{display:flex;align-items:flex-start;gap:8px;font-size:13px;font-weight:500;cursor:pointer}.fl-check input{margin-top:3px;width:16px;height:16px;flex:none}.fl-check .k-note{display:block;font-weight:400}` +
|
|
26
|
+
`.fl-will{display:grid;gap:10px;padding:16px 0;border-top:1px solid var(--so-line);border-bottom:1px solid var(--so-line)}.fl-will ul{margin:0;padding-left:18px;font-size:13px;line-height:1.55}.fl-will ol{margin:6px 0 0;padding-left:20px;white-space:pre-line;font-size:12.5px;line-height:1.5;color:var(--so-muted)}.fl-will li{overflow-wrap:anywhere}.fl-never{margin:0;font-size:13px;font-weight:600}` +
|
|
27
|
+
`@media(max-width:760px){.fl-page .k-btn.k-docked,.fl-new .k-btn.k-docked,.fl-use .k-btn.k-docked,.fl-kit .k-btn.k-docked{display:none}.fl-trow{grid-template-columns:minmax(0,1fr);gap:10px;padding:14px 0}.fl-ta .k-btn{width:100%}.fl-ta:empty{display:none}.fl-check input{width:20px;height:20px}}`;
|
|
38
28
|
/** A flow's main path, as a person reads it: from the start, each step's first way on (a sort's first answer, a choose's
|
|
39
29
|
* first option that goes somewhere, else next; a check's failure when passing just ends), never back to a step shown. */
|
|
40
30
|
export function mainPath(definition) {
|
|
@@ -51,32 +41,64 @@ export function mainPath(definition) {
|
|
|
51
41
|
}
|
|
52
42
|
/** A step's short name in the strip: a person's own steps from their side. */
|
|
53
43
|
const stepName = (stage) => stage.kind === "choose" ? "You choose" : stage.kind === "approval" ? "You approve" : stage.kind === "send" ? "Sent to you" : stage.title;
|
|
54
|
-
/** A flow's steps in order, joined by arrows
|
|
44
|
+
/** A flow's steps in order, joined by arrows. */
|
|
55
45
|
export function stepStrip(definition) {
|
|
56
46
|
return html `<ol class="gallery-steps" aria-label="Steps">${mainPath(definition).map((one, i) => {
|
|
57
47
|
const name = stepName(one);
|
|
58
|
-
return html `<li>${i === 0 ? "" : html `<span aria-hidden="true">→</span>`}${one.kind === "choose" || one.kind === "approval" ? html `<b
|
|
48
|
+
return html `<li>${i === 0 ? "" : html `<span aria-hidden="true">→</span>`}${one.kind === "choose" || one.kind === "approval" ? html `<b>${name}</b>` : name}</li>`;
|
|
59
49
|
})}</ol>`;
|
|
60
50
|
}
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
/** A tool's state in words, as Settings → Integrations
|
|
64
|
-
const STATE = { connected:
|
|
65
|
-
const toolState = (tool) => tool.state === null ? "" :
|
|
51
|
+
/** What a template needs, in words: its needs, else its tools, else nothing to connect. */
|
|
52
|
+
const needsWords = (template) => template.needs.length > 0 ? `Needs ${template.needs.join(", ")}` : (template.tools ?? []).length > 0 ? "" : "Nothing to connect";
|
|
53
|
+
/** A tool's state in words, as Settings → Integrations says it; nothing without a project. */
|
|
54
|
+
const STATE = { connected: "Connected", open: "Not connected", taken: "Added another way" };
|
|
55
|
+
const toolState = (tool) => tool.state === null ? "" : chip(STATE[tool.state], tool.state === "connected" ? "done" : "", { dot: tool.state === "connected" });
|
|
66
56
|
const toolMark = (tool) => brandMarkHtml(tool.id.replace(/-desktop$/, ""), tool.label, tool.state === "connected");
|
|
67
57
|
const toolsList = (tools) => tools.length === 0 ? ""
|
|
68
|
-
: html `<ul class="gallery-tools" aria-label="Tools">${tools.map(one => html `<li data-tool="${one.id}" data-state="${one.state ?? "unknown"}">${toolMark(one)}<
|
|
69
|
-
/**
|
|
58
|
+
: html `<ul class="gallery-tools" aria-label="Tools">${tools.map(one => html `<li data-tool="${one.id}" data-state="${one.state ?? "unknown"}">${toolMark(one)}<span>${one.label}${one.state === "open" ? " (not connected)" : ""}</span></li>`)}</ul>`;
|
|
59
|
+
/** One row of New flow: name · promise · one line · one outline action (the same shape for templates, kits, recipes and starters). */
|
|
60
|
+
export function templateRow(options) {
|
|
61
|
+
return html `<div class="fl-trow"${options.attrs ?? ""}><div class="fl-tmain"><b>${options.name}</b><span class="fl-tp">${options.promise}</span>${options.line ? html `<span class="fl-tline">${options.line}</span>` : ""}</div><div class="fl-ta">${options.action ?? ""}</div></div>`;
|
|
62
|
+
}
|
|
63
|
+
/** The groups New flow filters by: the template groups, then kits, recipes and starter flows. */
|
|
64
|
+
export const NEW_FLOW_GROUPS = [["all", "All"], ...GALLERY_GROUPS.map(one => [one.id, one.label]), ["kits", "With an agent"], ["recipes", "One-time work"], ["starters", "Starter flows"]];
|
|
65
|
+
/** The templates as rows, grouped (or one group): each with one "Use this". `repo` carries a chosen project to the template's page. */
|
|
70
66
|
export function galleryHtml(input) {
|
|
71
67
|
const href = (id) => `/flows/new/${id}${input.repo === null ? "" : `?repo=${encodeURIComponent(input.repo)}`}`;
|
|
72
|
-
const
|
|
68
|
+
const rowOf = (template) => {
|
|
73
69
|
const definition = galleryDiagram(template);
|
|
74
|
-
|
|
70
|
+
const needs = needsWords(template);
|
|
71
|
+
return templateRow({ name: template.name, promise: template.promise, attrs: html ` data-template="${template.id}"`,
|
|
72
|
+
line: html `${stepStrip(definition)}${toolsList(galleryToolsOf(template, definition, input.repo === null ? null : input.connections ?? null))}${needs ? html `<span>· ${needs}</span>` : ""}`,
|
|
73
|
+
action: input.canUse ? btn("Use this", { sm: true, href: href(template.id), attrs: { "aria-label": `Use ${template.name}` } }) : "" });
|
|
75
74
|
};
|
|
76
|
-
const
|
|
77
|
-
return html
|
|
75
|
+
const shown = GALLERY_GROUPS.filter(one => input.group === undefined || input.group === "all" || input.group === one.id);
|
|
76
|
+
return html `${shown.map(one => { const templates = GALLERY.filter(template => template.group === one.id); return html `${group(one.label, count(templates.length))}<div data-group="${one.id}">${templates.map(rowOf)}</div>`; })}`;
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* New flow: describe it (the shared composer; the words go to the lead as a chat draft, the project on its bar), then
|
|
80
|
+
* start from a template, filtered by group. Kits, recipes and starter flows arrive as their own rows.
|
|
81
|
+
*/
|
|
82
|
+
export function newFlowHtml(input) {
|
|
83
|
+
const groupKey = NEW_FLOW_GROUPS.some(([key]) => key === input.group) ? input.group : "all";
|
|
84
|
+
const keep = (key) => `/flows/new?${new URLSearchParams({ ...(key === "all" ? {} : { group: key }), ...(input.repo === null ? {} : { repo: input.repo }) }).toString()}`.replace(/\?$/, "");
|
|
85
|
+
const current = input.projects.find(one => one.path === input.repo)?.name ?? input.projects[0]?.name ?? "";
|
|
86
|
+
const describe = input.canUse ? html `<section class="k-sec" aria-labelledby="fl-dh"><h3 id="fl-dh">Describe what you want</h3>\
|
|
87
|
+
<form method="get" action="/flows/new" id="fl-ask-form">${composer({ id: "fl-ask", name: "describe", label: "What should happen", rows: 3, value: input.ask.value, error: input.ask.error,
|
|
88
|
+
placeholder: "Every night, run npm run e2e. When it fails, find the cause, fix it and open a pull request.",
|
|
89
|
+
project: { current, items: input.projects.map(one => one.name), name: "project" }, send: sendButton({ label: "Draft the flow" }),
|
|
90
|
+
note: "Opens in chat: your lead drafts it for you to check. Nothing runs until you create it." })}</form></section>` : "";
|
|
91
|
+
const show = (key) => groupKey === "all" || groupKey === key;
|
|
92
|
+
const filter = html `<span class="k-desk-only">${segLinks("Templates", groupKey, NEW_FLOW_GROUPS.map(([key, label]) => ({ key, label, href: keep(key) })))}</span>\
|
|
93
|
+
<span class="k-phone-only fl-tsel"><form method="get" action="/flows/new">${input.repo === null ? "" : html `<input type="hidden" name="repo" value="${input.repo}">`}${select({ name: "group", label: "Templates", items: NEW_FLOW_GROUPS, current: groupKey, wide: true, attrs: { "data-k-autosubmit": "" } })}<noscript>${btn("Show", { sm: true, type: "submit" })}</noscript></form></span>`;
|
|
94
|
+
const templates = GALLERY_GROUPS.some(one => show(one.id)) ? galleryHtml({ repo: input.repo, canUse: input.canUse, connections: input.connections, group: groupKey }) : "";
|
|
95
|
+
const blank = input.canUse && groupKey === "all" ? html `<p class="k-note fl-blank"><a href="/flows/new/${BLANK.id}${input.repo === null ? "" : `?repo=${encodeURIComponent(input.repo)}`}">Start from a blank flow</a></p>` : "";
|
|
96
|
+
const list = html `${templates}${show("kits") ? input.kits : ""}${show("recipes") ? input.recipes : ""}${show("starters") ? input.starters : ""}${blank}`;
|
|
97
|
+
const said = input.problem ? notice(input.problem) : input.said ? infoLine(input.said) : "";
|
|
98
|
+
return html `<div class="fl-new" data-kit-page>${phoneHeader("New flow", { back: "/flows" })}${doc(docHeader({ title: "New flow", back: ["/flows", "Flows"], meta: input.canUse ? "Describe it in a sentence, or start from a template." : "An approver creates flows.", same: true }), said, describe, html `<section class="k-sec" aria-labelledby="fl-th"><div class="fl-th"><h3 id="fl-th">Start from a template</h3>${filter}</div><div class="fl-tlist">${list}</div></section>`)}\
|
|
99
|
+
${input.canUse ? dock(btn("Draft the flow", { variant: "primary", type: "submit", attrs: { form: "fl-ask-form" } })) : ""}</div>`;
|
|
78
100
|
}
|
|
79
|
-
/** A template's page: its questions,
|
|
101
|
+
/** A template's page, a reading page: its steps and tools, its questions, what it will do and never do, then Create. */
|
|
80
102
|
export function galleryUseHtml(input) {
|
|
81
103
|
const { template } = input;
|
|
82
104
|
const tools = input.tools ?? input.preview?.tools ?? [];
|
|
@@ -84,32 +106,42 @@ export function galleryUseHtml(input) {
|
|
|
84
106
|
const missing = tools.filter(one => one.state !== "connected" && one.state !== "taken");
|
|
85
107
|
// Each tool with its state; one the project lacks offers its own Connect (the Tools page's, with the same password), and comes back here.
|
|
86
108
|
const connect = (tool) => {
|
|
87
|
-
const head = html `<p class="
|
|
109
|
+
const head = html `<p class="fl-tool-h">${toolMark(tool)} ${tool.label} ${toolState(tool)}</p>`;
|
|
88
110
|
// Added another way, it is there: nothing to connect.
|
|
89
111
|
if (tool.state === "connected" || tool.state === "taken" || input.csrf === "")
|
|
90
|
-
return html `<div class="
|
|
112
|
+
return html `<div class="fl-tool" data-tool="${tool.id}" data-state="${String(tool.state)}">${head}</div>`;
|
|
91
113
|
const how = tool.id === "figma-desktop" ? "Open the Figma desktop app and turn on its Dev Mode MCP server in Preferences." : `You sign in on ${tool.label}, then come back here.`;
|
|
92
|
-
|
|
114
|
+
const fieldId = `fl-connect-${tool.id}`;
|
|
115
|
+
return html `<div class="fl-tool" data-tool="${tool.id}" data-state="open">${head}${postForm("/settings/tools/connect", html `${note(how)}${reauth(field({ id: fieldId, label: "Your Toolroll password", control: kInput({ id: fieldId, name: "password", type: "password", attrs: { autocomplete: "current-password", required: true } }) }))}${acts(btn(`Connect ${tool.label}`, { type: "submit", attrs: { name: "service", value: tool.id } }))}`, { hidden: { repo: input.repo, shown: input.repo, template: template.id } })}</div>`;
|
|
93
116
|
};
|
|
94
|
-
const toolsBlock = tools.length === 0 ? "" : html `<section class="
|
|
117
|
+
const toolsBlock = tools.length === 0 ? "" : html `<section class="k-sec" aria-label="Tools"><h3>Tools</h3><div>${tools.map(connect)}</div></section>`;
|
|
95
118
|
// Made without a tool, the preview names the zone that needs it.
|
|
96
119
|
const zones = (tool) => joinHtml(tool.zones.map(zone => html `“${zone}”`), " and ");
|
|
97
|
-
const needing = html `${missing.filter(one => one.zones.length > 0).map(one => html `<p class="
|
|
98
|
-
const
|
|
99
|
-
const value = input.answers[
|
|
100
|
-
const
|
|
101
|
-
if (
|
|
102
|
-
return
|
|
103
|
-
return
|
|
120
|
+
const needing = html `${missing.filter(one => one.zones.length > 0).map(one => html `<p class="k-note" data-needs-tool="${one.id}">${zones(one)} need${one.zones.length === 1 ? "s" : ""} ${one.label}, which isn't connected.</p>`)}${tools.filter(one => one.state === "taken" && one.zones.length > 0).map(one => html `<p class="k-note" data-tool-taken="${one.id}">${zones(one)} use${one.zones.length === 1 ? "s" : ""} ${one.label}, added another way.</p>`)}`;
|
|
121
|
+
const ask = (one, index) => {
|
|
122
|
+
const value = input.answers[one.key] ?? one.default;
|
|
123
|
+
const id = `fl-ask-${index}`;
|
|
124
|
+
if (one.key === "outdated")
|
|
125
|
+
return field({ id, label: one.label, control: select({ id, name: "outdated", items: Object.entries(OUTDATED_COMMANDS).map(([key, command]) => [key, command.label]), current: value, wide: true }) });
|
|
126
|
+
return field({ id, label: one.label, ...(one.hint === undefined ? {} : { hint: one.hint }), control: kInput({ id, name: one.key, value, hint: one.hint !== undefined,
|
|
127
|
+
attrs: { required: true, maxlength: one.key === "command" ? 500 : one.key === "team" ? 12 : 100, ...(one.key === "command" ? { spellcheck: "false", autocapitalize: "off" } : one.key === "team" ? { spellcheck: "false", autocapitalize: "characters" } : {}) } }) });
|
|
104
128
|
};
|
|
105
129
|
const project = input.projects.length > 1
|
|
106
|
-
?
|
|
130
|
+
? field({ id: "fl-use-project", label: "Project", control: select({ id: "fl-use-project", name: "repo", items: input.projects.map(one => [one.path, one.name]), current: input.repo, wide: true }) })
|
|
107
131
|
: html `<input type="hidden" name="repo" value="${input.repo}">`;
|
|
108
|
-
const preview = input.preview === null ? "" : html `<section class="
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
const
|
|
113
|
-
const
|
|
114
|
-
|
|
132
|
+
const preview = input.preview === null ? "" : html `<section class="k-sec fl-will" aria-labelledby="fl-will"><h3 id="fl-will">What it will do</h3><ul>${input.preview.built.does.map(one => html `<li>${one}</li>`)}</ul><p class="fl-never">${input.preview.built.never}</p>${needing}\
|
|
133
|
+
${input.preview.startsFrom.length === 0 ? note("Cards start when you add them.") : input.preview.startsFrom.map(one => note(`Starts from: ${one}`))}${disclose("Every step", html `<ol>${input.preview.steps.map(one => html `<li>${one.replace(/^\d+\.\s/, "")}</li>`)}</ol>`)}</section>`;
|
|
134
|
+
const sendResult = !sendsAlready(template) && html `<label class="fl-check"><input type="checkbox" name="${SEND_RESULT.key}" value="yes"${input.answers[SEND_RESULT.key] === "yes" && html ` checked`} data-send-result><span>${SEND_RESULT.title}<span class="k-note">When a card finishes, what was done comes to you in your chat apps.</span></span></label>`;
|
|
135
|
+
// A flow that brings in items from anyone takes the password to make (chat-management.md §0a.3): a step-up, which Passwordless stands in for.
|
|
136
|
+
const anyone = input.preview !== null && galleryAdmitsAnyone(template);
|
|
137
|
+
const step = anyone ? reauth(field({ id: "fl-use-password", label: "Your password", hint: "It brings in items from anyone.", control: kInput({ id: "fl-use-password", name: "password", type: "password", hint: true, attrs: { autocomplete: "current-password" } }) })) : "";
|
|
138
|
+
const creates = input.preview !== null;
|
|
139
|
+
const createButton = (docked) => btn("Create flow", { variant: missing.length > 0 ? "outline" : "primary", type: "submit", ...(docked ? { docked: true } : {}),
|
|
140
|
+
attrs: { name: "intent", value: "create", ...(missing.length > 0 ? { "data-without-tools": "" } : {}) } });
|
|
141
|
+
const actions = html `${step}${acts(creates ? createButton(true) : "", btn(creates ? "Update preview" : "Preview", { variant: creates ? "outline" : "primary", type: "submit", attrs: { name: "intent", value: "preview" } }))}${creates ? note(anyone ? "Creating it asks for your password: it brings in items from anyone." : "Creating it needs no password.") : ""}`;
|
|
142
|
+
const form = postForm(`/flows/new/${template.id}`, html `${project}${template.asks.map(ask)}${field({ id: "fl-use-name", label: "Name", control: kInput({ id: "fl-use-name", name: "name", value: input.name, attrs: { maxlength: 80 } }) })}${sendResult}${preview}${actions}`, { attrs: { "data-gallery-use": true, id: "fl-use-form" }, hidden: { previewed: input.preview?.digest ?? "" } });
|
|
143
|
+
const needs = needsWords(template);
|
|
144
|
+
const steps = input.diagram === null && !needs ? "" : html `<section class="k-sec" aria-label="Steps">${input.diagram !== null && stepStrip(input.diagram)}${needs ? note(needs) : ""}</section>`;
|
|
145
|
+
return html `<div class="fl-use" data-kit-page data-gallery-template="${template.id}">${phoneHeader(template.name, { back: "/flows/new" })}${doc(docHeader({ title: template.name, back: ["/flows/new", "New flow"], meta: template.promise, same: true }), input.problem !== null ? notice(input.problem) : "", input.said ? infoLine(input.said) : "", steps, toolsBlock, form)}\
|
|
146
|
+
${creates ? dock(btn("Create flow", { variant: "primary", type: "submit", attrs: { form: "fl-use-form", name: "intent", value: "create" } })) : dock(btn("Preview", { variant: "primary", type: "submit", attrs: { form: "fl-use-form", name: "intent", value: "preview" } }))}</div>`;
|
|
115
147
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
* what it will do and what it never does
|
|
2
|
+
* Starter flows (New flow → Starter flows): a project's starter flows, each switched on with one yes. A row says what
|
|
3
|
+
* it's for, what it will do and what it never does (the whole of what the yes agrees to), then one button. A starter
|
|
4
4
|
* that's on links to its flow; one that can't run here says why.
|
|
5
5
|
*/
|
|
6
6
|
import type { StarterView } from "./flow-starters.js";
|
|
@@ -8,14 +8,8 @@ import { type Html } from "./html.js";
|
|
|
8
8
|
export declare const STARTERS_CSS: string;
|
|
9
9
|
export declare function startersHtml(input: {
|
|
10
10
|
repo: string;
|
|
11
|
-
|
|
12
|
-
path: string;
|
|
13
|
-
name: string;
|
|
14
|
-
}[];
|
|
11
|
+
projectName: string;
|
|
15
12
|
starters: readonly StarterView[];
|
|
16
|
-
csrf: string;
|
|
17
13
|
canSwitch: boolean;
|
|
18
14
|
suggested: string | null;
|
|
19
|
-
said: string | null;
|
|
20
|
-
problem: string | null;
|
|
21
15
|
}): Html;
|
package/dist/flow-starters-ui.js
CHANGED
|
@@ -1,32 +1,21 @@
|
|
|
1
1
|
import { html, postForm } from "./html.js";
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
`.
|
|
6
|
-
`.starter .starter-never{font-size:.875rem;font-weight:500}.starter-action{grid-column:2;grid-row:1 / span 4;align-self:center;margin:0}` +
|
|
7
|
-
`.starter-action button,.starter-action .button-link{min-height:44px;white-space:nowrap}.starter .starter-action button{width:auto}` +
|
|
8
|
-
`.starter .starter-action a.starter-open{background:var(--so-paper);color:var(--so-ink);border:1px solid var(--so-input-line)}` +
|
|
9
|
-
`.starter[data-suggested="true"]{margin:0 -12px;padding:14px 12px;border:1px solid var(--so-accent,var(--so-line));border-radius:10px}` +
|
|
10
|
-
`.starter-on{display:inline-flex;align-items:center;gap:6px;font-size:.75rem;font-weight:500;padding:0 8px;border-radius:5px;line-height:20px;color:var(--so-success);background:var(--so-success-soft)}` +
|
|
11
|
-
`.starter-on i{width:6px;height:6px;border-radius:50%;background:currentColor}.starters .starter-blocked{color:var(--so-muted);font-size:.875rem}` +
|
|
12
|
-
`.starters form.starters-project{display:flex;flex-wrap:wrap;gap:8px;align-items:flex-end;margin:0 0 8px}.starters form.starters-project label{display:grid;gap:4px}` +
|
|
13
|
-
`@media (max-width:560px){.starter{grid-template-columns:minmax(0,1fr)}.starter .starter-action{grid-column:1;grid-row:auto;margin-top:10px}` +
|
|
14
|
-
`.starter .starter-action button,.starter .starter-action .button-link{width:100%;justify-content:center}.starters select{font-size:16px}}`;
|
|
2
|
+
import { templateRow } from "./flow-gallery-ui.js";
|
|
3
|
+
import { btn, chip, group, note } from "./server/kit.js";
|
|
4
|
+
export const STARTERS_CSS = `.fl-starter-does{margin:2px 0 0;padding-left:18px;font-size:12.5px;line-height:1.5;color:var(--so-muted)}.fl-starter-never{font-weight:500;color:var(--so-ink)}` +
|
|
5
|
+
`.fl-trow[data-suggested="true"]{border-left:2px solid var(--so-ink);padding-left:12px}`;
|
|
15
6
|
export function startersHtml(input) {
|
|
16
|
-
const note = input.problem ? html `<p class="problem" role="alert">${input.problem}</p>` : input.said ? html `<p role="status">${input.said}</p>` : "";
|
|
17
|
-
const selector = input.projects.length > 1
|
|
18
|
-
? html `<form class="starters-project" method="get" action="/settings/flows"><label>Project<select name="repo">${input.projects.map(one => html `<option value="${one.path}"${one.path === input.repo && html ` selected`}>${one.name}</option>`)}</select></label><button>Show</button></form>` : "";
|
|
19
7
|
// The one asked about ("Do this every time…") comes first, marked.
|
|
20
8
|
const ordered = [...input.starters].sort((a, b) => Number(b.id === input.suggested) - Number(a.id === input.suggested));
|
|
21
9
|
const rows = ordered.map(one => {
|
|
22
|
-
const action = one.on !== null ?
|
|
10
|
+
const action = one.on !== null ? btn("Open flow", { sm: true, href: `/flows/${one.on.flow}` })
|
|
23
11
|
: one.blocked !== null || !input.canSwitch ? ""
|
|
24
|
-
: postForm("/settings/flows/on",
|
|
12
|
+
: postForm("/settings/flows/on", btn("Switch on", { sm: true, type: "submit", attrs: { "aria-label": `Switch on ${one.name}` } }), { hidden: { repo: input.repo, starter: one.id } });
|
|
25
13
|
// What the yes agrees to, in full, before it; nothing to agree to once it's on or can't be.
|
|
26
|
-
const
|
|
27
|
-
|
|
28
|
-
|
|
14
|
+
const line = one.on !== null ? chip("On", "done")
|
|
15
|
+
: one.blocked !== null ? html `<span>${one.blocked}</span>`
|
|
16
|
+
: html `<span><ul class="fl-starter-does">${one.does.map(words => html `<li>${words}</li>`)}</ul><span class="fl-starter-never">${one.never}</span></span>`;
|
|
17
|
+
return templateRow({ name: one.name, promise: one.summary, line, action, attrs: html ` id="starter-${one.id}" data-starter="${one.id}" data-suggested="${String(one.id === input.suggested && one.on === null)}"` });
|
|
29
18
|
});
|
|
30
|
-
const nobody = !input.canSwitch && input.starters.some(one => one.on === null) ?
|
|
31
|
-
return html
|
|
19
|
+
const nobody = !input.canSwitch && input.starters.some(one => one.on === null) ? note("An approver switches these on.") : "";
|
|
20
|
+
return html `${group(`Starter flows for ${input.projectName}`)}<div data-group="starters">${rows}</div>${nobody}`;
|
|
32
21
|
}
|
|
@@ -1,6 +1,26 @@
|
|
|
1
1
|
/** The words a "runs on its own" grant signs (flow-unattended.ts), kept free of server imports so the flow page's
|
|
2
2
|
* signing form says each limit exactly as the signature will. */
|
|
3
3
|
export declare const dollarWords: (micro: number) => string;
|
|
4
|
+
/** The most runs a day a grant may allow. */
|
|
5
|
+
export declare const UNATTENDED_MAX_RUNS = 50;
|
|
6
|
+
/** The largest per-attempt budget a grant may name: $100. */
|
|
7
|
+
export declare const UNATTENDED_MAX_BUDGET_MICROUSD = 100000000;
|
|
8
|
+
/** A per-attempt budget as typed ("5", "$7.50", "" for no cap), the one way the server and the flow page read it. */
|
|
9
|
+
export declare function unattendedBudgetOf(typed: string): {
|
|
10
|
+
ok: true;
|
|
11
|
+
value: number | null;
|
|
12
|
+
} | {
|
|
13
|
+
ok: false;
|
|
14
|
+
message: string;
|
|
15
|
+
};
|
|
16
|
+
/** Runs a day as typed ("3", "" for no limit), the one way the server and the flow page read it. */
|
|
17
|
+
export declare function unattendedRunsOf(typed: string): {
|
|
18
|
+
ok: true;
|
|
19
|
+
value: number | null;
|
|
20
|
+
} | {
|
|
21
|
+
ok: false;
|
|
22
|
+
message: string;
|
|
23
|
+
};
|
|
4
24
|
/** "Oct 19": the end's day, where space is short (the exact time goes beside it, on hover). */
|
|
5
25
|
export declare const untilDayWords: (iso: string) => string;
|
|
6
26
|
/** "Oct 19, 14:05 UTC": the expiry as every surface says it. */
|
|
@@ -1,6 +1,30 @@
|
|
|
1
1
|
/** The words a "runs on its own" grant signs (flow-unattended.ts), kept free of server imports so the flow page's
|
|
2
2
|
* signing form says each limit exactly as the signature will. */
|
|
3
3
|
export const dollarWords = (micro) => `$${(micro / 1_000_000).toFixed(2)}`;
|
|
4
|
+
/** The most runs a day a grant may allow. */
|
|
5
|
+
export const UNATTENDED_MAX_RUNS = 50;
|
|
6
|
+
/** The largest per-attempt budget a grant may name: $100. */
|
|
7
|
+
export const UNATTENDED_MAX_BUDGET_MICROUSD = 100_000_000;
|
|
8
|
+
/** A per-attempt budget as typed ("5", "$7.50", "" for no cap), the one way the server and the flow page read it. */
|
|
9
|
+
export function unattendedBudgetOf(typed) {
|
|
10
|
+
const text = typed.trim().replace(/^\$/, "");
|
|
11
|
+
if (text === "")
|
|
12
|
+
return { ok: true, value: null };
|
|
13
|
+
const value = Number(text);
|
|
14
|
+
if (!/^\d+(\.\d{1,2})?$/.test(text) || value <= 0 || Math.round(value * 1_000_000) > UNATTENDED_MAX_BUDGET_MICROUSD)
|
|
15
|
+
return { ok: false, message: `Give the budget in dollars, up to ${dollarWords(UNATTENDED_MAX_BUDGET_MICROUSD)}, or leave it empty.` };
|
|
16
|
+
return { ok: true, value: Math.round(value * 1_000_000) };
|
|
17
|
+
}
|
|
18
|
+
/** Runs a day as typed ("3", "" for no limit), the one way the server and the flow page read it. */
|
|
19
|
+
export function unattendedRunsOf(typed) {
|
|
20
|
+
const text = typed.trim();
|
|
21
|
+
if (text === "")
|
|
22
|
+
return { ok: true, value: null };
|
|
23
|
+
const value = Number(text);
|
|
24
|
+
if (!/^\d+$/.test(text) || !Number.isInteger(value) || value < 1 || value > UNATTENDED_MAX_RUNS)
|
|
25
|
+
return { ok: false, message: `Give runs a day as 1 to ${UNATTENDED_MAX_RUNS}, or leave it empty.` };
|
|
26
|
+
return { ok: true, value };
|
|
27
|
+
}
|
|
4
28
|
/** "Oct 19": the end's day, where space is short (the exact time goes beside it, on hover). */
|
|
5
29
|
export const untilDayWords = (iso) => new Intl.DateTimeFormat("en-US", { month: "short", day: "numeric", timeZone: "UTC" }).format(new Date(iso));
|
|
6
30
|
/** "Oct 19, 14:05 UTC": the expiry as every surface says it. */
|
|
@@ -8,10 +8,8 @@ export declare const FLOW_ACCEPTANCE: {
|
|
|
8
8
|
report: AcceptanceCriterion[];
|
|
9
9
|
task: AcceptanceCriterion[];
|
|
10
10
|
};
|
|
11
|
-
/** The
|
|
12
|
-
export
|
|
13
|
-
/** The largest per-attempt budget a grant may name: $100. */
|
|
14
|
-
export declare const UNATTENDED_MAX_BUDGET_MICROUSD = 100000000;
|
|
11
|
+
/** The limits a grant may name, and their one parser, live with its words (the flow page validates with them too). */
|
|
12
|
+
export { UNATTENDED_MAX_BUDGET_MICROUSD, UNATTENDED_MAX_RUNS } from "./flow-unattended-words.js";
|
|
15
13
|
/** The code a script schedule runs, as signed: the script's version and content digest, and for one that runs a project
|
|
16
14
|
* file, that file's path and, sorted by path, the digest of its bytes and of every project file it statically imports. */
|
|
17
15
|
export type SignedScript = {
|
|
@@ -77,7 +75,7 @@ export type UnattendedGrant = {
|
|
|
77
75
|
* revision signed, and neither project protected. Shared by every dispatch belt. */
|
|
78
76
|
export declare const grantStandsSql: (digest: string, now: string, repo: string) => string;
|
|
79
77
|
/** Why a grant ended, as its row and ledger line say it. A renewal's ledger line is the new signature's. */
|
|
80
|
-
export type UnattendedEnd = "expired" | "revoked" | "renewed" | "schedule changed" | "script changed" | "project protected" | "access changed";
|
|
78
|
+
export type UnattendedEnd = "expired" | "revoked" | "renewed" | "flow changed" | "schedule changed" | "script changed" | "project protected" | "access changed";
|
|
81
79
|
/**
|
|
82
80
|
* The one road for what ends grants from outside this module (store.ts calls it): a flow's schedule added, paused,
|
|
83
81
|
* resumed or removed; a project's approval rules protecting work; an account's access or standing changing. Every live
|
|
@@ -222,6 +220,12 @@ export type UnattendedView = {
|
|
|
222
220
|
optional: string | null;
|
|
223
221
|
}[];
|
|
224
222
|
schedulesDigest: string;
|
|
223
|
+
/** No live grant, and the latest one was ended by a change (the flow, a script, a schedule, the project's rules or
|
|
224
|
+
* the signer's access) before it would have run out: why, and when, so the page says it no longer runs on its own. */
|
|
225
|
+
ended: {
|
|
226
|
+
why: string;
|
|
227
|
+
at: string;
|
|
228
|
+
} | null;
|
|
225
229
|
};
|
|
226
230
|
export declare function unattendedViewOf(store: Store, flow: FlowRow, viewer: {
|
|
227
231
|
name: string;
|