toolroll 0.9.54 → 0.9.56
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 +19 -0
- package/README.md +29 -12
- package/dist/action-ledger.d.ts +2 -4
- package/dist/action-ledger.js +2 -10
- package/dist/agent-onboard.js +4 -1
- package/dist/approval-policy.d.ts +5 -5
- package/dist/approval-policy.js +4 -4
- package/dist/approval-rules-ui.d.ts +3 -2
- package/dist/approval-rules-ui.js +17 -22
- package/dist/assignment-ui.d.ts +3 -2
- package/dist/assignment-ui.js +4 -11
- package/dist/backup-ui.d.ts +2 -1
- package/dist/backup-ui.js +16 -27
- package/dist/brand-mark.d.ts +3 -2
- package/dist/brand-mark.js +4 -4
- package/dist/browser/workspace.css +1 -1
- package/dist/browser/workspace.js +25 -25
- package/dist/browser-shell.d.ts +2 -1
- package/dist/browser-shell.js +9 -9
- package/dist/browser-workspace.d.ts +14 -15
- package/dist/browser-workspace.js +0 -3
- package/dist/budget-alerts.js +3 -3
- package/dist/chat-actions.d.ts +23 -18
- package/dist/chat-actions.js +110 -88
- package/dist/chat-ask.d.ts +2 -2
- package/dist/chat-ask.js +5 -5
- package/dist/chat-channel.d.ts +15 -15
- package/dist/chat-channel.js +20 -20
- package/dist/chat-controls.d.ts +0 -4
- package/dist/chat-controls.js +0 -1
- package/dist/chat-core.js +1 -1
- package/dist/chat-delivery-state.js +2 -2
- package/dist/chat-delivery.d.ts +2 -2
- package/dist/chat-delivery.js +27 -34
- package/dist/chat-flow.js +3 -3
- package/dist/chat-messages.d.ts +1 -1
- package/dist/chat-messages.js +4 -4
- package/dist/chat-migration.d.ts +1 -1
- package/dist/chat-migration.js +11 -11
- package/dist/chat-polish.d.ts +5 -4
- package/dist/chat-polish.js +9 -10
- package/dist/chat-rooms.js +3 -3
- package/dist/chat-warmth.d.ts +2 -2
- package/dist/check-levels-ui.d.ts +2 -1
- package/dist/check-levels-ui.js +26 -53
- package/dist/cli.d.ts +3 -1
- package/dist/cli.js +15 -10
- package/dist/coding-shipping-ui.d.ts +2 -1
- package/dist/coding-shipping-ui.js +6 -6
- package/dist/coding-ui.d.ts +3 -1
- package/dist/coding-ui.js +30 -29
- package/dist/contracts/chat-actions.d.ts +32 -27
- package/dist/contracts/chat-actions.js +29 -11
- package/dist/contracts/chat-content.d.ts +1 -1
- package/dist/contracts/chat-content.js +1 -1
- package/dist/contracts/chat-tables.js +4 -4
- package/dist/contracts/cli.d.ts +1 -3
- package/dist/contracts/console-api.d.ts +7 -7
- package/dist/contracts/console-api.js +13 -12
- package/dist/contracts/flow-propose.d.ts +3 -3
- package/dist/contracts/flow.d.ts +132 -125
- package/dist/contracts/flow.js +27 -12
- package/dist/contracts/health.d.ts +1 -3
- package/dist/contracts/lead-context.d.ts +1 -1
- package/dist/contracts/lead-context.js +2 -2
- package/dist/contracts/lead-tools.d.ts +20 -18
- package/dist/contracts/lead-tools.js +15 -15
- package/dist/contracts/stage-output.d.ts +1 -1
- package/dist/contracts/stage-output.js +1 -1
- package/dist/contracts/store-json.d.ts +23 -22
- package/dist/contracts/store-json.js +17 -17
- package/dist/contracts/{teammate-turn.d.ts → subagent-turn.d.ts} +5 -5
- package/dist/contracts/{teammate-turn.js → subagent-turn.js} +7 -7
- package/dist/control-ui.d.ts +5 -4
- package/dist/control-ui.js +9 -34
- package/dist/converse.d.ts +23 -23
- package/dist/converse.js +28 -28
- package/dist/coordinator-proposals.d.ts +2 -2
- package/dist/coordinator-proposals.js +1 -1
- package/dist/coordinator.js +1 -1
- package/dist/credentials-ui.d.ts +5 -4
- package/dist/credentials-ui.js +23 -24
- package/dist/demo-chat.d.ts +3 -3
- package/dist/demo-chat.js +29 -76
- package/dist/deprecations.d.ts +12 -0
- package/dist/deprecations.js +20 -0
- package/dist/desktop-update-gate.js +8 -4
- package/dist/desktop-update.js +6 -1
- package/dist/discord-chat.js +6 -6
- package/dist/discord-settings.d.ts +3 -2
- package/dist/discord-settings.js +17 -42
- package/dist/dispatch.d.ts +1 -1
- package/dist/dispatch.js +1 -1
- package/dist/envelope.js +10 -1
- package/dist/evidence-pack.d.ts +2 -1
- package/dist/evidence-pack.js +45 -49
- package/dist/export-ui.d.ts +7 -2
- package/dist/export-ui.js +4 -10
- package/dist/export.js +7 -7
- package/dist/flow-engine.d.ts +2 -2
- package/dist/flow-engine.js +21 -21
- package/dist/flow-gallery-ui.d.ts +4 -3
- package/dist/flow-gallery-ui.js +28 -43
- package/dist/flow-insights.d.ts +1 -1
- package/dist/flow-share.js +13 -8
- package/dist/flow-starters-ui.d.ts +2 -1
- package/dist/flow-starters-ui.js +12 -15
- package/dist/flow-steps.d.ts +5 -5
- package/dist/flow-steps.js +19 -19
- package/dist/flow-triggers.d.ts +2 -1
- package/dist/flow-triggers.js +17 -15
- package/dist/flows-cli.js +1 -1
- package/dist/flows-ui.d.ts +4 -3
- package/dist/flows-ui.js +38 -49
- package/dist/flows.js +21 -20
- package/dist/guides.js +112 -6
- package/dist/html.d.ts +76 -0
- package/dist/html.js +173 -0
- package/dist/integrations-ui.d.ts +2 -1
- package/dist/integrations-ui.js +19 -23
- package/dist/invoke.js +1 -1
- package/dist/kits-ui.d.ts +5 -4
- package/dist/kits-ui.js +22 -24
- package/dist/kits.d.ts +12 -12
- package/dist/kits.js +31 -31
- package/dist/knowledge-ui.d.ts +6 -5
- package/dist/knowledge-ui.js +26 -25
- package/dist/{mate-cli.d.ts → lead-cli.d.ts} +11 -11
- package/dist/{mate-cli.js → lead-cli.js} +38 -38
- package/dist/lead-commitments.js +4 -4
- package/dist/lead-context.d.ts +12 -2
- package/dist/lead-context.js +15 -17
- package/dist/{mate-contract.d.ts → lead-contract.d.ts} +3 -3
- package/dist/{mate-contract.js → lead-contract.js} +7 -6
- package/dist/{mate-doors.d.ts → lead-doors.d.ts} +6 -6
- package/dist/{mate-doors.js → lead-doors.js} +24 -24
- package/dist/lead-follow.d.ts +5 -5
- package/dist/lead-follow.js +9 -9
- package/dist/lead-people.d.ts +5 -5
- package/dist/lead-people.js +14 -14
- package/dist/{mate-progress.d.ts → lead-progress.d.ts} +9 -9
- package/dist/{mate-progress.js → lead-progress.js} +3 -3
- package/dist/{mate-tools.d.ts → lead-tools.d.ts} +19 -19
- package/dist/{mate-tools.js → lead-tools.js} +109 -101
- package/dist/{mate.d.ts → lead.d.ts} +27 -27
- package/dist/{mate.js → lead.js} +102 -102
- package/dist/ledger-view.d.ts +2 -1
- package/dist/ledger-view.js +27 -29
- package/dist/limits-ui.d.ts +3 -2
- package/dist/limits-ui.js +8 -8
- package/dist/live-bus.d.ts +50 -8
- package/dist/live-bus.js +133 -50
- package/dist/mcp-connect.d.ts +1 -1
- package/dist/mcp-connect.js +3 -3
- package/dist/mcp-core.js +1 -1
- package/dist/mcp-oauth.js +35 -36
- package/dist/memory-pass.d.ts +2 -2
- package/dist/memory-pass.js +7 -7
- package/dist/models-ui.d.ts +2 -1
- package/dist/models-ui.js +25 -35
- package/dist/monitoring-ui.d.ts +4 -3
- package/dist/monitoring-ui.js +13 -26
- package/dist/notification-rows.d.ts +2 -2
- package/dist/openrouter-models.d.ts +2 -1
- package/dist/openrouter-models.js +5 -9
- package/dist/operate.d.ts +3 -3
- package/dist/operate.js +46 -41
- package/dist/people-audit-ui.d.ts +2 -1
- package/dist/people-audit-ui.js +13 -13
- package/dist/policy-ui.d.ts +3 -2
- package/dist/policy-ui.js +17 -23
- package/dist/policy.d.ts +1 -1
- package/dist/principal.d.ts +1 -1
- package/dist/principal.js +2 -2
- package/dist/project-delete-ui.d.ts +4 -3
- package/dist/project-delete-ui.js +16 -28
- package/dist/project-delete.d.ts +1 -1
- package/dist/project-delete.js +26 -26
- package/dist/project-memory.js +1 -1
- package/dist/project-tools.d.ts +1 -1
- package/dist/project-tools.js +1 -1
- package/dist/pull-request-ui.d.ts +2 -1
- package/dist/pull-request-ui.js +17 -32
- package/dist/recipe-ui.d.ts +8 -7
- package/dist/recipe-ui.js +39 -32
- package/dist/release-adapters.d.ts +60 -0
- package/dist/release-adapters.js +435 -0
- package/dist/release-cli.d.ts +24 -0
- package/dist/release-cli.js +181 -0
- package/dist/release-gate.d.ts +80 -0
- package/dist/release-gate.js +150 -0
- package/dist/release-links.d.ts +29 -0
- package/dist/release-links.js +102 -0
- package/dist/release.d.ts +331 -0
- package/dist/release.js +550 -0
- package/dist/reply-shape.d.ts +2 -1
- package/dist/reply-shape.js +5 -5
- package/dist/repository-context-ui.d.ts +2 -1
- package/dist/repository-context-ui.js +2 -7
- package/dist/request-budget-ui.d.ts +2 -1
- package/dist/request-budget-ui.js +16 -16
- package/dist/result-review.d.ts +2 -3
- package/dist/result-review.js +3 -9
- package/dist/retention-ui.d.ts +7 -1
- package/dist/retention-ui.js +7 -11
- package/dist/retention.js +4 -4
- package/dist/scope.d.ts +2 -2
- package/dist/scout-report.d.ts +1 -1
- package/dist/scout-report.js +1 -1
- package/dist/serve.d.ts +11 -2
- package/dist/serve.js +105 -99
- package/dist/server/chat.d.ts +7 -10
- package/dist/server/chat.js +984 -1021
- package/dist/server/chrome.d.ts +291 -0
- package/dist/server/chrome.js +747 -0
- package/dist/server/console.css +2819 -0
- package/dist/server/flows.d.ts +2 -6
- package/dist/server/flows.js +854 -773
- package/dist/server/guards.d.ts +1 -1
- package/dist/server/guards.js +8 -5
- package/dist/server/handler-context.d.ts +1 -1
- package/dist/server/handler-context.js +1 -1
- package/dist/server/handler-registry.d.ts +8 -6
- package/dist/server/handler-registry.js +14 -2
- package/dist/server/http.d.ts +274 -0
- package/dist/server/http.js +239 -0
- package/dist/server/live.d.ts +34 -0
- package/dist/server/live.js +309 -0
- package/dist/server/pages.d.ts +2 -6
- package/dist/server/pages.js +808 -789
- package/dist/server/people-tokens.d.ts +1 -6
- package/dist/server/people-tokens.js +581 -573
- package/dist/server/remote.d.ts +4 -7
- package/dist/server/remote.js +39 -70
- package/dist/server/render-chat.d.ts +402 -0
- package/dist/server/render-chat.js +927 -0
- package/dist/server/render-pages.d.ts +535 -0
- package/dist/server/render-pages.js +1277 -0
- package/dist/server/render-people.d.ts +32 -0
- package/dist/server/render-people.js +121 -0
- package/dist/server/render-results.d.ts +744 -0
- package/dist/server/render-results.js +2237 -0
- package/dist/server/render-settings.d.ts +154 -0
- package/dist/server/render-settings.js +580 -0
- package/dist/server/render-tasks.d.ts +632 -0
- package/dist/server/render-tasks.js +2345 -0
- package/dist/server/request-context.d.ts +46 -0
- package/dist/server/request-context.js +17 -0
- package/dist/server/route-table.d.ts +25 -3
- package/dist/server/route-table.js +167 -77
- package/dist/server/runtime.d.ts +28 -20
- package/dist/server/runtime.js +7 -1
- package/dist/server/session.d.ts +138 -0
- package/dist/server/session.js +94 -0
- package/dist/server/settings.d.ts +1 -5
- package/dist/server/settings.js +2186 -2121
- package/dist/server/task-views.d.ts +11 -4
- package/dist/server/task-views.js +26 -22
- package/dist/server/tasks.d.ts +9 -9
- package/dist/server/tasks.js +1489 -1420
- package/dist/server-telemetry.d.ts +4 -3
- package/dist/server-telemetry.js +4 -8
- package/dist/setup-guide.d.ts +2 -2
- package/dist/skills-ui.d.ts +4 -3
- package/dist/skills-ui.js +14 -17
- package/dist/skills.d.ts +1 -1
- package/dist/skills.js +2 -1
- package/dist/slack-chat.d.ts +2 -2
- package/dist/slack-chat.js +6 -6
- package/dist/slack-settings.d.ts +3 -2
- package/dist/slack-settings.js +17 -42
- package/dist/spend-ui.d.ts +5 -4
- package/dist/spend-ui.js +17 -40
- package/dist/spend.d.ts +14 -14
- package/dist/spend.js +31 -31
- package/dist/sso-ui.d.ts +3 -2
- package/dist/sso-ui.js +26 -27
- package/dist/storage-ui.d.ts +2 -1
- package/dist/storage-ui.js +14 -33
- package/dist/store-baseline.d.ts +19 -0
- package/dist/store-baseline.js +2873 -0
- package/dist/store.d.ts +197 -249
- package/dist/store.js +747 -3084
- package/dist/subagent-admin.d.ts +53 -0
- package/dist/{teammate-admin.js → subagent-admin.js} +37 -37
- package/dist/{teammate-desk.d.ts → subagent-desk.d.ts} +22 -23
- package/dist/{teammate-desk.js → subagent-desk.js} +26 -35
- package/dist/{teammate-memory.d.ts → subagent-memory.d.ts} +17 -17
- package/dist/{teammate-memory.js → subagent-memory.js} +41 -41
- package/dist/{teammate-question.d.ts → subagent-question.d.ts} +6 -6
- package/dist/{teammate-question.js → subagent-question.js} +17 -17
- package/dist/{teammate-tools.d.ts → subagent-tools.d.ts} +18 -18
- package/dist/{teammate-tools.js → subagent-tools.js} +36 -36
- package/dist/{teammate-week.d.ts → subagent-week.d.ts} +10 -10
- package/dist/{teammate-week.js → subagent-week.js} +23 -23
- package/dist/{teammate-work.d.ts → subagent-work.d.ts} +21 -21
- package/dist/{teammate-work.js → subagent-work.js} +50 -50
- package/dist/subagents-ui.d.ts +22 -0
- package/dist/subagents-ui.js +137 -0
- package/dist/{teammates.d.ts → subagents.d.ts} +11 -11
- package/dist/{teammates.js → subagents.js} +21 -21
- package/dist/subscription-chat.d.ts +13 -13
- package/dist/subscription-chat.js +19 -19
- package/dist/surface.d.ts +19 -2
- package/dist/surface.js +20 -33
- package/dist/task-composer.d.ts +2 -1
- package/dist/task-composer.js +7 -7
- package/dist/task-status.d.ts +4 -3
- package/dist/task-status.js +15 -16
- package/dist/team-cli.d.ts +1 -0
- package/dist/team-cli.js +14 -2
- package/dist/team-http.d.ts +5 -9
- package/dist/team-http.js +5 -63
- package/dist/team-leads.js +13 -13
- package/dist/team-runtime.d.ts +2 -2
- package/dist/team-runtime.js +8 -8
- package/dist/team-ui.d.ts +2 -1
- package/dist/team-ui.js +2 -5
- package/dist/teams-chat.js +6 -6
- package/dist/teams-settings.d.ts +3 -2
- package/dist/teams-settings.js +20 -31
- package/dist/telegram-flow.d.ts +3 -3
- package/dist/telegram-flow.js +3 -3
- package/dist/{telegram-mate.d.ts → telegram-lead.d.ts} +5 -5
- package/dist/{telegram-mate.js → telegram-lead.js} +27 -27
- package/dist/telegram-settings.d.ts +3 -2
- package/dist/telegram-settings.js +14 -25
- package/dist/telegram-status.js +1 -1
- package/dist/telegram-team.d.ts +1 -1
- package/dist/telegram-team.js +5 -5
- package/dist/telegram.d.ts +1 -1
- package/dist/telegram.js +9 -30
- package/dist/text-limits.d.ts +18 -18
- package/dist/text-limits.js +18 -18
- package/dist/toolroll-update-ui.d.ts +3 -2
- package/dist/toolroll-update-ui.js +35 -39
- package/dist/toolroll-update.d.ts +8 -0
- package/dist/toolroll-update.js +45 -8
- package/dist/tools-ui.d.ts +5 -2
- package/dist/tools-ui.js +31 -31
- package/dist/when-html.d.ts +11 -3
- package/dist/when-html.js +5 -6
- package/dist/workspace-ui.d.ts +4 -2
- package/dist/workspace-ui.js +10 -10
- package/package.json +1 -2
- package/dist/server/shared.d.ts +0 -3151
- package/dist/server/shared.js +0 -12001
- package/dist/teammate-admin.d.ts +0 -53
- package/dist/teammates-ui.d.ts +0 -20
- package/dist/teammates-ui.js +0 -166
package/dist/spend.d.ts
CHANGED
|
@@ -7,8 +7,8 @@
|
|
|
7
7
|
* Models), or at the provider's highest listed price when the model isn't
|
|
8
8
|
* listed; work with no tokens and no cost is "unpriced" and said so.
|
|
9
9
|
*
|
|
10
|
-
* Spend counts toward a project (a run's task, a
|
|
11
|
-
* a person (who filed the task, or had the chat), a
|
|
10
|
+
* Spend counts toward a project (a run's task, a subagent's, a project chat),
|
|
11
|
+
* a person (who filed the task, or had the chat), a subagent (its turns, and
|
|
12
12
|
* tasks it filed), and the whole installation. Budgets are per calendar
|
|
13
13
|
* month (UTC).
|
|
14
14
|
*/
|
|
@@ -30,7 +30,7 @@ export declare function claudeBillingFrom(seen: {
|
|
|
30
30
|
model: string | null;
|
|
31
31
|
planWindows: boolean;
|
|
32
32
|
}): Billing | null;
|
|
33
|
-
/** How Claude bills on this computer when Toolroll gives it no key (
|
|
33
|
+
/** How Claude bills on this computer when Toolroll gives it no key (subagent turns, drafts): as its keyless runs
|
|
34
34
|
* were last seen, and its plan until one has been. */
|
|
35
35
|
export declare function claudeMachineBilling(db: Database): Billing;
|
|
36
36
|
/** Whether API work on this provider can be priced: it reports its cost, or the catalogue has its prices. */
|
|
@@ -60,8 +60,8 @@ export declare function priceWork(db: Database, work: {
|
|
|
60
60
|
source: PriceSource;
|
|
61
61
|
price: Price | null;
|
|
62
62
|
};
|
|
63
|
-
export declare const SPEND_SCHEMA = "\nCREATE TABLE IF NOT EXISTS run_spend (\n run INTEGER PRIMARY KEY,\n microusd INTEGER,\n source TEXT NOT NULL CHECK (source IN ('reported', 'estimated', 'unpriced', 'subscription')),\n billing TEXT NOT NULL CHECK (billing IN ('subscription', 'api-key')),\n -- 1 once the run's own evidence set it (a key it was given, what its CLI said); a later turn can only make it a key.\n billing_fixed INTEGER NOT NULL DEFAULT 0,\n price_model TEXT,\n input_usd REAL,\n output_usd REAL,\n priced_at TEXT NOT NULL\n);\nCREATE TABLE IF NOT EXISTS budget (\n id INTEGER PRIMARY KEY AUTOINCREMENT,\n scope_kind TEXT NOT NULL CHECK (scope_kind IN ('installation', 'project', 'person', '
|
|
64
|
-
export type BudgetScope = "installation" | "project" | "person" | "
|
|
63
|
+
export declare const SPEND_SCHEMA = "\nCREATE TABLE IF NOT EXISTS run_spend (\n run INTEGER PRIMARY KEY,\n microusd INTEGER,\n source TEXT NOT NULL CHECK (source IN ('reported', 'estimated', 'unpriced', 'subscription')),\n billing TEXT NOT NULL CHECK (billing IN ('subscription', 'api-key')),\n -- 1 once the run's own evidence set it (a key it was given, what its CLI said); a later turn can only make it a key.\n billing_fixed INTEGER NOT NULL DEFAULT 0,\n price_model TEXT,\n input_usd REAL,\n output_usd REAL,\n priced_at TEXT NOT NULL\n);\nCREATE TABLE IF NOT EXISTS budget (\n id INTEGER PRIMARY KEY AUTOINCREMENT,\n scope_kind TEXT NOT NULL CHECK (scope_kind IN ('installation', 'project', 'person', 'subagent')),\n scope_key TEXT NOT NULL,\n limit_microusd INTEGER NOT NULL CHECK (limit_microusd > 0),\n hard_stop INTEGER NOT NULL DEFAULT 1,\n created_by TEXT NOT NULL,\n created_at TEXT NOT NULL,\n updated_by TEXT NOT NULL,\n updated_at TEXT NOT NULL,\n removed_by TEXT,\n removed_at TEXT\n);\nCREATE UNIQUE INDEX IF NOT EXISTS budget_live ON budget (scope_kind, scope_key) WHERE removed_at IS NULL;\nCREATE TABLE IF NOT EXISTS provider_limit (\n provider TEXT NOT NULL,\n window TEXT NOT NULL,\n used_percent REAL NOT NULL,\n window_minutes INTEGER,\n resets_at TEXT,\n reached INTEGER NOT NULL DEFAULT 0,\n plan TEXT,\n observed_at TEXT NOT NULL,\n PRIMARY KEY (provider, window)\n);\n-- Spend that isn't a run, a subagent turn or a chat: a flow's Claude draft.\nCREATE TABLE IF NOT EXISTS side_spend (\n id INTEGER PRIMARY KEY AUTOINCREMENT,\n kind TEXT NOT NULL CHECK (kind IN ('draft')),\n repo TEXT,\n provider TEXT NOT NULL,\n model TEXT,\n cost_usd REAL,\n billing TEXT NOT NULL CHECK (billing IN ('subscription', 'api-key')),\n at TEXT NOT NULL\n);\nCREATE INDEX IF NOT EXISTS side_spend_at ON side_spend (at);\nCREATE TABLE IF NOT EXISTS provider_account (\n provider TEXT PRIMARY KEY,\n billing TEXT NOT NULL CHECK (billing IN ('subscription', 'api-key')),\n observed_at TEXT NOT NULL\n);\nCREATE INDEX IF NOT EXISTS chat_turn_created ON chat_turn (created_at);\nCREATE INDEX IF NOT EXISTS mate_turn_created ON mate_turn (created_at);\nCREATE INDEX IF NOT EXISTS subagent_turn_at ON subagent_turn (at);\n";
|
|
64
|
+
export type BudgetScope = "installation" | "project" | "person" | "subagent";
|
|
65
65
|
export type Budget = {
|
|
66
66
|
id: number;
|
|
67
67
|
scope: BudgetScope;
|
|
@@ -74,12 +74,12 @@ export type Budget = {
|
|
|
74
74
|
/** One piece of spend, whatever made it, with what it counts toward. */
|
|
75
75
|
export type SpendItem = {
|
|
76
76
|
at: string;
|
|
77
|
-
kind: "run" | "
|
|
77
|
+
kind: "run" | "subagent" | "chat" | "sort" | "draft";
|
|
78
78
|
microusd: number | null;
|
|
79
79
|
source: PriceSource;
|
|
80
80
|
project: string | null;
|
|
81
81
|
person: string | null;
|
|
82
|
-
|
|
82
|
+
subagent: number | null;
|
|
83
83
|
provider: string;
|
|
84
84
|
model: string | null;
|
|
85
85
|
tokensIn: number | null;
|
|
@@ -100,9 +100,9 @@ export declare function monthNamed(name: string | null): {
|
|
|
100
100
|
to: string;
|
|
101
101
|
name: string;
|
|
102
102
|
} | null;
|
|
103
|
-
/** Every
|
|
104
|
-
export declare function
|
|
105
|
-
export declare function
|
|
103
|
+
/** Every subagent's name by id (its handle when its soul file names none). */
|
|
104
|
+
export declare function subagentNames(db: Database): Map<number, string>;
|
|
105
|
+
export declare function subagentFilers(db: Database): (repo: string | null, filedBy: string | null, kind: string | null) => number | null;
|
|
106
106
|
/** Who a task's work counts toward: its own filer, or, for a revision filed by automation (an automatic repair), the
|
|
107
107
|
* filer of the task it revises, up its ancestry (bounded). */
|
|
108
108
|
export declare function filersOf(db: Database): (taskRef: number) => {
|
|
@@ -110,7 +110,7 @@ export declare function filersOf(db: Database): (taskRef: number) => {
|
|
|
110
110
|
filedBy: string | null;
|
|
111
111
|
kind: string | null;
|
|
112
112
|
};
|
|
113
|
-
/** Every piece of spend in [from, to): runs (by when they started),
|
|
113
|
+
/** Every piece of spend in [from, to): runs (by when they started), subagent turns and chat turns. `modeOf` says how
|
|
114
114
|
* a provider bills when a record doesn't (older rows): its current login or key. */
|
|
115
115
|
export declare function spendItems(db: Database, from: string, to: string, modeOf?: (provider: string) => Billing): SpendItem[];
|
|
116
116
|
/** Whether a piece of spend counts toward a budget. */
|
|
@@ -134,9 +134,9 @@ export type BudgetHold = {
|
|
|
134
134
|
remainingMicrousd: number | null;
|
|
135
135
|
};
|
|
136
136
|
/** Why work waits on a budget, in words. */
|
|
137
|
-
export declare function budgetHoldWords(hold: BudgetHold, month: string,
|
|
137
|
+
export declare function budgetHoldWords(hold: BudgetHold, month: string, subagentName?: string): string;
|
|
138
138
|
/** Each budget's month so far: what counted toward it, and how many pieces of work had no price. */
|
|
139
139
|
export declare function budgetStates(budgets: readonly Budget[], items: readonly SpendItem[]): BudgetState[];
|
|
140
|
-
/** A budget in words: "The shop project's", "alex's", "Maya's (
|
|
141
|
-
export declare function budgetLabel(budget: Pick<Budget, "scope" | "key">,
|
|
140
|
+
/** A budget in words: "The shop project's", "alex's", "Maya's (subagent)", "The whole installation's". */
|
|
141
|
+
export declare function budgetLabel(budget: Pick<Budget, "scope" | "key">, subagentName?: string): string;
|
|
142
142
|
export declare const usd: (microusd: number | null) => string;
|
package/dist/spend.js
CHANGED
|
@@ -30,7 +30,7 @@ export function claudeBillingFrom(seen) {
|
|
|
30
30
|
return "subscription";
|
|
31
31
|
return seen.keySource === "none" ? "api-key" : null;
|
|
32
32
|
}
|
|
33
|
-
/** How Claude bills on this computer when Toolroll gives it no key (
|
|
33
|
+
/** How Claude bills on this computer when Toolroll gives it no key (subagent turns, drafts): as its keyless runs
|
|
34
34
|
* were last seen, and its plan until one has been. */
|
|
35
35
|
export function claudeMachineBilling(db) {
|
|
36
36
|
return seenBilling(db, "claude") ?? "subscription";
|
|
@@ -100,7 +100,7 @@ CREATE TABLE IF NOT EXISTS run_spend (
|
|
|
100
100
|
);
|
|
101
101
|
CREATE TABLE IF NOT EXISTS budget (
|
|
102
102
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
103
|
-
scope_kind TEXT NOT NULL CHECK (scope_kind IN ('installation', 'project', 'person', '
|
|
103
|
+
scope_kind TEXT NOT NULL CHECK (scope_kind IN ('installation', 'project', 'person', 'subagent')),
|
|
104
104
|
scope_key TEXT NOT NULL,
|
|
105
105
|
limit_microusd INTEGER NOT NULL CHECK (limit_microusd > 0),
|
|
106
106
|
hard_stop INTEGER NOT NULL DEFAULT 1,
|
|
@@ -123,7 +123,7 @@ CREATE TABLE IF NOT EXISTS provider_limit (
|
|
|
123
123
|
observed_at TEXT NOT NULL,
|
|
124
124
|
PRIMARY KEY (provider, window)
|
|
125
125
|
);
|
|
126
|
-
-- Spend that isn't a run, a
|
|
126
|
+
-- Spend that isn't a run, a subagent turn or a chat: a flow's Claude draft.
|
|
127
127
|
CREATE TABLE IF NOT EXISTS side_spend (
|
|
128
128
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
129
129
|
kind TEXT NOT NULL CHECK (kind IN ('draft')),
|
|
@@ -142,7 +142,7 @@ CREATE TABLE IF NOT EXISTS provider_account (
|
|
|
142
142
|
);
|
|
143
143
|
CREATE INDEX IF NOT EXISTS chat_turn_created ON chat_turn (created_at);
|
|
144
144
|
CREATE INDEX IF NOT EXISTS mate_turn_created ON mate_turn (created_at);
|
|
145
|
-
CREATE INDEX IF NOT EXISTS
|
|
145
|
+
CREATE INDEX IF NOT EXISTS subagent_turn_at ON subagent_turn (at);
|
|
146
146
|
`;
|
|
147
147
|
/** The calendar month (UTC) `at` falls in: [start, end) as ISO strings, and its name (YYYY-MM). */
|
|
148
148
|
export function monthOf(at) {
|
|
@@ -156,25 +156,25 @@ export function monthNamed(name) {
|
|
|
156
156
|
return null;
|
|
157
157
|
return monthOf(new Date(`${name}-01T00:00:00.000Z`));
|
|
158
158
|
}
|
|
159
|
-
/** The
|
|
160
|
-
* its project. A later
|
|
161
|
-
/** A
|
|
159
|
+
/** The subagent behind a filing: tasks a subagent files are filed as its name ("Maya (AI)", from its soul file), in
|
|
160
|
+
* its project. A later subagent with the same name and project wins, as it would read on the card. */
|
|
161
|
+
/** A subagent's name as its soul file gives it ("Maya"), or null. */
|
|
162
162
|
const soulName = (soul) => {
|
|
163
163
|
const front = /^---\s*\n([\s\S]*?)\n---/.exec(String(soul ?? ""));
|
|
164
164
|
return front === null ? null : /^name:\s*(.+?)\s*$/m.exec(front[1])?.[1] ?? null;
|
|
165
165
|
};
|
|
166
|
-
/** Every
|
|
167
|
-
export function
|
|
168
|
-
return new Map(db.prepare("SELECT id, handle, soul FROM
|
|
166
|
+
/** Every subagent's name by id (its handle when its soul file names none). */
|
|
167
|
+
export function subagentNames(db) {
|
|
168
|
+
return new Map(db.prepare("SELECT id, handle, soul FROM subagent").all().map(row => [Number(row["id"]), soulName(row["soul"]) ?? String(row["handle"])]));
|
|
169
169
|
}
|
|
170
|
-
export function
|
|
170
|
+
export function subagentFilers(db) {
|
|
171
171
|
const byName = new Map();
|
|
172
|
-
for (const row of db.prepare("SELECT id, repo, handle, soul FROM
|
|
172
|
+
for (const row of db.prepare("SELECT id, repo, handle, soul FROM subagent ORDER BY id").all()) {
|
|
173
173
|
const name = soulName(row["soul"]);
|
|
174
174
|
for (const one of [String(row["handle"]), ...(name === null ? [] : [name])])
|
|
175
175
|
byName.set(`${String(row["repo"])}\n${one} (AI)`, Number(row["id"]));
|
|
176
176
|
}
|
|
177
|
-
return (repo, filedBy, kind) => kind !== "
|
|
177
|
+
return (repo, filedBy, kind) => kind !== "subagent" || repo === null || filedBy === null ? null : byName.get(`${repo}\n${filedBy}`) ?? null;
|
|
178
178
|
}
|
|
179
179
|
/** Who a task's work counts toward: its own filer, or, for a revision filed by automation (an automatic repair), the
|
|
180
180
|
* filer of the task it revises, up its ancestry (bounded). */
|
|
@@ -198,10 +198,10 @@ export function filersOf(db) {
|
|
|
198
198
|
return found;
|
|
199
199
|
};
|
|
200
200
|
}
|
|
201
|
-
/** Every piece of spend in [from, to): runs (by when they started),
|
|
201
|
+
/** Every piece of spend in [from, to): runs (by when they started), subagent turns and chat turns. `modeOf` says how
|
|
202
202
|
* a provider bills when a record doesn't (older rows): its current login or key. */
|
|
203
203
|
export function spendItems(db, from, to, modeOf = provider => billingOf(provider, db)) {
|
|
204
|
-
const
|
|
204
|
+
const subagentOf = subagentFilers(db);
|
|
205
205
|
const filerOf = filersOf(db);
|
|
206
206
|
const text = (value) => value == null ? null : String(value);
|
|
207
207
|
const count = (value) => value == null ? null : Number(value);
|
|
@@ -224,19 +224,19 @@ export function spendItems(db, from, to, modeOf = provider => billingOf(provider
|
|
|
224
224
|
source: settled ? String(row["source"]) : priced.source,
|
|
225
225
|
project: text(row["repo"]),
|
|
226
226
|
person: kind === "person" || kind === "coordinator" ? filer.filedBy : null,
|
|
227
|
-
|
|
227
|
+
subagent: subagentOf(filer.repo, filer.filedBy, kind), provider, model: text(row["model"]),
|
|
228
228
|
tokensIn: count(row["tokens_in"]), tokensOut: count(row["tokens_out"]),
|
|
229
229
|
taskId: String(row["task"]), runId: Number(row["id"]), authMode: billing,
|
|
230
230
|
};
|
|
231
231
|
});
|
|
232
|
-
// A
|
|
232
|
+
// A subagent's own turns get no key from Toolroll (subagents.ts claudeTurnRunner): they bill as this computer's
|
|
233
233
|
// Claude sign-in was seen billing when the turn ran (a plan, or a Console login, key helper or gateway).
|
|
234
|
-
for (const row of db.prepare(`SELECT t.at, t.
|
|
234
|
+
for (const row of db.prepare(`SELECT t.at, t.subagent, t.model, t.cost_usd, t.tokens_in, t.tokens_out, t.billing, m.repo FROM subagent_turn t JOIN subagent m ON m.id = t.subagent
|
|
235
235
|
WHERE t.at >= ? AND t.at < ? ORDER BY t.id`).all(from, to)) {
|
|
236
236
|
const billing = row["billing"] === "api-key" ? "api-key" : "subscription";
|
|
237
237
|
const priced = priceWork(db, { provider: "claude", model: text(row["model"]), costUsd: count(row["cost_usd"]), tokensIn: count(row["tokens_in"]), tokensOut: count(row["tokens_out"]), billing });
|
|
238
|
-
items.push({ at: String(row["at"]), kind: "
|
|
239
|
-
|
|
238
|
+
items.push({ at: String(row["at"]), kind: "subagent", microusd: priced.microusd, source: priced.source, project: String(row["repo"]), person: null,
|
|
239
|
+
subagent: Number(row["subagent"]), provider: "claude", model: text(row["model"]),
|
|
240
240
|
tokensIn: count(row["tokens_in"]), tokensOut: count(row["tokens_out"]), taskId: null, runId: null, authMode: billing });
|
|
241
241
|
}
|
|
242
242
|
// A chat turn that mirrors a project chat's turn is that turn: counted once, as the project chat's. A task's chat
|
|
@@ -246,19 +246,19 @@ export function spendItems(db, from, to, modeOf = provider => billingOf(provider
|
|
|
246
246
|
UNION ALL SELECT t.created_at, t.approver, COALESCE((SELECT c.provider FROM chat_turn c WHERE c.mate_turn = t.id ORDER BY c.id LIMIT 1), 'lead'),
|
|
247
247
|
(SELECT c.model FROM chat_turn c WHERE c.mate_turn = t.id ORDER BY c.id LIMIT 1), t.settled_microusd, t.tokens_in, t.tokens_out,
|
|
248
248
|
CASE th.scope_kind WHEN 'project' THEN th.scope_key WHEN 'task' THEN (SELECT r.repo FROM task_ref r WHERE r.external_id = th.scope_key ORDER BY r.id DESC LIMIT 1) END
|
|
249
|
-
FROM mate_turn t LEFT JOIN
|
|
249
|
+
FROM mate_turn t LEFT JOIN lead_thread th ON th.id = t.thread WHERE t.created_at >= ? AND t.created_at < ?`).all(from, to, from, to)) {
|
|
250
250
|
const provider = String(row["provider"]);
|
|
251
251
|
const subscription = provider.endsWith("-subscription") && modeOf(provider) === "subscription";
|
|
252
252
|
const settled = count(row["settled_microusd"]);
|
|
253
253
|
items.push({ at: String(row["at"]), kind: "chat", microusd: subscription ? 0 : settled, source: subscription ? "subscription" : settled === null ? "unpriced" : "reported",
|
|
254
|
-
project: text(row["project"]), person: text(row["approver"]),
|
|
254
|
+
project: text(row["project"]), person: text(row["approver"]), subagent: null, provider, model: text(row["model"]),
|
|
255
255
|
tokensIn: count(row["tokens_in"]), tokensOut: count(row["tokens_out"]), taskId: null, runId: null, authMode: subscription ? "subscription" : "api-key" });
|
|
256
256
|
}
|
|
257
|
-
// A flow's Claude drafts: like a
|
|
257
|
+
// A flow's Claude drafts: like a subagent's turns, billed as the sign-in was.
|
|
258
258
|
for (const row of db.prepare("SELECT at, kind, repo, provider, model, cost_usd, billing FROM side_spend WHERE at >= ? AND at < ? ORDER BY id").all(from, to)) {
|
|
259
259
|
const billing = row["billing"] === "api-key" ? "api-key" : "subscription";
|
|
260
260
|
const priced = priceWork(db, { provider: String(row["provider"]), model: text(row["model"]), costUsd: count(row["cost_usd"]), tokensIn: null, tokensOut: null, billing });
|
|
261
|
-
items.push({ at: String(row["at"]), kind: "draft", microusd: priced.microusd, source: priced.source, project: text(row["repo"]), person: null,
|
|
261
|
+
items.push({ at: String(row["at"]), kind: "draft", microusd: priced.microusd, source: priced.source, project: text(row["repo"]), person: null, subagent: null,
|
|
262
262
|
provider: String(row["provider"]), model: text(row["model"]), tokensIn: null, tokensOut: null, taskId: null, runId: null, authMode: billing });
|
|
263
263
|
}
|
|
264
264
|
// A flow's Sort zone asks Jev on OpenRouter (the key's credit): what it cost is kept with its answer.
|
|
@@ -267,7 +267,7 @@ export function spendItems(db, from, to, modeOf = provider => billingOf(provider
|
|
|
267
267
|
WHERE fs.kind = 'sort' AND fs.decision_json IS NOT NULL AND fs.started_at >= ? AND fs.started_at < ? ORDER BY fs.started_at`).all(from, to)) {
|
|
268
268
|
const cost = typeof row["cost"] === "number" && Number.isFinite(row["cost"]) && row["cost"] >= 0 ? row["cost"] : null;
|
|
269
269
|
items.push({ at: String(row["at"]), kind: "sort", microusd: cost === null ? null : Math.round(cost * 1_000_000), source: cost === null ? "unpriced" : "reported",
|
|
270
|
-
project: text(row["repo"]), person: null,
|
|
270
|
+
project: text(row["repo"]), person: null, subagent: null, provider: "openrouter", model: text(row["model"]), tokensIn: null, tokensOut: null, taskId: null, runId: null, authMode: "api-key" });
|
|
271
271
|
}
|
|
272
272
|
return items;
|
|
273
273
|
}
|
|
@@ -279,14 +279,14 @@ export function countsToward(item, budget) {
|
|
|
279
279
|
return item.project === budget.key;
|
|
280
280
|
if (budget.scope === "person")
|
|
281
281
|
return item.person === budget.key;
|
|
282
|
-
return item.
|
|
282
|
+
return item.subagent !== null && String(item.subagent) === budget.key;
|
|
283
283
|
}
|
|
284
284
|
const PROVIDER_WORDS = { claude: "Claude", codex: "Codex", gemini: "Gemini", openrouter: "OpenRouter" };
|
|
285
285
|
/** Why work waits on a budget, in words. */
|
|
286
|
-
export function budgetHoldWords(hold, month,
|
|
286
|
+
export function budgetHoldWords(hold, month, subagentName) {
|
|
287
287
|
if (hold.over === null)
|
|
288
288
|
return "";
|
|
289
|
-
const label = budgetLabel(hold.over,
|
|
289
|
+
const label = budgetLabel(hold.over, subagentName);
|
|
290
290
|
return hold.why === "unpriced"
|
|
291
291
|
? `${label} budget can't price ${PROVIDER_WORDS[hold.unpricedProvider ?? ""] ?? hold.unpricedProvider} work on a key yet. Open Settings → Models to load prices`
|
|
292
292
|
: `${label} budget is used up for ${month}`;
|
|
@@ -300,14 +300,14 @@ export function budgetStates(budgets, items) {
|
|
|
300
300
|
percent: Math.floor((spent / budget.limitMicrousd) * 100) };
|
|
301
301
|
});
|
|
302
302
|
}
|
|
303
|
-
/** A budget in words: "The shop project's", "alex's", "Maya's (
|
|
304
|
-
export function budgetLabel(budget,
|
|
303
|
+
/** A budget in words: "The shop project's", "alex's", "Maya's (subagent)", "The whole installation's". */
|
|
304
|
+
export function budgetLabel(budget, subagentName) {
|
|
305
305
|
if (budget.scope === "installation")
|
|
306
306
|
return "The whole installation's";
|
|
307
307
|
if (budget.scope === "project")
|
|
308
308
|
return `The ${budget.key.split("/").filter(Boolean).pop() ?? budget.key} project's`;
|
|
309
309
|
if (budget.scope === "person")
|
|
310
310
|
return `${budget.key}'s`;
|
|
311
|
-
return `${
|
|
311
|
+
return `${subagentName ?? `Subagent ${budget.key}`}'s`;
|
|
312
312
|
}
|
|
313
313
|
export const usd = (microusd) => microusd === null ? "unpriced" : `$${(microusd / 1_000_000).toFixed(microusd !== 0 && Math.abs(microusd) < 10_000_000 ? 2 : 0)}`;
|
package/dist/sso-ui.d.ts
CHANGED
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
* operator's page; changes take their password.
|
|
5
5
|
*/
|
|
6
6
|
import type { OidcSettings } from "./oidc.js";
|
|
7
|
+
import { type Html } from "./html.js";
|
|
7
8
|
export declare const SSO_CSS: string;
|
|
8
9
|
export type SsoSettingsView = {
|
|
9
10
|
settings: OidcSettings | null;
|
|
@@ -16,7 +17,7 @@ export type SsoSettingsView = {
|
|
|
16
17
|
/** Whether the person looking is linked to the provider already. */
|
|
17
18
|
linked: boolean;
|
|
18
19
|
};
|
|
19
|
-
export declare function ssoSettingsHtml(view: SsoSettingsView,
|
|
20
|
+
export declare function ssoSettingsHtml(view: SsoSettingsView, notice: {
|
|
20
21
|
said?: string | null;
|
|
21
22
|
problem?: string | null;
|
|
22
|
-
}):
|
|
23
|
+
}): Html;
|
package/dist/sso-ui.js
CHANGED
|
@@ -1,39 +1,38 @@
|
|
|
1
|
-
|
|
1
|
+
import { html, postForm } from "./html.js";
|
|
2
2
|
export const SSO_CSS = `.sign-in{max-width:780px;min-width:0}.sign-in form{display:grid;gap:12px;margin:0}.sign-in label{display:grid;gap:6px}.sign-in input,.sign-in select{box-sizing:border-box;width:100%;max-width:100%;min-width:0}` +
|
|
3
3
|
`.sign-in .card{padding:16px 18px;margin:12px 0}.sign-in .card h2{font-size:1.05rem;margin:0 0 6px}.sign-in .redirect code{user-select:all;overflow-wrap:anywhere}.sign-in button{justify-self:start;min-height:44px}` +
|
|
4
4
|
`.sign-in .rules{display:grid;gap:8px}.sign-in .rule{display:grid;grid-template-columns:minmax(0,2fr) minmax(0,1fr) minmax(0,2fr);gap:8px;align-items:start}.sign-in .rule select[multiple]{min-height:5.5rem}` +
|
|
5
5
|
`.sign-in .row{display:flex;gap:12px;flex-wrap:wrap;align-items:center}.sign-in fieldset{border:0;padding:0;margin:0;display:grid;gap:6px}.sign-in fieldset label{display:flex;gap:8px;align-items:center}.sign-in fieldset input{width:auto}` +
|
|
6
6
|
`@media(max-width:600px){.sign-in .rule{grid-template-columns:1fr}.sign-in input,.sign-in select{font-size:16px}}`;
|
|
7
|
-
const password =
|
|
7
|
+
const password = html `<label>Your Toolroll password<input type="password" name="password" autocomplete="current-password" required></label>`;
|
|
8
8
|
function ruleRow(index, rule, projects) {
|
|
9
9
|
const chosen = rule === null ? [] : rule.projects === "all" ? ["all"] : rule.projects;
|
|
10
|
-
return `<div class="rule"><label>Group<input type="text" name="group-${index}" value="${
|
|
11
|
-
|
|
12
|
-
|
|
10
|
+
return html `<div class="rule"><label>Group<input type="text" name="group-${index}" value="${rule?.group ?? ""}" placeholder="${index === 0 ? "for example: engineering" : ""}" autocomplete="off"></label>\
|
|
11
|
+
<label>Role<select name="role-${index}"><option value="operator"${rule?.role === "operator" ? " selected" : ""}>Operator</option><option value="viewer"${rule?.role === "viewer" ? " selected" : ""}>Viewer</option></select></label>\
|
|
12
|
+
<label>Projects<select name="projects-${index}" multiple><option value="all"${chosen.includes("all") ? " selected" : ""}>All projects</option>${projects.map(one => html `<option value="${one.path}"${chosen.includes(one.path) ? " selected" : ""}>${one.name}</option>`)}</select></label></div>`;
|
|
13
13
|
}
|
|
14
|
-
export function ssoSettingsHtml(view,
|
|
14
|
+
export function ssoSettingsHtml(view, notice) {
|
|
15
15
|
const s = view.settings;
|
|
16
|
-
const
|
|
17
|
-
const
|
|
18
|
-
const note = notice.problem ? `<p class="problem" role="alert">${e(notice.problem)}</p>` : notice.said ? `<p role="status">${e(notice.said)}</p>` : "";
|
|
16
|
+
const rows = [...(s?.rules ?? []), null, null].slice(0, Math.max(3, (s?.rules.length ?? 0) + 1)).map((rule, index) => ruleRow(index, rule, view.projects));
|
|
17
|
+
const note = notice.problem ? html `<p class="problem" role="alert">${notice.problem}</p>` : notice.said ? html `<p role="status">${notice.said}</p>` : "";
|
|
19
18
|
const redirect = view.redirect === null
|
|
20
|
-
? `<p class="meta">Open this page from the address people use (your https address) to see the redirect address to register.</p>`
|
|
21
|
-
: `<p class="redirect">Register this redirect address with the provider: <code>${
|
|
22
|
-
const form =
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
const test = s === null ? "" :
|
|
19
|
+
? html `<p class="meta">Open this page from the address people use (your https address) to see the redirect address to register.</p>`
|
|
20
|
+
: html `<p class="redirect">Register this redirect address with the provider: <code>${view.redirect}</code></p>`;
|
|
21
|
+
const form = postForm("/settings/sign-in", html `\
|
|
22
|
+
<label>Provider address (issuer)<input type="url" name="issuer" value="${s?.issuer ?? ""}" placeholder="https://acme.okta.com/oauth2/default" required inputmode="url" autocomplete="off"></label>\
|
|
23
|
+
<label>Client ID<input type="text" name="client-id" value="${s?.clientId ?? ""}" required autocomplete="off"></label>\
|
|
24
|
+
<label>Client secret<input type="password" name="client-secret" autocomplete="off" placeholder="${s?.clientSecret ? "Saved. Leave blank to keep it" : "Leave blank for a public client"}"></label>\
|
|
25
|
+
<details><summary>More</summary><label>Button says “Sign in with …”<input type="text" name="label" value="${s?.label ?? ""}" placeholder="Okta"></label>\
|
|
26
|
+
<label>Scopes<input type="text" name="scopes" value="${s?.scopes ?? "openid email profile"}"></label><label>Groups claim<input type="text" name="groups-claim" value="${s?.groupsClaim ?? "groups"}"></label></details>\
|
|
27
|
+
<h2>Who may sign in</h2><p class="meta">A person's first matching group sets their role and projects, again at every sign-in. No match, no entry. Use * for everyone.</p><div class="rules">${rows}</div>\
|
|
28
|
+
<fieldset><legend>Passwords</legend><label><input type="radio" name="passwords" value="everyone"${s?.passwords !== "operators" ? " checked" : ""}> Everyone may still use a password</label>\
|
|
29
|
+
<label><input type="radio" name="passwords" value="operators"${s?.passwords === "operators" ? " checked" : ""}> Only instance operators (a way in if the provider is down)</label></fieldset>\
|
|
30
|
+
${password}<button>${s === null ? "Turn on sign-in with the provider" : "Save"}</button>`, { hidden: { action: "save" } });
|
|
31
|
+
const test = s === null ? "" : postForm("/settings/sign-in", html `<button class="secondary">Check the provider</button>`, { attrs: { class: "row" }, hidden: { action: "test" } });
|
|
33
32
|
const link = s === null ? "" : view.linked
|
|
34
|
-
? `<p class="meta">You sign in with ${
|
|
35
|
-
: `<section class="card"><h2>Your account</h2><p>Sign in with ${
|
|
36
|
-
const off = s === null ? "" : `<details class="card"><summary>Turn off</summary
|
|
37
|
-
return `<section class="sign-in">${note}<p>People sign in with your organisation's identity provider (Okta, Microsoft Entra, Google, anything that speaks OpenID Connect), and its groups decide what they may do.</p
|
|
38
|
-
|
|
33
|
+
? html `<p class="meta">You sign in with ${s.label}.</p>`
|
|
34
|
+
: html `<section class="card"><h2>Your account</h2><p>Sign in with ${s.label} as this account from now on.</p><p><a class="button-link" href="/login/sso?link=1">Link to ${s.label}</a></p></section>`;
|
|
35
|
+
const off = s === null ? "" : html `<details class="card"><summary>Turn off</summary>${postForm("/settings/sign-in", html `<p>People who signed in with ${s.label} can't sign in until it's on again, unless they have a password.</p>${password}<button class="danger">Turn off sign-in with ${s.label}</button>`, { hidden: { action: "remove" } })}</details>`;
|
|
36
|
+
return html `<section class="sign-in">${note}<p>People sign in with your organisation's identity provider (Okta, Microsoft Entra, Google, anything that speaks OpenID Connect), and its groups decide what they may do.</p>\
|
|
37
|
+
${redirect}<section class="card">${form}</section>${test}${link}${off}</section>`;
|
|
39
38
|
}
|
package/dist/storage-ui.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { type Html } from "./html.js";
|
|
1
2
|
import { type CheckoutPlan } from "./checkout-cleanup.js";
|
|
2
3
|
import { type SweepRecord } from "./storage-sweep.js";
|
|
3
4
|
export declare const STORAGE_CSS: string;
|
|
@@ -12,4 +13,4 @@ export type StorageView = {
|
|
|
12
13
|
export declare function storageHtml(view: StorageView, notice: {
|
|
13
14
|
said?: string | null;
|
|
14
15
|
problem?: string | null;
|
|
15
|
-
}):
|
|
16
|
+
}): Html;
|
package/dist/storage-ui.js
CHANGED
|
@@ -6,10 +6,10 @@
|
|
|
6
6
|
*/
|
|
7
7
|
import { homedir } from "node:os";
|
|
8
8
|
import { basename } from "node:path";
|
|
9
|
+
import { html, postForm } from "./html.js";
|
|
9
10
|
import { previewDigest, whyWords } from "./checkout-cleanup.js";
|
|
10
11
|
import { CLEANUP_CHOICES, bytesWords } from "./storage.js";
|
|
11
12
|
import { sweepDetails, sweepWords } from "./storage-sweep.js";
|
|
12
|
-
const e = (value) => String(value ?? "").replace(/[&<>"']/g, c => ({ "&": "&", "<": "<", ">": ">", '"': """, "'": "'" })[c]);
|
|
13
13
|
const plural = (count, one, many = `${one}s`) => `${count} ${count === 1 ? one : many}`;
|
|
14
14
|
export const STORAGE_CSS = `.storage{max-width:720px;min-width:0;overflow-wrap:anywhere}.storage h2{font-size:1.0625rem;margin:24px 0 4px}` +
|
|
15
15
|
`.storage .storage-state{display:grid;gap:12px}.storage .storage-state h2{margin:0}.storage .storage-state p{margin:0}` +
|
|
@@ -45,49 +45,30 @@ function whenWords(at) {
|
|
|
45
45
|
function sweepHtml(sweep) {
|
|
46
46
|
const last = sweep.last;
|
|
47
47
|
if (last === null)
|
|
48
|
-
return `<p class="meta" data-last-sweep="">${sweep.off ? "Automatic sweep is off." :
|
|
49
|
-
const who = last.source === "manual" ? `Cleaned up by ${
|
|
48
|
+
return html `<p class="meta" data-last-sweep="">${sweep.off ? "Automatic sweep is off." : html `Toolroll sweeps leftovers once a day. It hasn't run yet.`}</p>`;
|
|
49
|
+
const who = last.source === "manual" ? `Cleaned up by ${last.actor}` : "Swept";
|
|
50
50
|
const details = sweepDetails(last.parts);
|
|
51
51
|
const problem = last.parts.some(one => one.failed > 0 || one.problem !== undefined);
|
|
52
|
-
return `<div class="sweep" data-last-sweep="${
|
|
53
|
-
(details.length === 0 ? "" : `<details class="sweep-details"><summary>Details</summary><ul>${details.map(line => `<li>${e(homeAsTilde(line))}</li>`).join("")}</ul></details>`) + `</div>`;
|
|
52
|
+
return html `<div class="sweep" data-last-sweep="${last.at}"><p${problem ? html ` class="problem"` : ""}>${who} <time datetime="${last.at}">${whenWords(last.at)}</time>. ${sweepWords(last.parts)}</p>${details.length === 0 ? "" : html `<details class="sweep-details"><summary>Details</summary><ul>${details.map(line => html `<li>${homeAsTilde(line)}</li>`)}</ul></details>`}</div>`;
|
|
54
53
|
}
|
|
55
|
-
function item(one
|
|
54
|
+
function item(one) {
|
|
56
55
|
const discard = one.why === "has changes"
|
|
57
|
-
? `<details class="discard"><summary>Discard</summary
|
|
58
|
-
`<p class="meta">Throws away this checkout and its uncommitted changes. Its branch stays.</p>` +
|
|
59
|
-
`<label>Your Toolroll password<input type="password" name="password" autocomplete="current-password" required></label><button type="submit" class="danger">Discard changes</button></form></details>`
|
|
56
|
+
? html `<details class="discard"><summary>Discard</summary>${postForm("/settings/storage/discard", html `<p class="meta">Throws away this checkout and its uncommitted changes. Its branch stays.</p><label>Your Toolroll password<input type="password" name="password" autocomplete="current-password" required></label><button type="submit" class="danger">Discard changes</button>`, { hidden: { path: one.path } })}</details>`
|
|
60
57
|
: "";
|
|
61
58
|
// The task it was for, then where it is (the home folder as ~), then why it stays.
|
|
62
59
|
const home = homedir();
|
|
63
60
|
const where = home !== "" && one.path.startsWith(`${home}/`) ? `~${one.path.slice(home.length)}` : one.path;
|
|
64
|
-
return `<li data-checkout="${
|
|
65
|
-
`<span class="path">${e(where)}</span>` +
|
|
66
|
-
(one.why === null ? "" : `<span class="why">${e(whyWords(one)[0].toUpperCase() + whyWords(one).slice(1))}</span>`) + discard + `</li>`;
|
|
61
|
+
return html `<li data-checkout="${one.path}"><span class="name">${one.taskId ?? basename(one.path)}</span><span class="size">${bytesWords(one.bytes)}</span><span class="path">${where}</span>${one.why === null ? "" : html `<span class="why">${whyWords(one)[0].toUpperCase() + whyWords(one).slice(1)}</span>`}${discard}</li>`;
|
|
67
62
|
}
|
|
68
63
|
export function storageHtml(view, notice) {
|
|
69
|
-
const { plan
|
|
70
|
-
const note = notice.problem ? `<p class="problem" role="alert">${
|
|
71
|
-
const hidden = `<input type="hidden" name="csrf" value="${e(csrf)}">`;
|
|
64
|
+
const { plan } = view;
|
|
65
|
+
const note = notice.problem ? html `<p class="problem" role="alert">${notice.problem}</p>` : notice.said ? html `<p role="status">${notice.said}</p>` : "";
|
|
72
66
|
const facts = [plural(plan.count, "checkout"), `${plan.waitingReview} waiting for review`, `${plan.withChanges} kept for their changes`].join(" · ");
|
|
73
67
|
const frees = plan.go.length === 0 ? "Nothing to clean up now." : `Cleaning up now frees about ${bytesWords(plan.freeBytes)}.`;
|
|
74
68
|
const remove = plan.go.length === 0 ? "" :
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
const
|
|
79
|
-
|
|
80
|
-
`<h3>Removes ${plural(plan.go.length, "checkout")}, about ${e(bytesWords(plan.freeBytes))}</h3><p class="meta">Their branches stay, so nothing committed is lost.</p>` +
|
|
81
|
-
`<ul class="checkouts" data-goes>${plan.go.map(one => item(one, csrf)).join("")}</ul>`) +
|
|
82
|
-
(plan.stay.length === 0 ? "" : `<h3>Stays (${plan.stay.length})</h3><ul class="checkouts" data-stays>${plan.stay.map(one => item(one, csrf)).join("")}</ul>`) +
|
|
83
|
-
remove + `</div></details>`;
|
|
84
|
-
const options = CLEANUP_CHOICES.map(one => `<option value="${one.value}"${one.value === plan.cleanup ? " selected" : ""}>${e(one.label)}</option>`).join("");
|
|
85
|
-
return `<section class="storage">${note}` +
|
|
86
|
-
`<div class="card storage-state"><h2 data-checkout-bytes="${plan.totalBytes}">Checkouts use ${e(bytesWords(plan.totalBytes))}</h2><p class="meta">${e(facts)}</p>` +
|
|
87
|
-
`<p data-clean-bytes="${plan.freeBytes}">${e(frees)}</p>${view.sweep === undefined ? "" : sweepHtml(view.sweep)}${preview}</div>` +
|
|
88
|
-
`<h2>Automatic cleanup</h2>` +
|
|
89
|
-
`<form method="post" action="/settings/storage" class="setting">${hidden}` +
|
|
90
|
-
`<label>Remove a finished task's clean checkout<select name="cleanup">${options}</select></label>` +
|
|
91
|
-
`<p class="meta">Its branch always stays. Checkouts with changes, commits on no branch, in use, or waiting for review are kept.</p>` +
|
|
92
|
-
`<label>Your Toolroll password<input type="password" name="password" autocomplete="current-password" required></label><button type="submit">Save</button></form></section>`;
|
|
69
|
+
postForm("/settings/storage/clean", html `<label>Your Toolroll password<input type="password" name="password" autocomplete="current-password" required></label><button type="submit">Remove ${plural(plan.go.length, "checkout")}</button>`, { attrs: { class: "remove" }, hidden: { preview: previewDigest(plan) } });
|
|
70
|
+
const preview = html `<details class="clean-up" data-clean-up${notice.problem?.startsWith("Enter your Toolroll password to clean") ? html ` open` : ""}><summary>Clean up</summary><div class="preview">${plan.go.length === 0 ? html `<p>Nothing to remove now.</p>` :
|
|
71
|
+
html `<h3>Removes ${plural(plan.go.length, "checkout")}, about ${bytesWords(plan.freeBytes)}</h3><p class="meta">Their branches stay, so nothing committed is lost.</p><ul class="checkouts" data-goes>${plan.go.map(one => item(one))}</ul>`}${plan.stay.length === 0 ? "" : html `<h3>Stays (${plan.stay.length})</h3><ul class="checkouts" data-stays>${plan.stay.map(one => item(one))}</ul>`}${remove}</div></details>`;
|
|
72
|
+
const options = CLEANUP_CHOICES.map(one => html `<option value="${one.value}"${one.value === plan.cleanup ? html ` selected` : ""}>${one.label}</option>`);
|
|
73
|
+
return html `<section class="storage">${note}<div class="card storage-state"><h2 data-checkout-bytes="${plan.totalBytes}">Checkouts use ${bytesWords(plan.totalBytes)}</h2><p class="meta">${facts}</p><p data-clean-bytes="${plan.freeBytes}">${frees}</p>${view.sweep === undefined ? "" : sweepHtml(view.sweep)}${preview}</div><h2>Automatic cleanup</h2>${postForm("/settings/storage", html `<label>Remove a finished task's clean checkout<select name="cleanup">${options}</select></label><p class="meta">Its branch always stays. Checkouts with changes, commits on no branch, in use, or waiting for review are kept.</p><label>Your Toolroll password<input type="password" name="password" autocomplete="current-password" required></label><button type="submit">Save</button>`, { attrs: { class: "setting" } })}</section>`;
|
|
93
74
|
}
|