slotlock 0.8.0 → 0.10.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.
Files changed (86) hide show
  1. package/CHANGELOG.md +542 -0
  2. package/NOTICE +2 -0
  3. package/README.md +4 -2
  4. package/agent-server.d.ts +25 -5
  5. package/agent-server.js +152 -7
  6. package/agent-store-backend.d.ts +6 -1
  7. package/agent-store-backend.js +1068 -68
  8. package/application-api.d.ts +28 -1
  9. package/application-api.js +789 -21
  10. package/approvals.d.ts +2 -0
  11. package/approvals.js +12 -5
  12. package/assistant.d.ts +34 -8
  13. package/assistant.js +81 -26
  14. package/availability-store.d.ts +193 -0
  15. package/availability-store.js +566 -0
  16. package/booking-rules.d.ts +103 -5
  17. package/booking-rules.js +263 -15
  18. package/config.d.ts +50 -0
  19. package/config.js +107 -0
  20. package/dashboard-assistant-ui.d.ts +1 -1
  21. package/dashboard-assistant-ui.js +109 -8
  22. package/dashboard-scope.d.ts +7 -0
  23. package/dashboard-scope.js +21 -0
  24. package/dashboard.d.ts +152 -8
  25. package/dashboard.js +3868 -399
  26. package/ddl-identity.d.ts +25 -0
  27. package/ddl-identity.js +881 -0
  28. package/ddl-scim.d.ts +5 -0
  29. package/ddl-scim.js +740 -0
  30. package/ddl-sso.d.ts +15 -0
  31. package/ddl-sso.js +1033 -0
  32. package/ddl.d.ts +44 -9
  33. package/ddl.js +2872 -47
  34. package/domains.d.ts +117 -0
  35. package/domains.js +340 -0
  36. package/event-feed.d.ts +2 -2
  37. package/event-feed.js +24 -34
  38. package/holiday-import.d.ts +38 -0
  39. package/holiday-import.js +56 -0
  40. package/holidays.d.ts +38 -0
  41. package/holidays.js +209 -0
  42. package/hours.d.ts +125 -0
  43. package/hours.js +224 -0
  44. package/index.d.ts +24 -5
  45. package/index.js +17 -3
  46. package/interop.d.ts +25 -7
  47. package/interop.js +145 -17
  48. package/invitation-email.d.ts +26 -0
  49. package/invitation-email.js +73 -0
  50. package/lifecycle.d.ts +31 -0
  51. package/lifecycle.js +115 -0
  52. package/maintenance.d.ts +26 -2
  53. package/maintenance.js +70 -2
  54. package/metrics.js +3 -0
  55. package/oidc.d.ts +135 -0
  56. package/oidc.js +452 -0
  57. package/openapi.json +20172 -9850
  58. package/package.json +2 -1
  59. package/ranges.d.ts +28 -0
  60. package/ranges.js +135 -0
  61. package/resource-search.d.ts +19 -0
  62. package/resource-search.js +153 -0
  63. package/rest-api.js +7 -0
  64. package/rules.d.ts +2 -0
  65. package/rules.js +2 -0
  66. package/scim-server.d.ts +19 -0
  67. package/scim-server.js +243 -0
  68. package/scim.d.ts +180 -0
  69. package/scim.js +416 -0
  70. package/self-host.d.ts +19 -6
  71. package/self-host.js +483 -28
  72. package/slotlock.manifest.json +1 -1
  73. package/store.d.ts +637 -23
  74. package/store.js +2529 -147
  75. package/types.d.ts +230 -0
  76. package/types.js +68 -1
  77. package/workspace-domains.d.ts +146 -0
  78. package/workspace-domains.js +249 -0
  79. package/workspace-roles.d.ts +47 -0
  80. package/workspace-roles.js +304 -0
  81. package/workspace-scim.d.ts +89 -0
  82. package/workspace-scim.js +157 -0
  83. package/workspace-sso.d.ts +131 -0
  84. package/workspace-sso.js +180 -0
  85. package/workspaces.d.ts +240 -0
  86. package/workspaces.js +433 -0
package/self-host.d.ts CHANGED
@@ -10,7 +10,7 @@ export { SlotlockConfigError, type SlotlockEnv, type SlotlockServeDashboardConfi
10
10
  * (site/docs-src/self-host.md) document these. Not STRIPE_SECRET_KEY, which only the owner's
11
11
  * `partner close` reads, to bill partners.
12
12
  */
13
- export declare const SLOTLOCK_ENVIRONMENT_VARIABLES: readonly ["DATABASE_URL", "SLOTLOCK_MIGRATE_DATABASE_URL", "SLOTLOCK_AUTH_TOKEN", "SLOTLOCK_CONFIRMATION_SECRET", "SLOTLOCK_CONFIRM_WRITES", "SLOTLOCK_METRICS_TOKEN", "SLOTLOCK_PUBLIC_URL", "SLOTLOCK_TENANT", "SLOTLOCK_AVAILABILITY", "SLOTLOCK_REST_API", "SLOTLOCK_RETENTION_DAYS", "SLOTLOCK_WEBHOOKS", "SLOTLOCK_SHARED_LIMITS", "SLOTLOCK_USAGE", "SLOTLOCK_QUOTAS", "SLOTLOCK_OVERAGE", "SLOTLOCK_LIMITS", "SLOTLOCK_TRUSTED_PROXY_HEADER", "SLOTLOCK_TRUSTED_PROXY_SECRET", "SLOTLOCK_KNOWN_CLIENTS", "OTEL_EXPORTER_OTLP_ENDPOINT", "OTEL_EXPORTER_OTLP_TRACES_ENDPOINT", "OTEL_EXPORTER_OTLP_METRICS_ENDPOINT", "OTEL_EXPORTER_OTLP_HEADERS", "OTEL_SERVICE_NAME", "SLOTLOCK_GITHUB_CLIENT_ID", "SLOTLOCK_GITHUB_CLIENT_SECRET", "SLOTLOCK_GOOGLE_CLIENT_ID", "SLOTLOCK_GOOGLE_CLIENT_SECRET", "SLOTLOCK_SESSION_SECRET", "SLOTLOCK_ASSISTANT_SECRET", "SLOTLOCK_ASSISTANT_PREVIOUS_SECRETS", "SLOTLOCK_ASSISTANT_PRIVATE_URLS", "SLOTLOCK_DASHBOARD_USERS", "SLOTLOCK_DASHBOARD_ORGS", "SLOTLOCK_DASHBOARD_NAME", "SLOTLOCK_DASHBOARD_TERMS_URL", "SLOTLOCK_DASHBOARD_PRIVACY_URL", "HOST", "PORT"];
13
+ export declare const SLOTLOCK_ENVIRONMENT_VARIABLES: readonly ["DATABASE_URL", "SLOTLOCK_MIGRATE_DATABASE_URL", "SLOTLOCK_AUTH_TOKEN", "SLOTLOCK_CONFIRMATION_SECRET", "SLOTLOCK_CONFIRM_WRITES", "SLOTLOCK_METRICS_TOKEN", "SLOTLOCK_PUBLIC_URL", "SLOTLOCK_TENANT", "SLOTLOCK_AVAILABILITY", "SLOTLOCK_REST_API", "SLOTLOCK_RETENTION_DAYS", "SLOTLOCK_WEBHOOKS", "SLOTLOCK_SHARED_LIMITS", "SLOTLOCK_USAGE", "SLOTLOCK_QUOTAS", "SLOTLOCK_OVERAGE", "SLOTLOCK_LIMITS", "SLOTLOCK_TRUSTED_PROXY_HEADER", "SLOTLOCK_TRUSTED_PROXY_SECRET", "SLOTLOCK_KNOWN_CLIENTS", "OTEL_EXPORTER_OTLP_ENDPOINT", "OTEL_EXPORTER_OTLP_TRACES_ENDPOINT", "OTEL_EXPORTER_OTLP_METRICS_ENDPOINT", "OTEL_EXPORTER_OTLP_HEADERS", "OTEL_SERVICE_NAME", "SLOTLOCK_GITHUB_CLIENT_ID", "SLOTLOCK_GITHUB_CLIENT_SECRET", "SLOTLOCK_GOOGLE_CLIENT_ID", "SLOTLOCK_GOOGLE_CLIENT_SECRET", "SLOTLOCK_SESSION_SECRET", "SLOTLOCK_ASSISTANT_SECRET", "SLOTLOCK_ASSISTANT_PREVIOUS_SECRETS", "SLOTLOCK_ASSISTANT_PRIVATE_URLS", "SLOTLOCK_DASHBOARD_USERS", "SLOTLOCK_DASHBOARD_ORGS", "SLOTLOCK_DASHBOARD_TEAM_WORKSPACES", "SLOTLOCK_DASHBOARD_NAME", "SLOTLOCK_DASHBOARD_TERMS_URL", "SLOTLOCK_DASHBOARD_PRIVACY_URL", "SLOTLOCK_INVITATION_EMAIL", "SLOTLOCK_INVITATION_EMAIL_KEY", "SLOTLOCK_INVITATION_EMAIL_FROM", "SLOTLOCK_VERIFIED_DOMAINS", "SLOTLOCK_DNS_RESOLVER", "SLOTLOCK_TEAM_SSO", "SLOTLOCK_SSO_SECRET", "SLOTLOCK_TEAM_SCIM", "HOST", "PORT"];
14
14
  export interface SlotlockDatabaseConfig {
15
15
  /** The role `serve` and `resource` connect as. */
16
16
  databaseUrl: string;
@@ -60,6 +60,18 @@ export interface SlotlockServeConfig extends SlotlockDatabaseConfig {
60
60
  * keys may act for, whether it serves the dashboard or another server (a Worker) does.
61
61
  */
62
62
  allowedUsers?: '*' | string[];
63
+ /**
64
+ * Team workspaces (SLOTLOCK_DASHBOARD_TEAM_WORKSPACES=on), whether this server serves the dashboard
65
+ * or a Worker does: their `team:<id>` keys act only while the workspace exists (workspace_active).
66
+ */
67
+ teamWorkspaces?: boolean;
68
+ /**
69
+ * Verified domains (SLOTLOCK_VERIFIED_DOMAINS=on), whether this server serves the dashboard or a
70
+ * Worker does: their daily check runs in this server's maintenance.
71
+ */
72
+ verifiedDomains?: {
73
+ resolver: 'doh' | 'system';
74
+ };
63
75
  /**
64
76
  * Serve the REST API under /v1, with the application API for admin keys, and its OpenAPI document
65
77
  * (SLOTLOCK_REST_API: on unless off).
@@ -147,11 +159,12 @@ export interface SlotlockRunningServer {
147
159
  }
148
160
  /**
149
161
  * Whether the dashboard's allowlists let a tenant act, wherever the dashboard runs: a
150
- * `github-org:<id>` tenant only while SLOTLOCK_DASHBOARD_ORGS lists that organization; a
151
- * `github:<id>` tenant only while SLOTLOCK_DASHBOARD_USERS is `*` or lists that id; every tenant when
152
- * SLOTLOCK_DASHBOARD_USERS is not set, and every other tenant.
162
+ * `github-org:<id>` tenant only while SLOTLOCK_DASHBOARD_ORGS lists that organization; a `team:<id>`
163
+ * tenant only with SLOTLOCK_DASHBOARD_TEAM_WORKSPACES=on (and, as serve's authenticate asks, while
164
+ * it exists); a `github:<id>` tenant only while SLOTLOCK_DASHBOARD_USERS is `*` or lists that id;
165
+ * every tenant when SLOTLOCK_DASHBOARD_USERS is not set, and every other tenant.
153
166
  */
154
- export declare function slotlockServeAllowsTenant(config: Pick<SlotlockServeConfig, 'organizations' | 'allowedUsers'>, tenantRef: string): boolean;
167
+ export declare function slotlockServeAllowsTenant(config: Pick<SlotlockServeConfig, 'organizations' | 'allowedUsers' | 'teamWorkspaces'>, tenantRef: string): boolean;
155
168
  /**
156
169
  * The store `serve` answers requests from. Every surface writes through it, so its tenant limits
157
170
  * bound what any key can add, and its transaction limits what one tenant holds of the shared pool.
@@ -166,7 +179,7 @@ export declare function createSlotlockMaintenanceStore(sql: SlotlockSql, caps: S
166
179
  }): SlotlockStore;
167
180
  /** Serve MCP and A2A over HTTP from the Postgres store, as one token-authenticated tenant. */
168
181
  export declare function startSlotlockServer(config: SlotlockServeConfig, log: SlotlockLogger): Promise<SlotlockRunningServer>;
169
- export declare const SLOTLOCK_CLI_HELP = "Usage: slotlock <command> [options]\n\nCommands:\n migrate Apply the schema and row-level security, then grant the serving\n role when SLOTLOCK_MIGRATE_DATABASE_URL names another role\n serve [--migrate] Serve MCP, A2A and REST over HTTP (--migrate runs migrate first);\n maintains every tenant with data every hour\n maintain Run maintenance for the tenant now: extend recurring events to the\n rolling horizon and prune past the retention window (for cron)\n resource add <ref> [--timezone <IANA zone>]\n Create a calendar resource (a car, room, person or machine)\n resource list Print the tenant's resources, one JSON object per line\n resource archive <ref> Take a resource out of use: no new bookings, holds or events, and\n no longer listed; what is on it stays\n resource restore <ref> Put an archived resource back in use\n resource parent <ref> (<parent-ref> | none)\n Make a resource a part of a composite (a room of a hall): booking\n the whole then takes every part, and a part's booking keeps the\n whole from being booked; none makes it no part\n resource delete <ref> Delete a resource and its history; refused while a booking, hold\n or event on it has not ended\n hours show [--resource <ref>]\n Print each resource's bookable hours (null: the server's)\n hours set <json> (--resource <ref> | --all)\n Give one resource, or every one, weekly hours of its own; [] closes\n hours clear (--resource <ref> | --all)\n Hand one resource, or every one, back to the server's hours\n rules set <json> --resource <ref>\n Set what a booking on the resource must satisfy, like\n {\"minNoticeMinutes\":120,\"slotStepMinutes\":30,\"turnaroundMinutes\":30,\"enforceHours\":true}\n rules clear --resource <ref>\n Remove the resource's booking rules\n key create <name> [--scope read,write,admin] [--expires-in-days <n>]\n [--locations <ref,...>] [--groups <ref,...>]\n Create an API key for the tenant (default scope read,write, no\n expiry; admin adds the application API) and print it once; only its\n SHA-256 is stored. With --locations or --groups, the key reaches\n only the resources there (each location with those in it), read and\n write only\n [--max-holds <n>] [--max-held-hours <n>] [--requests-per-minute <n>]\n [--requires-approval]\n ... and hold the key below the server's limits: its live holds, the\n hours they hold, its requests a minute, and every booking it makes\n waiting for a person (the dashboard's Approvals)\n key limits <id> [--max-holds <n>] [--max-held-hours <n>] [--requests-per-minute <n>]\n [--requires-approval]\n Replace an active key's limits; none at all gives it the server's\n key list Print the tenant's API keys, one JSON object per line\n key rotate <id> Give an active key a new secret and print it once; the old one stops\n working at once, and the key keeps its id and what it booked\n key revoke <id> Revoke one of the tenant's API keys at once\n sandbox seed <car-rental|clinic|desks>\n Fill an empty test environment (SLOTLOCK_TENANT=<tenant>~test) with a\n scenario: locations, groups, resources, hours, rules and bookings\n sandbox reset Empty the test environment; its keys, webhooks and plan stay\n sandbox fast-forward <minutes>\n Make what would lapse in the test environment within that time lapse\n now (holds, offers, approvals), then run maintenance\n sandbox race <resource-ref> [--agents <n>] [--start <date-time>]\n Race agents (default 100) for one hour on a test resource and print who\n booked, who was refused and why, and how long the answers took\n partner create <id> <name> --contract <json | @file>\n Create a partner reselling Slotlock, or change its name and contract:\n its plans (limits and quotas), its accounts' key scopes and its prices\n (as the schema owner, SLOTLOCK_MIGRATE_DATABASE_URL)\n partner list Print the partners, one JSON object per line\n partner suspend <id> Suspend a partner: every account with it, and its keys stop\n partner unsuspend <id> Make a suspended partner active again\n partner key <id> <name> Create a partner key (slk_partner_\u2026) and print it once\n partner revoke-key <id> <key-id>\n Revoke one of a partner's keys at once\n partner customer <id> <cus_\u2026>\n The Stripe customer its monthly invoice goes to\n partner webhook <id> <https-url>\n Where usage.period_closed goes; prints its signing secret once\n billing daily Once a day (cron), as the schema owner: close last month for every\n active partner once it has been over for 6 hours, report overage\n when SLOTLOCK_OVERAGE is on (with STRIPE_SECRET_KEY), and list the\n partners' reconciliation mismatches of the last 7 days; exits 1 when\n anything waits or differs. Safe to run again\n overage report [--month <YYYY-MM>] [--event <name>]\n With SLOTLOCK_OVERAGE: send Stripe each paying Developer tenant's\n operations above its quota this month (or --month) not yet sent, as\n Billing Meter events (--event, default slotlock_operations), once\n (as the schema owner, with STRIPE_SECRET_KEY)\n partner close <id> [--month <YYYY-MM>]\n Close an ended month (default: last month): price its accounts' use\n with the contract, record the lines once, send usage.period_closed,\n and with STRIPE_SECRET_KEY add the lines to the partner's next Stripe\n invoice; safe to run again\n tenant status Print whether the tenant is suspended\n tenant suspend [--reason <text>]\n Suspend the tenant: it may read, and writes nothing; its keys get\n no new secret (as SLOTLOCK_MIGRATE_DATABASE_URL's role when set:\n only the schema owner suspends)\n tenant unsuspend Lift the tenant's suspension (as the schema owner, likewise)\n tenant erase --confirm <tenant>\n Delete everything the tenant has, its API keys included, for good;\n --confirm repeats SLOTLOCK_TENANT; refused while its hosted plan is\n paid for (cancel the subscription first)\n plan status Print the deployment's default plan and the tenant's own (hosted)\n plan default (free | developer | none) [--from <date>]\n Put every tenant without a plan of its own on this one from --from\n (an ISO date or time; default now), or none for no default; a new\n tenant gets it at once (as the schema owner, like tenant suspend)\n healthcheck Exit 0 when the local server reports ready\n\nOptions:\n -h, --help Show this help\n -v, --version Print the version\n\nEnvironment:\n DATABASE_URL postgres:// URL of the role Slotlock serves as (required); serve\n refuses a superuser or a role with BYPASSRLS\n SLOTLOCK_MIGRATE_DATABASE_URL postgres:// URL of the schema owner, for migrate (optional)\n SLOTLOCK_AUTH_TOKEN Optional bearer token for SLOTLOCK_TENANT with read and write\n access (never admin), 32+ random characters (serve); API keys\n work with or without it\n SLOTLOCK_CONFIRMATION_SECRET Signs pending write confirmations, 32+ random characters (serve;\n required when SLOTLOCK_CONFIRM_WRITES is not none)\n SLOTLOCK_METRICS_TOKEN Serves Prometheus metrics at /metrics to this bearer token, 32+\n random characters (serve; off without it)\n SLOTLOCK_CONFIRM_WRITES none (default), all, or a comma-separated list of write tools\n that wait for a person (MCP 2026-07-28 form elicitation; refused\n with confirmation_required on REST, A2A and older MCP)\n SLOTLOCK_PUBLIC_URL URL clients use (default http://localhost:$PORT)\n SLOTLOCK_TENANT Tenant of the token, resource and key commands (default \"default\")\n SLOTLOCK_AVAILABILITY JSON array of weekly bookable-hours rules (default: none)\n SLOTLOCK_REST_API on (default): also serve the tools as a REST API under /v1, and\n the application API for admin keys, described at /openapi.json;\n off: MCP and A2A only\n SLOTLOCK_RETENTION_DAYS Days of history kept: the change feed, ended waitlist entries,\n and retired answers (never under 30) (1-3650, default 30;\n serve and maintain)\n SLOTLOCK_USAGE on: count each tenant's operations, bookings and holds by day, API\n key and surface (slotlock.usage_daily, kept 400 days); off\n (default): record nothing\n SLOTLOCK_QUOTAS on (needs SLOTLOCK_USAGE=on): refuse a tenant on a plan past its\n operations or bookings this month (quota_exceeded, 429); off\n (default): no quota\n SLOTLOCK_OVERAGE off (default), or on (300) or a percent (111-1000): a paying\n Developer tenant is refused only past that share of its\n operations, and the rest above its quota is billed (overage\n report); needs SLOTLOCK_QUOTAS=on\n SLOTLOCK_SHARED_LIMITS on: tenant request budgets are shared by every server through the\n database, so N servers allow a tenant its rate once; off (default):\n each server keeps its own\n SLOTLOCK_WEBHOOKS on (default): deliver each tenant's changes to its webhooks,\n never to a private network address; private: those too, for\n testing; off: keep the change feed, send nothing\n SLOTLOCK_LIMITS Request limits, comma-separated name=value over the defaults:\n rates as <count>/<s|m|h>[:<burst>] or off for address (50/s:100,\n every request per client, before any key lookup), key_read,\n tenant_read (20/s:40), key_write, tenant_write (5/s:20),\n global_read (500/s:1000), global_write (50/s:100), app_read\n (20/s:40) and app_write (5/s:20) per tenant for the\n application API, sign_in (20/m:20); counts for\n tenant_series_writes and series_writes\n (1: writes that may expand a recurring series at once),\n maintenance_runs (1: application API maintenance runs at once),\n subscriptions (256), tenant_subscriptions (16),\n key_subscriptions (4, and fewer than tenant_subscriptions),\n key_lookups (2), key_lookup_queue (256),\n tenant_transactions (2: one tenant's database transactions at\n once, of the pool's 10); what one tenant may store:\n tenant_holds (10000), tenant_reservations, tenant_events and\n tenant_coverage (100000), holder_holds (20) and\n holder_held_hours (2160), which maintain keeps too\n SLOTLOCK_TRUSTED_PROXY_HEADER The request field a proxy in front sets to the client address,\n such as CF-Connecting-IP (default: the connection's address);\n set it only when every request comes through that proxy\n SLOTLOCK_TRUSTED_PROXY_SECRET A secret the proxy adds to every request in x-slotlock-proxy-proof,\n 32+ random characters: without it there, the field above is\n ignored and the connection's address counts\n SLOTLOCK_KNOWN_CLIENTS Comma-separated integrator names /metrics counts requests by, from\n their Slotlock-Client field (any other: other; none sent: none)\n OTEL_EXPORTER_OTLP_ENDPOINT An OpenTelemetry collector (OTLP over HTTP, JSON): a span per\n call is sent to <endpoint>/v1/traces (off without it)\n OTEL_EXPORTER_OTLP_TRACES_ENDPOINT The full traces URL instead of <endpoint>/v1/traces\n OTEL_EXPORTER_OTLP_METRICS_ENDPOINT The full metrics URL instead of <endpoint>/v1/metrics (sent each minute)\n OTEL_EXPORTER_OTLP_HEADERS name=value pairs sent with each export, such as an API key\n OTEL_SERVICE_NAME service.name of the spans (default slotlock)\n SLOTLOCK_GITHUB_CLIENT_ID Serve the dashboard at $SLOTLOCK_PUBLIC_URL/dashboard: the Client ID\n of a GitHub OAuth app with callback .../dashboard/callback\n SLOTLOCK_GITHUB_CLIENT_SECRET That app's client secret (dashboard)\n SLOTLOCK_SESSION_SECRET Signs dashboard sessions, 32+ random characters (dashboard)\n SLOTLOCK_ASSISTANT_SECRET Turns on the dashboard assistant and seals each organization's\n model API key with it, 32+ random characters (dashboard; off\n without it)\n SLOTLOCK_ASSISTANT_PREVIOUS_SECRETS Up to three earlier SLOTLOCK_ASSISTANT_SECRET values,\n comma-separated, newest first: keys sealed under one still open\n and are sealed again under the current secret when next used\n SLOTLOCK_ASSISTANT_PRIVATE_URLS off (default): refuse an assistant model URL whose host is, or\n resolves to, a loopback, private, link-local, CGNAT, unique-local\n or metadata address; on: allow them, for a self-hosted model\n (Ollama, vLLM) on this machine or network\n SLOTLOCK_GOOGLE_CLIENT_ID Sign-in with Google too: the client id of a Google OAuth client with\n redirect URI .../dashboard/callback (not with organization ids)\n SLOTLOCK_GOOGLE_CLIENT_SECRET Its client secret\n SLOTLOCK_DASHBOARD_USERS * or comma-separated GitHub user ids (and google:<subject>) that may sign in and whose keys work\n SLOTLOCK_DASHBOARD_ORGS comma-separated GitHub organization ids: each is one shared tenant;\n or enabled: each person's own tenant, plus the organizations an\n owner turns on from the dashboard's Teams page\n SLOTLOCK_DASHBOARD_NAME What the dashboard calls this deployment (default Slotlock)\n SLOTLOCK_DASHBOARD_TERMS_URL https URL of the terms people accept by signing in, linked on the\n sign-in page (none named without it)\n SLOTLOCK_DASHBOARD_PRIVACY_URL https URL of the privacy notice the sign-in page links\n HOST Listen address (default 127.0.0.1)\n PORT Listen port (default 8080)\n\nGenerate each secret with: openssl rand -hex 32\n";
182
+ export declare const SLOTLOCK_CLI_HELP = "Usage: slotlock <command> [options]\n\nCommands:\n migrate Apply the schema and row-level security, then grant the serving\n role when SLOTLOCK_MIGRATE_DATABASE_URL names another role\n serve [--migrate] Serve MCP, A2A and REST over HTTP (--migrate runs migrate first);\n maintains every tenant with data every hour\n maintain Run maintenance for the tenant now: extend recurring events to the\n rolling horizon and prune past the retention window (for cron)\n resource add <ref> [--timezone <IANA zone>]\n Create a calendar resource (a car, room, person or machine)\n resource list Print the tenant's resources, one JSON object per line\n resource archive <ref> Take a resource out of use: no new bookings, holds or events, and\n no longer listed; what is on it stays\n resource restore <ref> Put an archived resource back in use\n resource parent <ref> (<parent-ref> | none)\n Make a resource a part of a composite (a room of a hall): booking\n the whole then takes every part, and a part's booking keeps the\n whole from being booked; none makes it no part\n resource delete <ref> Delete a resource and its history; refused while a booking, hold\n or event on it has not ended\n hours show [--resource <ref>]\n Print each resource's bookable hours: its own (null when none) and\n what it inherits from its location or the tenant\n hours set <json> (--resource <ref> | --location <ref> | --all [--override | --matching])\n Give one resource, one location or the tenant weekly hours; a\n resource without its own inherits its location's, then the\n tenant's, then the server's; --override also clears resources'\n own hours, --matching only those the same as the tenant's were\n or as these (copies 0.8.0 made); [] closes\n hours clear (--resource <ref> | --location <ref> | --all)\n Clear those hours, so they inherit from further up\n closures list Print the tenant's closures and special days from yesterday on\n closures add <first-day> [<last-day>] --label <text> [--open HH:MM-HH:MM]\n [--resource <ref> | --location <ref>]\n Close whole days (YYYY-MM-DD), or with --open give them those hours\n instead, everywhere or for one location or resource\n closures delete <id> Remove a closure\n closures import <file> [--resource <ref> | --location <ref>] [--label <calendar name>]\n Close the days an iCalendar file of holidays names (each all-day\n event, for the next two years); importing the calendar again (by its\n name: --label, else its own) updates them and removes those it dropped\n rules set <json> --resource <ref>\n Set what a booking on the resource must satisfy, like\n {\"minNoticeMinutes\":120,\"slotStepMinutes\":30,\"turnaroundMinutes\":30,\"enforceHours\":true}\n rules clear --resource <ref>\n Remove the resource's booking rules\n key create <name> [--scope read,write,admin] [--expires-in-days <n>]\n [--locations <ref,...>] [--groups <ref,...>]\n Create an API key for the tenant (default scope read,write, no\n expiry; admin adds the application API) and print it once; only its\n SHA-256 is stored. With --locations or --groups, the key reaches\n only the resources there (each location with those in it), read and\n write only\n [--max-holds <n>] [--max-held-hours <n>] [--requests-per-minute <n>]\n [--requires-approval]\n ... and hold the key below the server's limits: its live holds, the\n hours they hold, its requests a minute, and every booking it makes\n waiting for a person (the dashboard's Approvals)\n key limits <id> [--max-holds <n>] [--max-held-hours <n>] [--requests-per-minute <n>]\n [--requires-approval]\n Replace an active key's limits; none at all gives it the server's\n key list Print the tenant's API keys, one JSON object per line\n key rotate <id> Give an active key a new secret and print it once; the old one stops\n working at once, and the key keeps its id and what it booked\n key revoke <id> Revoke one of the tenant's API keys at once\n sandbox seed <car-rental|clinic|desks>\n Fill an empty test environment (SLOTLOCK_TENANT=<tenant>~test) with a\n scenario: locations, groups, resources, hours, rules and bookings\n sandbox reset Empty the test environment; its keys, webhooks and plan stay\n sandbox fast-forward <minutes>\n Make what would lapse in the test environment within that time lapse\n now (holds, offers, approvals), then run maintenance\n sandbox race <resource-ref> [--agents <n>] [--start <date-time>]\n Race agents (default 100) for one hour on a test resource and print who\n booked, who was refused and why, and how long the answers took\n partner create <id> <name> --contract <json | @file>\n Create a partner reselling Slotlock, or change its name and contract:\n its plans (limits and quotas), its accounts' key scopes and its prices\n (as the schema owner, SLOTLOCK_MIGRATE_DATABASE_URL)\n partner list Print the partners, one JSON object per line\n partner suspend <id> Suspend a partner: every account with it, and its keys stop\n partner unsuspend <id> Make a suspended partner active again\n partner key <id> <name> Create a partner key (slk_partner_\u2026) and print it once\n partner revoke-key <id> <key-id>\n Revoke one of a partner's keys at once\n partner customer <id> <cus_\u2026>\n The Stripe customer its monthly invoice goes to\n partner webhook <id> <https-url>\n Where usage.period_closed goes; prints its signing secret once\n billing daily Once a day (cron), as the schema owner: close last month for every\n active partner once it has been over for 6 hours, report overage\n when SLOTLOCK_OVERAGE is on (with STRIPE_SECRET_KEY), and list the\n partners' reconciliation mismatches of the last 7 days; exits 1 when\n anything waits or differs. Safe to run again\n overage report [--month <YYYY-MM>] [--event <name>]\n With SLOTLOCK_OVERAGE: send Stripe each paying Developer tenant's\n operations above its quota this month (or --month) not yet sent, as\n Billing Meter events (--event, default slotlock_operations), once\n (as the schema owner, with STRIPE_SECRET_KEY)\n partner close <id> [--month <YYYY-MM>]\n Close an ended month (default: last month): price its accounts' use\n with the contract, record the lines once, send usage.period_closed,\n and with STRIPE_SECRET_KEY add the lines to the partner's next Stripe\n invoice; safe to run again\n tenant status Print whether the tenant is suspended\n tenant suspend [--reason <text>]\n Suspend the tenant: it may read, and writes nothing; its keys get\n no new secret (as SLOTLOCK_MIGRATE_DATABASE_URL's role when set:\n only the schema owner suspends)\n tenant unsuspend Lift the tenant's suspension (as the schema owner, likewise)\n tenant export [--out <dir>]\n Print everything the tenant keeps, read at one moment, one JSON line a\n row ({\"table\": ..., \"row\": ...}); with --out, one <table>.ndjson file a\n table in that directory (new or empty). Secrets stay out\n tenant erase --confirm <tenant>\n Delete everything the tenant has, its API keys included, for good;\n --confirm repeats SLOTLOCK_TENANT; refused while its hosted plan is\n paid for (cancel the subscription first)\n audit verify Check the tenant's audit log chain: prints how many events held and\n the first that does not (exit 1 when one does not)\n audit export Print the tenant's audit log, newest first, one JSON object per line\n subject export (--email <address> | --holder <ref> | --contact <ref> | --reference <ref>)\n Print what the tenant keeps about one person, one JSON line per row:\n {\"table\": ..., \"row\": ...}, up to 10,000 rows a table\n subject erase (--email <address> | --holder <ref> | --contact <ref> | --reference <ref>)\n Take one person out of the tenant's data: their contacts, attendee\n and waitlist entries go; their references become an erased name;\n bookings stay as time taken. Prints what it changed, by table\n plan status Print the deployment's default plan and the tenant's own (hosted)\n plan default (free | developer | none) [--from <date>]\n Put every tenant without a plan of its own on this one from --from\n (an ISO date or time; default now), or none for no default; a new\n tenant gets it at once (as the schema owner, like tenant suspend)\n healthcheck Exit 0 when the local server reports ready\n\nOptions:\n -h, --help Show this help\n -v, --version Print the version\n\nEnvironment:\n DATABASE_URL postgres:// URL of the role Slotlock serves as (required); serve\n refuses a superuser or a role with BYPASSRLS\n SLOTLOCK_MIGRATE_DATABASE_URL postgres:// URL of the schema owner, for migrate (optional)\n SLOTLOCK_AUTH_TOKEN Optional bearer token for SLOTLOCK_TENANT with read and write\n access (never admin), 32+ random characters (serve); API keys\n work with or without it\n SLOTLOCK_CONFIRMATION_SECRET Signs pending write confirmations, 32+ random characters (serve;\n required when SLOTLOCK_CONFIRM_WRITES is not none)\n SLOTLOCK_METRICS_TOKEN Serves Prometheus metrics at /metrics to this bearer token, 32+\n random characters (serve; off without it)\n SLOTLOCK_CONFIRM_WRITES none (default), all, or a comma-separated list of write tools\n that wait for a person (MCP 2026-07-28 form elicitation; refused\n with confirmation_required on REST, A2A and older MCP)\n SLOTLOCK_PUBLIC_URL URL clients use (default http://localhost:$PORT)\n SLOTLOCK_TENANT Tenant of the token, resource and key commands (default \"default\")\n SLOTLOCK_AVAILABILITY JSON array of weekly bookable-hours rules (default: none)\n SLOTLOCK_REST_API on (default): also serve the tools as a REST API under /v1, and\n the application API for admin keys, described at /openapi.json;\n off: MCP and A2A only\n SLOTLOCK_RETENTION_DAYS Days of history kept: the change feed, ended waitlist entries,\n and retired answers (never under 30) (1-3650, default 30;\n serve and maintain)\n SLOTLOCK_USAGE on: count each tenant's operations, bookings and holds by day, API\n key and surface (slotlock.usage_daily, kept 400 days); off\n (default): record nothing\n SLOTLOCK_QUOTAS on (needs SLOTLOCK_USAGE=on): refuse a tenant on a plan past its\n operations or bookings this month (quota_exceeded, 429); off\n (default): no quota\n SLOTLOCK_OVERAGE off (default), or on (300) or a percent (111-1000): a paying\n Developer tenant is refused only past that share of its\n operations, and the rest above its quota is billed (overage\n report); needs SLOTLOCK_QUOTAS=on\n SLOTLOCK_SHARED_LIMITS on: tenant request budgets are shared by every server through the\n database, so N servers allow a tenant its rate once; off (default):\n each server keeps its own\n SLOTLOCK_WEBHOOKS on (default): deliver each tenant's changes to its webhooks,\n never to a private network address; private: those too, for\n testing; off: keep the change feed, send nothing\n SLOTLOCK_LIMITS Request limits, comma-separated name=value over the defaults:\n rates as <count>/<s|m|h>[:<burst>] or off for address (50/s:100,\n every request per client, before any key lookup), key_read,\n tenant_read (20/s:40), key_write, tenant_write (5/s:20),\n global_read (500/s:1000), global_write (50/s:100), app_read\n (20/s:40) and app_write (5/s:20) per tenant for the\n application API, sign_in (20/m:20); counts for\n tenant_series_writes and series_writes\n (1: writes that may expand a recurring series at once),\n maintenance_runs (1: application API maintenance runs at once),\n subscriptions (256), tenant_subscriptions (16),\n key_subscriptions (4, and fewer than tenant_subscriptions),\n key_lookups (2), key_lookup_queue (256),\n tenant_transactions (2: one tenant's database transactions at\n once, of the pool's 10); what one tenant may store:\n tenant_holds (10000), tenant_reservations, tenant_events and\n tenant_coverage (100000), holder_holds (20) and\n holder_held_hours (2160), which maintain keeps too\n SLOTLOCK_TRUSTED_PROXY_HEADER The request field a proxy in front sets to the client address,\n such as CF-Connecting-IP (default: the connection's address);\n set it only when every request comes through that proxy\n SLOTLOCK_TRUSTED_PROXY_SECRET A secret the proxy adds to every request in x-slotlock-proxy-proof,\n 32+ random characters: without it there, the field above is\n ignored and the connection's address counts\n SLOTLOCK_KNOWN_CLIENTS Comma-separated integrator names /metrics counts requests by, from\n their Slotlock-Client field (any other: other; none sent: none)\n OTEL_EXPORTER_OTLP_ENDPOINT An OpenTelemetry collector (OTLP over HTTP, JSON): a span per\n call is sent to <endpoint>/v1/traces (off without it)\n OTEL_EXPORTER_OTLP_TRACES_ENDPOINT The full traces URL instead of <endpoint>/v1/traces\n OTEL_EXPORTER_OTLP_METRICS_ENDPOINT The full metrics URL instead of <endpoint>/v1/metrics (sent each minute)\n OTEL_EXPORTER_OTLP_HEADERS name=value pairs sent with each export, such as an API key\n OTEL_SERVICE_NAME service.name of the spans (default slotlock)\n SLOTLOCK_GITHUB_CLIENT_ID Serve the dashboard at $SLOTLOCK_PUBLIC_URL/dashboard: the Client ID\n of a GitHub OAuth app with callback .../dashboard/callback\n SLOTLOCK_GITHUB_CLIENT_SECRET That app's client secret (dashboard)\n SLOTLOCK_SESSION_SECRET Signs dashboard sessions, 32+ random characters (dashboard)\n SLOTLOCK_ASSISTANT_SECRET Turns on the dashboard assistant and seals each organization's\n model API key with it, 32+ random characters (dashboard; off\n without it)\n SLOTLOCK_ASSISTANT_PREVIOUS_SECRETS Up to three earlier SLOTLOCK_ASSISTANT_SECRET values,\n comma-separated, newest first: keys sealed under one still open\n and are sealed again under the current secret when next used\n SLOTLOCK_ASSISTANT_PRIVATE_URLS off (default): refuse an assistant model URL whose host is, or\n resolves to, a loopback, private, link-local, CGNAT, unique-local\n or metadata address; on: allow them, for a self-hosted model\n (Ollama, vLLM) on this machine or network\n SLOTLOCK_GOOGLE_CLIENT_ID Sign-in with Google too: the client id of a Google OAuth client with\n redirect URI .../dashboard/callback (not with organization ids)\n SLOTLOCK_GOOGLE_CLIENT_SECRET Its client secret\n SLOTLOCK_DASHBOARD_USERS * or comma-separated GitHub user ids (and google:<subject>) that may sign in and whose keys work\n SLOTLOCK_DASHBOARD_ORGS comma-separated GitHub organization ids: each is one shared tenant;\n or enabled: each person's own tenant, plus the organizations an\n owner turns on from the dashboard's Teams page\n SLOTLOCK_DASHBOARD_TEAM_WORKSPACES on: anyone signed in creates team workspaces and invites\n people to them by email, each with a role (default off; not\n with organization ids)\n SLOTLOCK_DASHBOARD_NAME What the dashboard calls this deployment (default Slotlock)\n SLOTLOCK_DASHBOARD_TERMS_URL https URL of the terms people accept by signing in, linked on the\n sign-in page (none named without it)\n SLOTLOCK_DASHBOARD_PRIVACY_URL https URL of the privacy notice the sign-in page links\n SLOTLOCK_INVITATION_EMAIL resend or postmark: send team invitations by email (with team\n workspaces; without it, the person inviting sends the link)\n SLOTLOCK_INVITATION_EMAIL_KEY Resend's API key, or Postmark's server token\n SLOTLOCK_INVITATION_EMAIL_FROM The sender the provider verified: team@example.com or Name <...>\n SLOTLOCK_VERIFIED_DOMAINS on or off (default): team Owners verify their domain with a DNS\n record, and people at it may ask to join (with team workspaces)\n SLOTLOCK_DNS_RESOLVER doh (default: Cloudflare, checked by Google) or system: how a\n verified domain's record is looked up each day\n SLOTLOCK_TEAM_SSO on or off (default): each team signs in through its own identity\n provider over OpenID Connect (with verified domains)\n SLOTLOCK_SSO_SECRET 32+ characters: seals each team's client secret (with team SSO)\n SLOTLOCK_TEAM_SCIM on or off (default): each team's identity provider provisions its\n people over SCIM 2.0 at /dashboard/scim/v2 (with verified domains)\n HOST Listen address (default 127.0.0.1)\n PORT Listen port (default 8080)\n\nGenerate each secret with: openssl rand -hex 32\n";
170
183
  /**
171
184
  * Run one `slotlock` command and resolve to its exit code: 0 success, 1 failure, 2 a usage or
172
185
  * configuration error (nothing was started). `serve` resolves once `io.signal` aborts and it has