toolroll 0.8.1 → 0.9.0
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 +49 -0
- package/dist/agent-onboard.d.ts +7 -0
- package/dist/agent-onboard.js +23 -5
- package/dist/browser/workspace.css +1 -1
- package/dist/browser/workspace.js +21 -20
- package/dist/browser-workspace.d.ts +10 -0
- package/dist/chat-delivery.js +51 -3
- package/dist/chat-quiet.d.ts +28 -0
- package/dist/chat-quiet.js +161 -0
- package/dist/checkout-cleanup.d.ts +84 -0
- package/dist/checkout-cleanup.js +245 -0
- package/dist/cli.d.ts +1 -1
- package/dist/cli.js +3 -0
- package/dist/digest-times.d.ts +3 -0
- package/dist/digest-times.js +7 -0
- package/dist/dispose.js +2 -0
- package/dist/export.js +1 -1
- package/dist/first-run.d.ts +4 -1
- package/dist/first-run.js +2 -1
- package/dist/integrations-ui.d.ts +13 -0
- package/dist/integrations-ui.js +65 -0
- package/dist/integrations.d.ts +113 -0
- package/dist/integrations.js +526 -0
- package/dist/mcp.js +26 -0
- package/dist/operate.d.ts +5 -6
- package/dist/operate.js +254 -25
- package/dist/project-delete.js +1 -0
- package/dist/prompt.d.ts +7 -2
- package/dist/prompt.js +36 -7
- package/dist/publish.d.ts +11 -0
- package/dist/publish.js +9 -2
- package/dist/pull-request-flow.d.ts +140 -0
- package/dist/pull-request-flow.js +440 -0
- package/dist/pull-request-schema.d.ts +5 -0
- package/dist/pull-request-schema.js +21 -0
- package/dist/pull-request-ui.d.ts +23 -0
- package/dist/pull-request-ui.js +55 -0
- package/dist/pulls.d.ts +5 -0
- package/dist/pulls.js +11 -0
- package/dist/serve.d.ts +6 -0
- package/dist/serve.js +323 -26
- package/dist/setup-guide.d.ts +2 -2
- package/dist/storage-ui.d.ts +10 -0
- package/dist/storage-ui.js +67 -0
- package/dist/storage.d.ts +15 -1
- package/dist/storage.js +62 -7
- package/dist/store.d.ts +66 -5
- package/dist/store.js +160 -19
- package/dist/surface.js +2 -0
- package/dist/telegram-progress.d.ts +1 -0
- package/dist/telegram-progress.js +1 -1
- package/dist/telegram.js +95 -15
- package/dist/toolroll-update-ui.d.ts +2 -0
- package/dist/toolroll-update-ui.js +1 -1
- package/dist/toolroll-update.d.ts +26 -6
- package/dist/toolroll-update.js +188 -50
- package/dist/worktree.d.ts +24 -10
- package/dist/worktree.js +84 -44
- package/package.json +1 -1
package/dist/cli.d.ts
CHANGED
|
@@ -39,7 +39,7 @@ export type ParseResult = {
|
|
|
39
39
|
error: string;
|
|
40
40
|
};
|
|
41
41
|
type Write = (line: string) => void;
|
|
42
|
-
export declare const HELP = "toolroll \u2014 a control plane for unattended coding agents\n\nUsage\n toolroll [path...] report what is in flight\n toolroll pulls report what is waiting on a person\n toolroll graph report which work graph is already here\n toolroll repos list connected repositories, and how to adjust\n toolroll repos add <path> connect one (no path: the repo you are in)\n toolroll repos remove <path>\n toolroll repos add-from-github <owner/name> --root <dir>\n preview, then clone and connect (--yes)\n toolroll link put `toolroll` on your PATH\n toolroll unlink take it off again\n toolroll update update to the latest release: verified, drained, undoable\n (preview first; --yes, --now, --at HH:MM, --rollback)\n toolroll contract the machine contract: envelope version + capabilities\n (--commands dumps the declared command guide)\n toolroll skills install --claude-code [--dir <path>]\n install this binary's guides for Claude Code (preview first)\n toolroll skills install teach a repo's agents this queue exists (preview first)\n toolroll skills list the guides this exact binary serves\n toolroll skills get <name> print one guide (version-matched, never stale)\n toolroll demo a seeded throwaway sandbox \u2014 see it working in 90 seconds\n toolroll up app + builder for every saved project \u2014 the normal start\n toolroll onboard make your agent Toolroll's lead: add this repo, install its skill (--yes)\n toolroll status running, queued, ready results, release check and plan windows\n toolroll session native coding sessions through the running service\n toolroll connect save a private connection to your central service\n toolroll lead named leads on the connected service\n toolroll conversation shared and private conversations on that service\n toolroll chat --lead <id> --conversation <id> central chat (use --local for local chat)\n\nOperating the queue \u2014 `toolroll task` prints the whole surface,\nand any queue command + --help prints it too\n toolroll approver add <name>\n mint the credential that lets a person say yes\n toolroll ready what could be dispatched right now\n toolroll check-progress <run> current or final approved check progress\n toolroll task wait <id> [--timeout <seconds>]\n wait for this attempt to finish or need a person\n toolroll task add <title> queue work\n toolroll task scope <id> --goal <text>\n state what success is; approval binds to it\n toolroll task approve <id>\n the yes \u2014 nothing builds without one\n toolroll claim <id> --runner <name>\n toolroll heartbeat <lease> / release <lease> / reap\n toolroll tick --runner <name> --token <t> --repo <path>\n one unattended pass over the ready set\n toolroll chat --as <you> --token <t>\n talk to the mate: one conversation across every\n project; it proposes, you confirm (--say \"\u2026\" for one turn)\n toolroll serve --repo <path> advanced: run only the console\n (--editor vscode with --runner: file links\n open in VS Code on the device you browse from)\n toolroll watch / daemon install advanced: run only the builder\n toolroll reconcile recover what the last stretch left behind\n\nWith nothing connected it reports everything it can find below the working\ndirectory. Once you connect repositories it reports those instead.\n\nOptions\n --all report everything discoverable, ignoring connected repos\n --depth <n> directory levels to descend (default: 6)\n --hidden include dot-directories, which are skipped by default\n --dirty also read each working tree for uncommitted files\n --local branches only: no network, no pull requests, no issues\n --json emit a machine-readable envelope instead of a report\n -o <file> also write that envelope to a file (requires --json)\n -h, --help show this\n\nNothing is written, installed, or configured. Every repository is read\nthrough git with --no-optional-locks, so a scan never contends for the\nindex lock with an editor you have open.\n\n--dirty is off by default because reading a working tree is unbounded\nwork: on a large or cloud-synced repo `git status` can take minutes,\nwhere listing branches takes milliseconds.\n\nlink shows what it would do and changes nothing; add --yes to apply it,\nor --to <dir> to choose where. It never uses sudo, never leaves your\nhome directory, and never touches a file it did not create.";
|
|
42
|
+
export declare const HELP = "toolroll \u2014 a control plane for unattended coding agents\n\nUsage\n toolroll [path...] report what is in flight\n toolroll pulls report what is waiting on a person\n toolroll graph report which work graph is already here\n toolroll repos list connected repositories, and how to adjust\n toolroll repos add <path> connect one (no path: the repo you are in)\n toolroll repos remove <path>\n toolroll repos add-from-github <owner/name> --root <dir>\n preview, then clone and connect (--yes)\n toolroll link put `toolroll` on your PATH\n toolroll unlink take it off again\n toolroll update update to the latest release: verified, drained, undoable\n (preview first; --yes, --now, --at HH:MM, --rollback)\n toolroll contract the machine contract: envelope version + capabilities\n (--commands dumps the declared command guide)\n toolroll skills install --claude-code [--dir <path>]\n install this binary's guides for Claude Code (preview first)\n toolroll skills install teach a repo's agents this queue exists (preview first)\n toolroll skills list the guides this exact binary serves\n toolroll skills get <name> print one guide (version-matched, never stale)\n toolroll demo a seeded throwaway sandbox \u2014 see it working in 90 seconds\n toolroll up app + builder for every saved project \u2014 the normal start\n toolroll onboard make your agent Toolroll's lead: add this repo, install its skill (--yes)\n toolroll status running, queued, ready results, release check and plan windows\n toolroll integrations which integrations work, and what to do about the ones that don't (--json)\n toolroll session native coding sessions through the running service\n toolroll connect save a private connection to your central service\n toolroll lead named leads on the connected service\n toolroll conversation shared and private conversations on that service\n toolroll chat --lead <id> --conversation <id> central chat (use --local for local chat)\n\nOperating the queue \u2014 `toolroll task` prints the whole surface,\nand any queue command + --help prints it too\n toolroll approver add <name>\n mint the credential that lets a person say yes\n toolroll ready what could be dispatched right now\n toolroll check-progress <run> current or final approved check progress\n toolroll task wait <id> [--timeout <seconds>]\n wait for this attempt to finish or need a person\n toolroll task add <title> queue work\n toolroll task scope <id> --goal <text>\n state what success is; approval binds to it\n toolroll task approve <id>\n the yes \u2014 nothing builds without one\n toolroll claim <id> --runner <name>\n toolroll heartbeat <lease> / release <lease> / reap\n toolroll tick --runner <name> --token <t> --repo <path>\n one unattended pass over the ready set\n toolroll chat --as <you> --token <t>\n talk to the mate: one conversation across every\n project; it proposes, you confirm (--say \"\u2026\" for one turn)\n toolroll serve --repo <path> advanced: run only the console\n (--editor vscode with --runner: file links\n open in VS Code on the device you browse from)\n toolroll watch / daemon install advanced: run only the builder\n toolroll reconcile recover what the last stretch left behind\n\nWith nothing connected it reports everything it can find below the working\ndirectory. Once you connect repositories it reports those instead.\n\nOptions\n --all report everything discoverable, ignoring connected repos\n --depth <n> directory levels to descend (default: 6)\n --hidden include dot-directories, which are skipped by default\n --dirty also read each working tree for uncommitted files\n --local branches only: no network, no pull requests, no issues\n --json emit a machine-readable envelope instead of a report\n -o <file> also write that envelope to a file (requires --json)\n -h, --help show this\n\nNothing is written, installed, or configured. Every repository is read\nthrough git with --no-optional-locks, so a scan never contends for the\nindex lock with an editor you have open.\n\n--dirty is off by default because reading a working tree is unbounded\nwork: on a large or cloud-synced repo `git status` can take minutes,\nwhere listing branches takes milliseconds.\n\nlink shows what it would do and changes nothing; add --yes to apply it,\nor --to <dir> to choose where. It never uses sudo, never leaves your\nhome directory, and never touches a file it did not create.";
|
|
43
43
|
export declare function parseArgs(argv: readonly string[]): ParseResult;
|
|
44
44
|
/** The commands that operate the queue rather than report on the world. */
|
|
45
45
|
/** Every verb the CLI routes OUTSIDE runOperate. Exported (with
|
package/dist/cli.js
CHANGED
|
@@ -61,6 +61,7 @@ Usage
|
|
|
61
61
|
toolroll up app + builder for every saved project — the normal start
|
|
62
62
|
toolroll onboard make your agent Toolroll's lead: add this repo, install its skill (--yes)
|
|
63
63
|
toolroll status running, queued, ready results, release check and plan windows
|
|
64
|
+
toolroll integrations which integrations work, and what to do about the ones that don't (--json)
|
|
64
65
|
toolroll session native coding sessions through the running service
|
|
65
66
|
toolroll connect save a private connection to your central service
|
|
66
67
|
toolroll lead named leads on the connected service
|
|
@@ -197,10 +198,12 @@ export const OPERATE_COMMANDS = new Set([
|
|
|
197
198
|
"ledger",
|
|
198
199
|
"storage",
|
|
199
200
|
"monitoring",
|
|
201
|
+
"integrations",
|
|
200
202
|
"check-progress",
|
|
201
203
|
"spend",
|
|
202
204
|
"budget",
|
|
203
205
|
"retention",
|
|
206
|
+
"notifications",
|
|
204
207
|
"backup",
|
|
205
208
|
"restore",
|
|
206
209
|
"export",
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/** The evening digest choices Settings offers: off, or an evening hour — plus a time set from the command line,
|
|
2
|
+
* kept as chosen. Shared by the console page and its no-script fallback. */
|
|
3
|
+
export function digestTimes(current) {
|
|
4
|
+
const hours = ["17:00", "18:00", "19:00", "20:00", "21:00", "22:00"];
|
|
5
|
+
const times = current === null || hours.includes(current) ? hours : [...hours, current].sort();
|
|
6
|
+
return [["off", "Off"], ...times.map(one => [one, `At ${one}`])];
|
|
7
|
+
}
|
package/dist/dispose.js
CHANGED
|
@@ -132,6 +132,7 @@ function disposeBuildOutcomeLocked(context, result) {
|
|
|
132
132
|
const grant = store.publicationGrantFor(repo);
|
|
133
133
|
const headSha = store.getRun(runId)?.headRevision ?? null;
|
|
134
134
|
if (grant !== null &&
|
|
135
|
+
grant.publishOn !== "complete" &&
|
|
135
136
|
headSha !== null &&
|
|
136
137
|
headWithin(branch, grant.headPrefix) &&
|
|
137
138
|
(grant.selector === "all" || origin === "ours")) {
|
|
@@ -203,6 +204,7 @@ function disposeBuildOutcomeLocked(context, result) {
|
|
|
203
204
|
const grant = store.publicationGrantFor(repo);
|
|
204
205
|
const headSha = store.getRun(runId)?.headRevision ?? null;
|
|
205
206
|
if (grant !== null &&
|
|
207
|
+
grant.publishOn !== "complete" &&
|
|
206
208
|
headSha !== null &&
|
|
207
209
|
headWithin(branch, grant.headPrefix) &&
|
|
208
210
|
(grant.selector === "all" || origin === "ours")) {
|
package/dist/export.js
CHANGED
|
@@ -60,7 +60,7 @@ export function categoryOf(table) {
|
|
|
60
60
|
[/^(run|artifact|claim|worktree$|execution_slot|contest|tournament_terms|fallback_cycle|fallback_transition|held_session|session_turn|attended_authorization|criterion_review|diff_comment|proof_|review_request|repair_chain|incident|publication$|merge_|side_spend|knowledge_snapshot|learning_snapshot|skill_snapshot)/, "runs"],
|
|
61
61
|
[/^(task|hold$|plan_|scope_|tool_seal|decision|external_|skill_test)/, "tasks"],
|
|
62
62
|
[/^(project|approval_policy|capability|verify_command|worktree_setup|backend_grant|intake_grant|publication_grant|operating_mode|knowledge_|learning_|memory_|skill_)/, "projects"],
|
|
63
|
-
[/(_config|_default|_defaults)$|^(budget|model_|provider_|installation_fact|runtime_check|monitoring_status|quota|mode_rail|schema_version)/, "settings"],
|
|
63
|
+
[/(_config|_default|_defaults)$|^(budget|model_|provider_|installation_fact|runtime_check|monitoring_status|integration_check|quota|mode_rail|schema_version)/, "settings"],
|
|
64
64
|
];
|
|
65
65
|
return rules.find(([pattern]) => pattern.test(table))?.[1] ?? "other";
|
|
66
66
|
}
|
package/dist/first-run.d.ts
CHANGED
|
@@ -9,14 +9,17 @@ export type FirstRunAction = {
|
|
|
9
9
|
kind: "command";
|
|
10
10
|
command: string;
|
|
11
11
|
};
|
|
12
|
+
/** `checking`: the answer is not in yet, so the step is shown neither done nor to do. */
|
|
12
13
|
export type FirstRunStep = {
|
|
13
14
|
key: "agent" | "project" | "task";
|
|
14
15
|
title: string;
|
|
15
16
|
done: boolean;
|
|
16
17
|
action: FirstRunAction | null;
|
|
18
|
+
checking?: true;
|
|
17
19
|
};
|
|
20
|
+
/** agentSignedIn is null until this machine's first sign-in check has answered. */
|
|
18
21
|
export type FirstRunFacts = {
|
|
19
|
-
agentSignedIn: boolean;
|
|
22
|
+
agentSignedIn: boolean | null;
|
|
20
23
|
projects: number;
|
|
21
24
|
hasTask: boolean;
|
|
22
25
|
firstResultAt: string | null;
|
package/dist/first-run.js
CHANGED
|
@@ -15,7 +15,8 @@ export function firstRunSteps(facts) {
|
|
|
15
15
|
return null;
|
|
16
16
|
const project = facts.projects > 0;
|
|
17
17
|
return [
|
|
18
|
-
{ key: "agent", title: "Agent signed in", done:
|
|
18
|
+
facts.agentSignedIn === null ? { key: "agent", title: "Agent signed in", done: false, action: null, checking: true }
|
|
19
|
+
: { key: "agent", title: "Agent signed in", done: facts.agentSignedIn, action: facts.agentSignedIn ? null : { kind: "command", command: SIGN_IN_COMMAND } },
|
|
19
20
|
{ key: "project", title: "Project added", done: project, action: project ? null : { kind: "link", label: "Add a project", href: "/projects" } },
|
|
20
21
|
{ key: "task", title: "Your first task", done: facts.hasTask, action: facts.hasTask ? null : { kind: "link", label: "New task", href: "/tasks/new" } },
|
|
21
22
|
];
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Settings → Integrations: which integrations work. One row each: its name
|
|
3
|
+
* and state, the account it's connected as, when it last worked and last
|
|
4
|
+
* failed, what uses it, and one action. The list comes from saved checks; a
|
|
5
|
+
* render never waits on one (see integrations.ts).
|
|
6
|
+
*/
|
|
7
|
+
import { type Integration } from "./integrations.js";
|
|
8
|
+
export declare const INTEGRATIONS_CSS: string;
|
|
9
|
+
export declare function integrationsHtml(list: readonly Integration[], csrf: string, notice: {
|
|
10
|
+
said?: string | null;
|
|
11
|
+
problem?: string | null;
|
|
12
|
+
checking?: boolean;
|
|
13
|
+
}): string;
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Settings → Integrations: which integrations work. One row each: its name
|
|
3
|
+
* and state, the account it's connected as, when it last worked and last
|
|
4
|
+
* failed, what uses it, and one action. The list comes from saved checks; a
|
|
5
|
+
* render never waits on one (see integrations.ts).
|
|
6
|
+
*/
|
|
7
|
+
import { STATE_WORDS } from "./integrations.js";
|
|
8
|
+
const e = (value) => String(value ?? "").replace(/[&<>"']/g, c => ({ "&": "&", "<": "<", ">": ">", '"': """, "'": "'" })[c]);
|
|
9
|
+
const when = (at) => `${e(at.slice(0, 16).replace("T", " "))} UTC`;
|
|
10
|
+
export const INTEGRATIONS_CSS = `.integrations{max-width:760px;min-width:0}.integrations h2{margin:24px 0 6px;font-size:.9375rem}` +
|
|
11
|
+
`.integrations .integration{display:grid;grid-template-columns:minmax(0,1fr) auto;gap:4px 16px;align-items:start;padding:12px 0;border-top:1px solid var(--so-line);min-width:0}` +
|
|
12
|
+
`.integrations .integration>:not(.integration-action){grid-column:1}.integrations .integration:first-of-type{border-top:0}.integrations .integration-head{margin:0;display:flex;flex-wrap:wrap;gap:6px 10px;align-items:center;min-width:0}` +
|
|
13
|
+
`.integrations .integration-head strong{overflow-wrap:anywhere}.integrations .integration p{margin:2px 0 0;min-width:0;overflow-wrap:anywhere}` +
|
|
14
|
+
`.integrations .integration-action{grid-column:2;grid-row:1 / span 3;align-self:center;margin:0}.integrations .integration-action button,.integrations .integration-action .button-link{min-height:44px;white-space:nowrap}` +
|
|
15
|
+
`.integrations .integration .integration-action button{width:auto}.integrations .integration .integration-action a.integration-quiet{background:var(--so-paper);color:var(--so-ink);border:1px solid var(--so-input-line)}` +
|
|
16
|
+
`.integrations .integration-fix{color:var(--so-danger)}.integrations code{overflow-wrap:anywhere}.integrations details{margin-top:4px;font-size:.8125rem}.integrations summary{cursor:pointer;padding:4px 0}` +
|
|
17
|
+
`.integration-state{display:inline-flex;align-items:center;gap:6px;font-size:.75rem;font-weight:500;padding:0 8px;border-radius:5px;line-height:20px;white-space:nowrap}` +
|
|
18
|
+
`.integration-state i{width:6px;height:6px;border-radius:50%;background:currentColor}` +
|
|
19
|
+
`.integration-state--connected{color:var(--so-success);background:var(--so-success-soft)}.integration-state--broken{color:var(--so-danger);background:var(--so-danger-soft)}` +
|
|
20
|
+
`.integration-state--not-set-up,.integration-state--checking{color:var(--so-muted);background:var(--so-neutral-soft)}` +
|
|
21
|
+
`@media (max-width:560px){.integrations .integration{grid-template-columns:minmax(0,1fr)}.integrations .integration-action{grid-column:1;grid-row:auto;margin-top:8px}}`;
|
|
22
|
+
const GROUPS = [
|
|
23
|
+
["chat", "Chat"], ["code", "Code and issues"], ["mail", "Email"], ["tools", "MCP tools"], ["monitoring", "Monitoring"], ["agents", "Agents"],
|
|
24
|
+
];
|
|
25
|
+
function action(one, csrf) {
|
|
26
|
+
const a = one.action;
|
|
27
|
+
if (a.kind === "test") {
|
|
28
|
+
return csrf === "" ? "" : `<form method="post" action="/settings/integrations/test" class="integration-action"><input type="hidden" name="csrf" value="${e(csrf)}">` +
|
|
29
|
+
`<input type="hidden" name="key" value="${e(one.key)}"><button type="submit">Send test</button></form>`;
|
|
30
|
+
}
|
|
31
|
+
// A command is run where Toolroll runs; a page is linked. Fix and Set up both say which.
|
|
32
|
+
// Fix is the one filled action on the page; Set up stays quiet so a list of unused services doesn't shout.
|
|
33
|
+
if (a.href !== null)
|
|
34
|
+
return `<p class="integration-action"><a class="button-link${a.kind === "setup" ? " integration-quiet" : ""}" href="${e(a.href)}">${a.label}</a></p>`;
|
|
35
|
+
return "";
|
|
36
|
+
}
|
|
37
|
+
function row(one, csrf) {
|
|
38
|
+
const badge = !one.checked && one.state === "connected"
|
|
39
|
+
? `<span class="integration-state integration-state--checking"><i aria-hidden="true"></i>Checking</span>`
|
|
40
|
+
: `<span class="integration-state integration-state--${one.state}"><i aria-hidden="true"></i>${STATE_WORDS[one.state]}</span>`;
|
|
41
|
+
const facts = [one.account, one.detail].filter(Boolean).map(e).join(" · ");
|
|
42
|
+
const command = one.action.kind !== "test" ? one.action.command : null;
|
|
43
|
+
const fix = one.action.kind === "fix" ? `<p class="integration-fix" role="status">${e(one.action.words)}</p>` : "";
|
|
44
|
+
const run = command === null || (one.action.kind === "fix" && one.action.words.includes(command)) ? "" : `<p class="meta">Run <code>${e(command)}</code> on the computer running Toolroll.</p>`;
|
|
45
|
+
const seen = [one.lastSuccessAt === null ? null : `Last success ${when(one.lastSuccessAt)}`, one.usedBy.length === 0 ? null : `Used by ${e(one.usedBy.join(", "))}`].filter(Boolean);
|
|
46
|
+
const history = one.state === "not-set-up" || seen.length === 0 ? "" : `<p class="meta">${seen.join(" · ")}</p>`;
|
|
47
|
+
const lastError = one.lastError === null || one.state === "not-set-up" ? "" : one.state === "broken" && one.action.kind === "fix" && one.action.words === one.lastError
|
|
48
|
+
? `<p class="meta">Failed ${when(one.lastErrorAt ?? "")}</p>`
|
|
49
|
+
: `<details><summary>Last error ${one.lastErrorAt === null ? "" : when(one.lastErrorAt)}</summary><p class="meta">${e(one.lastError)}</p></details>`;
|
|
50
|
+
return `<div class="integration" data-integration="${e(one.key)}" data-state="${one.state}">` +
|
|
51
|
+
`<p class="integration-head"><strong>${e(one.name)}</strong> ${badge}</p>` +
|
|
52
|
+
action(one, csrf) +
|
|
53
|
+
(facts === "" ? "" : `<p class="meta">${facts}</p>`) + fix + run + history + lastError + `</div>`;
|
|
54
|
+
}
|
|
55
|
+
export function integrationsHtml(list, csrf, notice) {
|
|
56
|
+
const note = notice.problem ? `<p class="problem" role="alert">${e(notice.problem)}</p>` : notice.said ? `<p role="status">${e(notice.said)}</p>` : "";
|
|
57
|
+
const broken = list.filter(one => one.state === "broken").length;
|
|
58
|
+
const summary = broken > 0 ? `<p><strong>${broken} need${broken === 1 ? "s" : ""} fixing.</strong></p>`
|
|
59
|
+
: notice.checking || list.some(one => !one.checked) ? `<p class="meta" role="status">Checking in the background. Refresh in a few seconds.</p>` : "";
|
|
60
|
+
const sections = GROUPS.map(([group, title]) => {
|
|
61
|
+
const rows = list.filter(one => one.group === group);
|
|
62
|
+
return rows.length === 0 ? "" : `<h2>${title}</h2>${rows.map(one => row(one, csrf)).join("")}`;
|
|
63
|
+
}).join("");
|
|
64
|
+
return `<section class="integrations">${note}${summary}${sections}</section>`;
|
|
65
|
+
}
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import type { Store } from "./store.js";
|
|
2
|
+
import type { Runner } from "./backend.js";
|
|
3
|
+
import type { ConnectionChecker } from "./provider-connection.js";
|
|
4
|
+
export type IntegrationState = "connected" | "not-set-up" | "broken";
|
|
5
|
+
export type IntegrationGroup = "chat" | "code" | "mail" | "tools" | "monitoring" | "agents";
|
|
6
|
+
/** Where a person goes (or what they run) to set something up or fix it. */
|
|
7
|
+
export type IntegrationPlace = {
|
|
8
|
+
href: string | null;
|
|
9
|
+
command: string | null;
|
|
10
|
+
};
|
|
11
|
+
export type IntegrationAction = ({
|
|
12
|
+
kind: "setup";
|
|
13
|
+
label: "Set up";
|
|
14
|
+
} & IntegrationPlace) | {
|
|
15
|
+
kind: "test";
|
|
16
|
+
label: "Send test";
|
|
17
|
+
} | ({
|
|
18
|
+
kind: "fix";
|
|
19
|
+
label: "Fix";
|
|
20
|
+
words: string;
|
|
21
|
+
} & IntegrationPlace);
|
|
22
|
+
export type Integration = {
|
|
23
|
+
key: string;
|
|
24
|
+
group: IntegrationGroup;
|
|
25
|
+
name: string;
|
|
26
|
+
state: IntegrationState;
|
|
27
|
+
/** The account or workspace name, never a credential. */
|
|
28
|
+
account: string | null;
|
|
29
|
+
/** One short fact, such as which projects can be pushed to. */
|
|
30
|
+
detail: string | null;
|
|
31
|
+
/** Whether a check has answered for this integration yet (always true for those with nothing to check). */
|
|
32
|
+
checked: boolean;
|
|
33
|
+
checkedAt: string | null;
|
|
34
|
+
lastSuccessAt: string | null;
|
|
35
|
+
lastError: string | null;
|
|
36
|
+
lastErrorAt: string | null;
|
|
37
|
+
usedBy: string[];
|
|
38
|
+
action: IntegrationAction;
|
|
39
|
+
};
|
|
40
|
+
export declare const STATE_WORDS: Record<IntegrationState, string>;
|
|
41
|
+
export type ProbeResult = {
|
|
42
|
+
outcome: "ok";
|
|
43
|
+
account?: string | null;
|
|
44
|
+
detail?: string | null;
|
|
45
|
+
} | {
|
|
46
|
+
outcome: "failed";
|
|
47
|
+
problem: string;
|
|
48
|
+
account?: string | null;
|
|
49
|
+
detail?: string | null;
|
|
50
|
+
fix?: Partial<IntegrationPlace>;
|
|
51
|
+
} | {
|
|
52
|
+
outcome: "absent";
|
|
53
|
+
problem: string;
|
|
54
|
+
setup?: Partial<IntegrationPlace>;
|
|
55
|
+
};
|
|
56
|
+
export type IntegrationIo = {
|
|
57
|
+
store: Store;
|
|
58
|
+
/** The folder beside the database where credential files live; null when there is none. */
|
|
59
|
+
dir: string | null;
|
|
60
|
+
telegramTokenFile: string | null;
|
|
61
|
+
env: Record<string, string | undefined>;
|
|
62
|
+
/** The projects whose MCP tools and GitHub push rights are listed. */
|
|
63
|
+
repos: readonly string[];
|
|
64
|
+
fetch?: typeof fetch;
|
|
65
|
+
/** How `gh` runs (tests replace it). */
|
|
66
|
+
gh?: Runner;
|
|
67
|
+
/** The same sign-in check Settings → AI providers makes. */
|
|
68
|
+
checkConnection?: ConnectionChecker;
|
|
69
|
+
/** Where project tool secrets live. */
|
|
70
|
+
toolHome?: string;
|
|
71
|
+
/** Whether a mail server answers at host:port (tests replace it). */
|
|
72
|
+
reach?: (host: string, port: number, secure: boolean) => Promise<void>;
|
|
73
|
+
clock?: () => Date;
|
|
74
|
+
};
|
|
75
|
+
/** Remove every known secret value and anything shaped like a token; keep it to one short line. */
|
|
76
|
+
export declare function scrubIntegrationText(text: string, secrets?: readonly (string | null | undefined)[]): string;
|
|
77
|
+
type CheckRow = {
|
|
78
|
+
key: string;
|
|
79
|
+
outcome: "ok" | "failed" | "absent";
|
|
80
|
+
account: string | null;
|
|
81
|
+
detail: string | null;
|
|
82
|
+
problem: string | null;
|
|
83
|
+
checkedAt: string;
|
|
84
|
+
okAt: string | null;
|
|
85
|
+
error: string | null;
|
|
86
|
+
errorAt: string | null;
|
|
87
|
+
};
|
|
88
|
+
export declare function readIntegrationChecks(store: Store): Map<string, CheckRow>;
|
|
89
|
+
export declare function recordIntegrationCheck(store: Store, key: string, result: ProbeResult, now: Date): void;
|
|
90
|
+
/** Whether something answers at host:port: a connection opened and closed, no login and no message. */
|
|
91
|
+
export declare function reachServer(host: string, port: number, secure: boolean): Promise<void>;
|
|
92
|
+
/** Every integration as it stands now, from files and saved history only: no network, no process, never slow. */
|
|
93
|
+
export declare function integrationsNow(io: IntegrationIo): Integration[];
|
|
94
|
+
/** Run the checks (all, or the named ones), record each answer, and return the list. */
|
|
95
|
+
export declare function checkIntegrations(io: IntegrationIo, keys?: readonly string[]): Promise<Integration[]>;
|
|
96
|
+
/** The one line `status` and `onboard` add when something is Broken; null when nothing is. */
|
|
97
|
+
export declare function integrationsBrokenLine(list: readonly Integration[]): string | null;
|
|
98
|
+
/** The CLI's lines: one per integration, then the fix under anything Broken. */
|
|
99
|
+
export declare function renderIntegrations(list: readonly Integration[]): string[];
|
|
100
|
+
export type IntegrationMonitor = {
|
|
101
|
+
/** The list now, without waiting; starts a background check when the last one is older than the cache. */
|
|
102
|
+
list(): Integration[];
|
|
103
|
+
/** Check now and wait (Send test, or the first answer in a test). */
|
|
104
|
+
check(keys?: readonly string[]): Promise<Integration[]>;
|
|
105
|
+
/** Whether a background check is running. */
|
|
106
|
+
readonly checking: boolean;
|
|
107
|
+
};
|
|
108
|
+
/** Checks in the background with a short cache, as the first-run suggestions are: a render never waits on a check. */
|
|
109
|
+
export declare function createIntegrationMonitor(io: () => IntegrationIo, options?: {
|
|
110
|
+
cacheMs?: number;
|
|
111
|
+
now?: () => number;
|
|
112
|
+
}): IntegrationMonitor;
|
|
113
|
+
export {};
|