wizards 0.1.9
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/LICENSE +110 -0
- package/README.md +170 -0
- package/apps/runtime/dist/agents/architect.js +453 -0
- package/apps/runtime/dist/agents/project-assistant.js +354 -0
- package/apps/runtime/dist/agents/subscription.js +206 -0
- package/apps/runtime/dist/agents/thought.js +21 -0
- package/apps/runtime/dist/agents/website.js +197 -0
- package/apps/runtime/dist/app.js +153 -0
- package/apps/runtime/dist/auth/account.js +121 -0
- package/apps/runtime/dist/auth/index.js +329 -0
- package/apps/runtime/dist/auth/keys.js +55 -0
- package/apps/runtime/dist/auth/local-ticket.js +23 -0
- package/apps/runtime/dist/authoring/example.js +73 -0
- package/apps/runtime/dist/authoring/guide.js +259 -0
- package/apps/runtime/dist/authoring/ops.js +117 -0
- package/apps/runtime/dist/cli/autostart.js +134 -0
- package/apps/runtime/dist/cli/clients.js +44 -0
- package/apps/runtime/dist/cli/environment.js +81 -0
- package/apps/runtime/dist/cli/exec.js +45 -0
- package/apps/runtime/dist/cli/home.js +74 -0
- package/apps/runtime/dist/cli/machine.js +145 -0
- package/apps/runtime/dist/cli/main.js +125 -0
- package/apps/runtime/dist/cli/setup.js +273 -0
- package/apps/runtime/dist/cli/start.js +190 -0
- package/apps/runtime/dist/cli/status.js +120 -0
- package/apps/runtime/dist/cli/ui.js +12 -0
- package/apps/runtime/dist/cli/update.js +58 -0
- package/apps/runtime/dist/connectors/builtin.js +70 -0
- package/apps/runtime/dist/connectors/external.js +400 -0
- package/apps/runtime/dist/connectors/gmail.js +107 -0
- package/apps/runtime/dist/connectors/imap.js +217 -0
- package/apps/runtime/dist/connectors/index.js +266 -0
- package/apps/runtime/dist/connectors/mail.js +72 -0
- package/apps/runtime/dist/connectors/outlook.js +90 -0
- package/apps/runtime/dist/credits/credits.js +103 -0
- package/apps/runtime/dist/credits/estimate.js +132 -0
- package/apps/runtime/dist/db/adopt.js +149 -0
- package/apps/runtime/dist/db/client.js +146 -0
- package/apps/runtime/dist/db/control-schema.js +70 -0
- package/apps/runtime/dist/db/migrations/control/0000_last_absorbing_man.sql +47 -0
- package/apps/runtime/dist/db/migrations/control/0001_gorgeous_nicolaos.sql +41 -0
- package/apps/runtime/dist/db/migrations/control/0002_secret_penance.sql +1 -0
- package/apps/runtime/dist/db/migrations/control/0003_nasty_mordo.sql +3 -0
- package/apps/runtime/dist/db/migrations/control/0004_breezy_eddie_brock.sql +6 -0
- package/apps/runtime/dist/db/migrations/control/meta/0000_snapshot.json +311 -0
- package/apps/runtime/dist/db/migrations/control/meta/0001_snapshot.json +599 -0
- package/apps/runtime/dist/db/migrations/control/meta/0002_snapshot.json +607 -0
- package/apps/runtime/dist/db/migrations/control/meta/0003_snapshot.json +311 -0
- package/apps/runtime/dist/db/migrations/control/meta/0004_snapshot.json +350 -0
- package/apps/runtime/dist/db/migrations/control/meta/_journal.json +41 -0
- package/apps/runtime/dist/db/migrations/tenant/0000_eminent_freak.sql +188 -0
- package/apps/runtime/dist/db/migrations/tenant/0001_salty_master_chief.sql +1 -0
- package/apps/runtime/dist/db/migrations/tenant/0002_cloudy_firebird.sql +1 -0
- package/apps/runtime/dist/db/migrations/tenant/0003_brown_warbound.sql +52 -0
- package/apps/runtime/dist/db/migrations/tenant/0004_romantic_nocturne.sql +6 -0
- package/apps/runtime/dist/db/migrations/tenant/meta/0000_snapshot.json +1323 -0
- package/apps/runtime/dist/db/migrations/tenant/meta/0001_snapshot.json +1330 -0
- package/apps/runtime/dist/db/migrations/tenant/meta/0002_snapshot.json +1337 -0
- package/apps/runtime/dist/db/migrations/tenant/meta/0003_snapshot.json +1605 -0
- package/apps/runtime/dist/db/migrations/tenant/meta/0004_snapshot.json +1645 -0
- package/apps/runtime/dist/db/migrations/tenant/meta/_journal.json +41 -0
- package/apps/runtime/dist/db/schema.js +278 -0
- package/apps/runtime/dist/documents/parse.js +230 -0
- package/apps/runtime/dist/engenty/connections-external/build-connector.js +193 -0
- package/apps/runtime/dist/engenty/connections-external/errors.js +11 -0
- package/apps/runtime/dist/engenty/connections-external/import-service.js +270 -0
- package/apps/runtime/dist/engenty/connections-external/importer/classify.js +34 -0
- package/apps/runtime/dist/engenty/connections-external/importer/map-auth.js +185 -0
- package/apps/runtime/dist/engenty/connections-external/importer/normalize-mcp.js +63 -0
- package/apps/runtime/dist/engenty/connections-external/importer/normalize-openapi.js +130 -0
- package/apps/runtime/dist/engenty/connections-external/invoke/http-invoker.js +143 -0
- package/apps/runtime/dist/engenty/connections-external/invoke/mcp-client.js +143 -0
- package/apps/runtime/dist/engenty/connections-external/net/guarded-fetch.js +119 -0
- package/apps/runtime/dist/engenty/connections-external/oauth-dcr.js +88 -0
- package/apps/runtime/dist/engenty/connections-external/registry-client.js +336 -0
- package/apps/runtime/dist/engenty/connections-external/registry-source.js +82 -0
- package/apps/runtime/dist/engenty/connections-external/types.js +7 -0
- package/apps/runtime/dist/engenty/connections-github/connector.js +209 -0
- package/apps/runtime/dist/engenty/connections-google/connectors/calendar.js +359 -0
- package/apps/runtime/dist/engenty/connections-google/connectors/contacts.js +94 -0
- package/apps/runtime/dist/engenty/connections-google/connectors/drive.js +290 -0
- package/apps/runtime/dist/engenty/connections-google/connectors/gmail.js +567 -0
- package/apps/runtime/dist/engenty/connections-google/definitions.js +11 -0
- package/apps/runtime/dist/engenty/connections-google/shared.js +73 -0
- package/apps/runtime/dist/engenty/connections-hubspot/connector.js +140 -0
- package/apps/runtime/dist/engenty/connections-microsoft/action.js +17 -0
- package/apps/runtime/dist/engenty/connections-microsoft/graph.js +114 -0
- package/apps/runtime/dist/engenty/connections-microsoft/onedrive.js +286 -0
- package/apps/runtime/dist/engenty/connections-microsoft/outlook.js +464 -0
- package/apps/runtime/dist/engenty/connections-s3/s3.js +328 -0
- package/apps/runtime/dist/engenty/connections-sdk/files-capability.js +86 -0
- package/apps/runtime/dist/engenty/connections-sdk/oauth2.js +150 -0
- package/apps/runtime/dist/engenty/connections-sdk/registry.js +89 -0
- package/apps/runtime/dist/engenty/connections-sdk/storage-capability.js +84 -0
- package/apps/runtime/dist/engenty/connections-sdk/types.js +30 -0
- package/apps/runtime/dist/engenty/connections-slack/connector.js +360 -0
- package/apps/runtime/dist/engenty/csv-import/csv-matrix.js +178 -0
- package/apps/runtime/dist/engenty/csv-import/import-sources.js +87 -0
- package/apps/runtime/dist/engenty/csv-import/parse-csv.js +129 -0
- package/apps/runtime/dist/engenty/csv-import/serialize-csv.js +20 -0
- package/apps/runtime/dist/engenty/csv-import/types.js +1 -0
- package/apps/runtime/dist/engenty/csv-import/xlsx-workbook.js +159 -0
- package/apps/runtime/dist/engenty/doc-converter/interface.js +9 -0
- package/apps/runtime/dist/engenty/doc-converter/page-break.js +92 -0
- package/apps/runtime/dist/engenty/doc-converter/providers/local/index.js +72 -0
- package/apps/runtime/dist/engenty/document-scanner/schemas/classifier.js +30 -0
- package/apps/runtime/dist/engenty/document-scanner/schemas/index.js +4 -0
- package/apps/runtime/dist/engenty/document-scanner/schemas/invoice.js +73 -0
- package/apps/runtime/dist/engenty/document-scanner/schemas/receipt.js +63 -0
- package/apps/runtime/dist/engenty/document-scanner/schemas/shared.js +40 -0
- package/apps/runtime/dist/engenty/search-index/chunks.js +164 -0
- package/apps/runtime/dist/engenty/search-index/contracts.js +9 -0
- package/apps/runtime/dist/engenty/shims/connections-sdk.js +39 -0
- package/apps/runtime/dist/engenty/shims/plugin-sdk.js +1 -0
- package/apps/runtime/dist/engenty/shims/web-ingest.js +3 -0
- package/apps/runtime/dist/engenty/web-ingest/ssrf.js +277 -0
- package/apps/runtime/dist/engine/asks.js +60 -0
- package/apps/runtime/dist/engine/events.js +46 -0
- package/apps/runtime/dist/engine/input.js +170 -0
- package/apps/runtime/dist/engine/prepare.js +51 -0
- package/apps/runtime/dist/engine/requirements.js +124 -0
- package/apps/runtime/dist/engine/resources.js +89 -0
- package/apps/runtime/dist/engine/runner.js +560 -0
- package/apps/runtime/dist/engine/steps.js +598 -0
- package/apps/runtime/dist/engine/template.js +183 -0
- package/apps/runtime/dist/engine/types.js +3 -0
- package/apps/runtime/dist/env.js +224 -0
- package/apps/runtime/dist/files/blobs.js +22 -0
- package/apps/runtime/dist/files/objects.js +86 -0
- package/apps/runtime/dist/files/storage.js +95 -0
- package/apps/runtime/dist/geocode.js +58 -0
- package/apps/runtime/dist/harness/claude.js +138 -0
- package/apps/runtime/dist/harness/codex.js +228 -0
- package/apps/runtime/dist/harness/cursor.js +93 -0
- package/apps/runtime/dist/harness/env.js +124 -0
- package/apps/runtime/dist/harness/gemini.js +126 -0
- package/apps/runtime/dist/harness/index.js +99 -0
- package/apps/runtime/dist/harness/model.js +267 -0
- package/apps/runtime/dist/harness/prompt.js +121 -0
- package/apps/runtime/dist/harness/terminal.js +193 -0
- package/apps/runtime/dist/harness/types.js +7 -0
- package/apps/runtime/dist/index.js +76 -0
- package/apps/runtime/dist/limits.js +57 -0
- package/apps/runtime/dist/link-preview.js +59 -0
- package/apps/runtime/dist/manage.js +151 -0
- package/apps/runtime/dist/mcp/auth.js +102 -0
- package/apps/runtime/dist/mcp/bridge.js +59 -0
- package/apps/runtime/dist/mcp/handler.js +42 -0
- package/apps/runtime/dist/mcp/scopes.js +5 -0
- package/apps/runtime/dist/mcp/tools.js +401 -0
- package/apps/runtime/dist/media/ffmpeg.js +121 -0
- package/apps/runtime/dist/media/generate.js +120 -0
- package/apps/runtime/dist/media/marking.js +121 -0
- package/apps/runtime/dist/media/transcribe.js +30 -0
- package/apps/runtime/dist/model-errors.js +3 -0
- package/apps/runtime/dist/models.js +489 -0
- package/apps/runtime/dist/plugin.js +61 -0
- package/apps/runtime/dist/render/chromium.js +85 -0
- package/apps/runtime/dist/render/convert.js +161 -0
- package/apps/runtime/dist/render/downloads.js +277 -0
- package/apps/runtime/dist/render/guard.js +25 -0
- package/apps/runtime/dist/routes/api.js +74 -0
- package/apps/runtime/dist/routes/delivery.js +52 -0
- package/apps/runtime/dist/routes/internal.js +49 -0
- package/apps/runtime/dist/routes/marketplace.js +36 -0
- package/apps/runtime/dist/routes/runs.js +805 -0
- package/apps/runtime/dist/routes/studio.js +702 -0
- package/apps/runtime/dist/routes/wizard-stream.js +30 -0
- package/apps/runtime/dist/running.js +39 -0
- package/apps/runtime/dist/sandbox/agentos.js +191 -0
- package/apps/runtime/dist/sandbox/docker.js +77 -0
- package/apps/runtime/dist/sandbox/index.js +30 -0
- package/apps/runtime/dist/sandbox/types.js +1 -0
- package/apps/runtime/dist/secrets/crypto.js +28 -0
- package/apps/runtime/dist/secrets/signing.js +34 -0
- package/apps/runtime/dist/secrets/vault.js +59 -0
- package/apps/runtime/dist/services/architect.js +92 -0
- package/apps/runtime/dist/services/brand.js +20 -0
- package/apps/runtime/dist/services/cloud.js +52 -0
- package/apps/runtime/dist/services/draft-events.js +27 -0
- package/apps/runtime/dist/services/errors.js +24 -0
- package/apps/runtime/dist/services/files.js +129 -0
- package/apps/runtime/dist/services/links.js +16 -0
- package/apps/runtime/dist/services/marketplace.js +292 -0
- package/apps/runtime/dist/services/package.js +202 -0
- package/apps/runtime/dist/services/profile.js +23 -0
- package/apps/runtime/dist/services/project-files.js +266 -0
- package/apps/runtime/dist/services/project-index.js +109 -0
- package/apps/runtime/dist/services/projects.js +181 -0
- package/apps/runtime/dist/services/runs.js +224 -0
- package/apps/runtime/dist/services/shares.js +129 -0
- package/apps/runtime/dist/services/widgets.js +34 -0
- package/apps/runtime/dist/services/wizards.js +342 -0
- package/apps/runtime/dist/settings.js +16 -0
- package/apps/runtime/dist/store/index.js +279 -0
- package/apps/runtime/dist/tenants/control.js +106 -0
- package/apps/runtime/dist/tenants/tenant.js +26 -0
- package/apps/runtime/dist/tools/browser.js +380 -0
- package/apps/runtime/dist/tools/connector.js +204 -0
- package/apps/runtime/dist/tools/index.js +234 -0
- package/apps/runtime/dist/tools/mail.js +104 -0
- package/apps/runtime/dist/tools/net-guard.js +59 -0
- package/apps/runtime/dist/tools/project.js +53 -0
- package/apps/runtime/dist/tools/shared.js +42 -0
- package/apps/runtime/dist/tools/store.js +238 -0
- package/apps/runtime/dist/update.js +118 -0
- package/apps/runtime/dist/widgets/bundle.js +99 -0
- package/apps/runtime/dist/widgets/render.js +312 -0
- package/apps/runtime/dist/widgets/runtime.js +84 -0
- package/apps/runtime/dist/widgets/script.js +30 -0
- package/apps/runtime/dist/widgets/step.js +192 -0
- package/apps/web/dist/ai-labels/ai-black.svg +20 -0
- package/apps/web/dist/ai-labels/ai-generated-black.svg +31 -0
- package/apps/web/dist/ai-labels/ai-generated-white.svg +32 -0
- package/apps/web/dist/ai-labels/ai-modified-black.svg +30 -0
- package/apps/web/dist/ai-labels/ai-modified-white.svg +31 -0
- package/apps/web/dist/ai-labels/ai-white.svg +21 -0
- package/apps/web/dist/assets/ChangelogDialog-O00vEnbm.js +1 -0
- package/apps/web/dist/assets/ChatPanel-CH9ry0Hp.js +1 -0
- package/apps/web/dist/assets/CreditsDialog-DRzecwUw.js +1 -0
- package/apps/web/dist/assets/EditorPage-BRnBf02Q.js +2 -0
- package/apps/web/dist/assets/FlowPreview-DDOj8IAH.js +1 -0
- package/apps/web/dist/assets/HomePage-CAGTTG3N.js +1 -0
- package/apps/web/dist/assets/ImportWizard-0RRrAnX3.js +1 -0
- package/apps/web/dist/assets/LocalRuntime-BrP-ENHg.css +1 -0
- package/apps/web/dist/assets/LocalRuntime-C-TBYrzs.js +16 -0
- package/apps/web/dist/assets/NewWizardPage-kI90h3_9.js +2 -0
- package/apps/web/dist/assets/PublicRunner-CKRDeIVd.js +1 -0
- package/apps/web/dist/assets/RunnerView-Rc4H8wYH.js +2 -0
- package/apps/web/dist/assets/SettingsPage-CESMuZxq.js +2 -0
- package/apps/web/dist/assets/SetupPage-D1rrYU14.js +1 -0
- package/apps/web/dist/assets/SharePage-COTAmOgh.js +1 -0
- package/apps/web/dist/assets/api-BY28cjGY.js +10 -0
- package/apps/web/dist/assets/arrow-left-UpjOBfQp.js +1 -0
- package/apps/web/dist/assets/arrow-right-CucQycEy.js +1 -0
- package/apps/web/dist/assets/changelog-p3wsaDDS.js +1 -0
- package/apps/web/dist/assets/check-Bjx41xbB.js +1 -0
- package/apps/web/dist/assets/createLucideIcon-DBu2Ovxj.js +1 -0
- package/apps/web/dist/assets/definition-cy7uvY5L.js +55 -0
- package/apps/web/dist/assets/engenty-CWIFpgKq.js +1 -0
- package/apps/web/dist/assets/esm-D1OEhjTK.js +7 -0
- package/apps/web/dist/assets/external-link-CRg6DVdO.js +1 -0
- package/apps/web/dist/assets/fluffy-engenty-CJ3wCQgT.js +456 -0
- package/apps/web/dist/assets/index-CQ_q9iMO.css +1 -0
- package/apps/web/dist/assets/index-D0gxh90t.js +10 -0
- package/apps/web/dist/assets/jsx-runtime-Dk72oS4N.js +1 -0
- package/apps/web/dist/assets/meta-BbSdtTUa.js +1 -0
- package/apps/web/dist/assets/mic-CeqQ89Dj.js +1 -0
- package/apps/web/dist/assets/oss-credits-cPuZUFHD.js +1 -0
- package/apps/web/dist/assets/outputs-D6pxH6ve.js +69 -0
- package/apps/web/dist/assets/plus-HA210wXA.js +1 -0
- package/apps/web/dist/assets/preload-helper-CNEI7N0N.js +1 -0
- package/apps/web/dist/assets/qr-scanner-worker.min-CJaXo3yu.js +98 -0
- package/apps/web/dist/assets/qr-scanner.min-C_UI0gH-.js +1 -0
- package/apps/web/dist/assets/type-BzYgVWKL.js +1 -0
- package/apps/web/dist/assets/ui-Ddwl35A6.js +1 -0
- package/apps/web/dist/assets/upload-DsgLfQ3U.js +1 -0
- package/apps/web/dist/assets/video-Dk1fuTDl.js +1 -0
- package/apps/web/dist/assets/wand-sparkles-Bf97fs8T.js +1 -0
- package/apps/web/dist/embed.js +102 -0
- package/apps/web/dist/favicon.svg +1 -0
- package/apps/web/dist/icons/apple-touch-icon.png +0 -0
- package/apps/web/dist/icons/icon-192.png +0 -0
- package/apps/web/dist/icons/icon-512.png +0 -0
- package/apps/web/dist/icons/icon-maskable-512.png +0 -0
- package/apps/web/dist/index.html +46 -0
- package/apps/web/dist/install.sh +532 -0
- package/apps/web/dist/manifest.webmanifest +20 -0
- package/apps/web/dist/sw.js +15 -0
- package/bin/wizards.mjs +13 -0
- package/node_modules/@engenty-wizards/shared/dist/connectors.d.ts +77 -0
- package/node_modules/@engenty-wizards/shared/dist/connectors.js +2 -0
- package/node_modules/@engenty-wizards/shared/dist/definition.d.ts +1415 -0
- package/node_modules/@engenty-wizards/shared/dist/definition.js +762 -0
- package/node_modules/@engenty-wizards/shared/dist/engenty/data-tables/columns-format.d.ts +5 -0
- package/node_modules/@engenty-wizards/shared/dist/engenty/data-tables/columns-format.js +132 -0
- package/node_modules/@engenty-wizards/shared/dist/engenty/data-tables/columns-value.d.ts +11 -0
- package/node_modules/@engenty-wizards/shared/dist/engenty/data-tables/columns-value.js +209 -0
- package/node_modules/@engenty-wizards/shared/dist/engenty/data-tables/columns.d.ts +532 -0
- package/node_modules/@engenty-wizards/shared/dist/engenty/data-tables/columns.js +216 -0
- package/node_modules/@engenty-wizards/shared/dist/engenty/data-tables/index.d.ts +3 -0
- package/node_modules/@engenty-wizards/shared/dist/engenty/data-tables/index.js +3 -0
- package/node_modules/@engenty-wizards/shared/dist/engenty/search-index/catalog-lexical.d.ts +14 -0
- package/node_modules/@engenty-wizards/shared/dist/engenty/search-index/catalog-lexical.js +166 -0
- package/node_modules/@engenty-wizards/shared/dist/engenty/search-index/catalog-rank.d.ts +33 -0
- package/node_modules/@engenty-wizards/shared/dist/engenty/search-index/catalog-rank.js +99 -0
- package/node_modules/@engenty-wizards/shared/dist/marketplace-entry.d.ts +35 -0
- package/node_modules/@engenty-wizards/shared/dist/marketplace-entry.js +86 -0
- package/node_modules/@engenty-wizards/shared/dist/marketplace-search.d.ts +20 -0
- package/node_modules/@engenty-wizards/shared/dist/marketplace-search.js +133 -0
- package/node_modules/@engenty-wizards/shared/dist/marketplace.d.ts +298 -0
- package/node_modules/@engenty-wizards/shared/dist/marketplace.js +211 -0
- package/node_modules/@engenty-wizards/shared/dist/merge.d.ts +7 -0
- package/node_modules/@engenty-wizards/shared/dist/merge.js +40 -0
- package/node_modules/@engenty-wizards/shared/dist/projects.d.ts +74 -0
- package/node_modules/@engenty-wizards/shared/dist/projects.js +96 -0
- package/node_modules/@engenty-wizards/shared/dist/run.d.ts +219 -0
- package/node_modules/@engenty-wizards/shared/dist/run.js +1 -0
- package/node_modules/@engenty-wizards/shared/dist/store.d.ts +100 -0
- package/node_modules/@engenty-wizards/shared/dist/store.js +44 -0
- package/node_modules/@engenty-wizards/shared/dist/workspace.d.ts +16 -0
- package/node_modules/@engenty-wizards/shared/dist/workspace.js +51 -0
- package/node_modules/@engenty-wizards/shared/package.json +10 -0
- package/package.json +62 -0
- package/plugin/.claude-plugin/plugin.json +9 -0
- package/plugin/.mcp.json +8 -0
- package/plugin/commands/wizard.md +8 -0
- package/plugin/skills/build-wizard/SKILL.md +27 -0
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
import { FACT_TYPES, PROJECT_LIMITS } from "@engenty-wizards/shared/projects";
|
|
2
|
+
import { and, count, eq } from "drizzle-orm";
|
|
3
|
+
import { nanoid } from "nanoid";
|
|
4
|
+
import { z } from "zod";
|
|
5
|
+
import { db, schema } from "../db/client.js";
|
|
6
|
+
import { env } from "../env.js";
|
|
7
|
+
import { managed, tenantInfo } from "../manage.js";
|
|
8
|
+
import { currentTenant } from "../tenants/tenant.js";
|
|
9
|
+
import { notFound, ServiceError } from "./errors.js";
|
|
10
|
+
import { forgetWizardLinks } from "./links.js";
|
|
11
|
+
import { removeProjectFiles } from "./project-files.js";
|
|
12
|
+
const MASK = "••••";
|
|
13
|
+
export const mcpServerSchema = z.object({
|
|
14
|
+
id: z.string().regex(/^[a-z][a-z0-9_]{0,30}$/),
|
|
15
|
+
name: z.string().min(1).max(60),
|
|
16
|
+
url: z.string().url(),
|
|
17
|
+
headers: z.record(z.string(), z.string()).optional(),
|
|
18
|
+
});
|
|
19
|
+
export const brandColorSchema = z.object({
|
|
20
|
+
name: z.string().max(120),
|
|
21
|
+
value: z.string().regex(/^#[0-9a-fA-F]{6}$/),
|
|
22
|
+
});
|
|
23
|
+
export const projectFactSchema = z.object({
|
|
24
|
+
key: z.string().regex(/^[a-z0-9_]{1,40}$/),
|
|
25
|
+
label: z.string().min(1).max(80),
|
|
26
|
+
value: z.string().max(4000),
|
|
27
|
+
type: z.enum(FACT_TYPES).optional(),
|
|
28
|
+
});
|
|
29
|
+
export const projectPatchSchema = z.object({
|
|
30
|
+
name: z.string().min(1).max(80).optional(),
|
|
31
|
+
brand: z
|
|
32
|
+
.object({
|
|
33
|
+
name: z.string().max(120).optional(),
|
|
34
|
+
about: z.string().max(8000).optional(),
|
|
35
|
+
colors: z.array(brandColorSchema).max(PROJECT_LIMITS.colors).optional(),
|
|
36
|
+
})
|
|
37
|
+
.optional(),
|
|
38
|
+
facts: z.array(projectFactSchema).max(PROJECT_LIMITS.facts).optional(),
|
|
39
|
+
mcpServers: z.array(mcpServerSchema).max(10).optional(),
|
|
40
|
+
});
|
|
41
|
+
/**
|
|
42
|
+
* How many projects the tenant works with: the number the Manage-App gives for it, else this
|
|
43
|
+
* runtime's (`LIMIT_PROJECTS`, one unless set). The studio shows a project switcher only above
|
|
44
|
+
* one. Projects beyond the number stay in the database and are not listed.
|
|
45
|
+
*/
|
|
46
|
+
export async function projectLimit() {
|
|
47
|
+
if (managed) {
|
|
48
|
+
const info = await tenantInfo(currentTenant()).catch(() => null);
|
|
49
|
+
if (info?.limits.projects) {
|
|
50
|
+
return info.limits.projects;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
return Math.max(1, env.limits.projects);
|
|
54
|
+
}
|
|
55
|
+
export async function ownedProject(_userId, projectId) {
|
|
56
|
+
const p = await db.query.project.findFirst({
|
|
57
|
+
where: and(eq(schema.project.id, projectId), eq(schema.project.tenantId, currentTenant())),
|
|
58
|
+
});
|
|
59
|
+
if (!p) {
|
|
60
|
+
throw notFound();
|
|
61
|
+
}
|
|
62
|
+
return p;
|
|
63
|
+
}
|
|
64
|
+
/** A tenant always has a project: the first one is made when its studio is first opened. */
|
|
65
|
+
async function ensureProject() {
|
|
66
|
+
const any = await db.query.project.findFirst({
|
|
67
|
+
where: eq(schema.project.tenantId, currentTenant()),
|
|
68
|
+
});
|
|
69
|
+
if (!any) {
|
|
70
|
+
await db
|
|
71
|
+
.insert(schema.project)
|
|
72
|
+
.values({ id: nanoid(12), tenantId: currentTenant(), name: "Meine Wizards" });
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
/** The project a wizard lands in when the caller names none: the oldest one. */
|
|
76
|
+
export async function defaultProject(_userId) {
|
|
77
|
+
await ensureProject();
|
|
78
|
+
const p = await db.query.project.findFirst({
|
|
79
|
+
where: eq(schema.project.tenantId, currentTenant()),
|
|
80
|
+
orderBy: [schema.project.createdAt],
|
|
81
|
+
});
|
|
82
|
+
if (!p) {
|
|
83
|
+
throw notFound();
|
|
84
|
+
}
|
|
85
|
+
return p;
|
|
86
|
+
}
|
|
87
|
+
export async function listProjects(_userId) {
|
|
88
|
+
await ensureProject();
|
|
89
|
+
// The oldest come first: with a limit of one, that is the project everything lands in.
|
|
90
|
+
const projects = await db.query.project.findMany({
|
|
91
|
+
where: eq(schema.project.tenantId, currentTenant()),
|
|
92
|
+
orderBy: [schema.project.createdAt],
|
|
93
|
+
limit: await projectLimit(),
|
|
94
|
+
});
|
|
95
|
+
const counts = await db
|
|
96
|
+
.select({ projectId: schema.wizard.projectId, n: count() })
|
|
97
|
+
.from(schema.wizard)
|
|
98
|
+
.where(eq(schema.wizard.tenantId, currentTenant()))
|
|
99
|
+
.groupBy(schema.wizard.projectId);
|
|
100
|
+
return projects.map((p) => ({
|
|
101
|
+
...p,
|
|
102
|
+
wizardCount: counts.find((x) => x.projectId === p.id)?.n ?? 0,
|
|
103
|
+
}));
|
|
104
|
+
}
|
|
105
|
+
/** MCP server headers carry the admin's credentials: shown masked, kept when sent back masked. */
|
|
106
|
+
export function maskedServers(p) {
|
|
107
|
+
return p.mcpServers.map((s) => ({
|
|
108
|
+
...s,
|
|
109
|
+
headers: s.headers
|
|
110
|
+
? Object.fromEntries(Object.keys(s.headers).map((k) => [k, MASK]))
|
|
111
|
+
: undefined,
|
|
112
|
+
}));
|
|
113
|
+
}
|
|
114
|
+
export async function createProject(_userId, name) {
|
|
115
|
+
const [{ n }] = await db
|
|
116
|
+
.select({ n: count() })
|
|
117
|
+
.from(schema.project)
|
|
118
|
+
.where(eq(schema.project.tenantId, currentTenant()));
|
|
119
|
+
const limit = await projectLimit();
|
|
120
|
+
if (n >= limit) {
|
|
121
|
+
throw new ServiceError("refused", limit === 1 ? "Hier gibt es ein Projekt." : `Höchstens ${limit} Projekte.`);
|
|
122
|
+
}
|
|
123
|
+
const id = nanoid(12);
|
|
124
|
+
await db
|
|
125
|
+
.insert(schema.project)
|
|
126
|
+
.values({ id, tenantId: currentTenant(), name, brand: {}, mcpServers: [] });
|
|
127
|
+
return { id };
|
|
128
|
+
}
|
|
129
|
+
export async function updateProject(userId, projectId, patch) {
|
|
130
|
+
const p = await ownedProject(userId, projectId);
|
|
131
|
+
const mcpServers = patch.mcpServers?.map((s) => {
|
|
132
|
+
const before = p.mcpServers.find((x) => x.id === s.id);
|
|
133
|
+
const headers = s.headers
|
|
134
|
+
? Object.fromEntries(Object.entries(s.headers).map(([k, v]) => [
|
|
135
|
+
k,
|
|
136
|
+
v === MASK ? (before?.headers?.[k] ?? "") : v,
|
|
137
|
+
]))
|
|
138
|
+
: undefined;
|
|
139
|
+
return { ...s, headers };
|
|
140
|
+
});
|
|
141
|
+
if (patch.facts && new Set(patch.facts.map((f) => f.key)).size !== patch.facts.length) {
|
|
142
|
+
throw new ServiceError("invalid", "Zwei Einträge haben denselben Schlüssel.");
|
|
143
|
+
}
|
|
144
|
+
await db
|
|
145
|
+
.update(schema.project)
|
|
146
|
+
.set({
|
|
147
|
+
...(patch.name ? { name: patch.name } : {}),
|
|
148
|
+
...(patch.brand ? { brand: { ...p.brand, ...patch.brand } } : {}),
|
|
149
|
+
...(patch.facts ? { facts: patch.facts } : {}),
|
|
150
|
+
...(mcpServers ? { mcpServers } : {}),
|
|
151
|
+
updatedAt: new Date(),
|
|
152
|
+
})
|
|
153
|
+
.where(eq(schema.project.id, p.id));
|
|
154
|
+
}
|
|
155
|
+
export function projectProfile(p) {
|
|
156
|
+
return {
|
|
157
|
+
name: p.brand.name ?? "",
|
|
158
|
+
about: p.brand.about ?? "",
|
|
159
|
+
colors: p.brand.colors ?? [],
|
|
160
|
+
facts: p.facts.filter((f) => f.value.trim()),
|
|
161
|
+
};
|
|
162
|
+
}
|
|
163
|
+
export async function deleteProject(_userId, projectId) {
|
|
164
|
+
const all = await db.query.project.findMany({
|
|
165
|
+
where: eq(schema.project.tenantId, currentTenant()),
|
|
166
|
+
});
|
|
167
|
+
if (all.length <= 1) {
|
|
168
|
+
throw new ServiceError("refused", "Das letzte Projekt bleibt.");
|
|
169
|
+
}
|
|
170
|
+
const wizards = await db
|
|
171
|
+
.select({ id: schema.wizard.id })
|
|
172
|
+
.from(schema.wizard)
|
|
173
|
+
.where(eq(schema.wizard.projectId, projectId));
|
|
174
|
+
for (const w of wizards) {
|
|
175
|
+
await forgetWizardLinks(w.id);
|
|
176
|
+
}
|
|
177
|
+
await removeProjectFiles(projectId);
|
|
178
|
+
await db
|
|
179
|
+
.delete(schema.project)
|
|
180
|
+
.where(and(eq(schema.project.id, projectId), eq(schema.project.tenantId, currentTenant())));
|
|
181
|
+
}
|
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
import { formatsFor } from "@engenty-wizards/shared/definition";
|
|
2
|
+
import { and, count, desc, eq, inArray } from "drizzle-orm";
|
|
3
|
+
import { canSpend, MICROS_PER_CREDIT } from "../credits/credits.js";
|
|
4
|
+
import { db, schema } from "../db/client.js";
|
|
5
|
+
import { unattended } from "../engine/asks.js";
|
|
6
|
+
import { emitEvent, recentEvents, subscribe } from "../engine/events.js";
|
|
7
|
+
import { createRun, RunInputError, reviewStep, submitPage } from "../engine/runner.js";
|
|
8
|
+
import { ModelUnavailableError } from "../model-errors.js";
|
|
9
|
+
import { signedUrl } from "../secrets/signing.js";
|
|
10
|
+
import { currentTenant } from "../tenants/tenant.js";
|
|
11
|
+
import { notFound, ServiceError } from "./errors.js";
|
|
12
|
+
import { ownedWizard, requireClean, studioUrl } from "./wizards.js";
|
|
13
|
+
/** Test runs an MCP client started with answers: pages are filled and reviews accepted for it. */
|
|
14
|
+
const autopilots = new Set();
|
|
15
|
+
export async function startTestRun(userId, wizardId, autopilot) {
|
|
16
|
+
const w = await ownedWizard(userId, wizardId);
|
|
17
|
+
const { definition, files } = await requireClean(w);
|
|
18
|
+
if (!(await canSpend())) {
|
|
19
|
+
throw new ServiceError("no_credits", "Dein Guthaben ist aufgebraucht.");
|
|
20
|
+
}
|
|
21
|
+
const runId = await createRun({
|
|
22
|
+
wizardId: w.id,
|
|
23
|
+
definition,
|
|
24
|
+
files,
|
|
25
|
+
version: null,
|
|
26
|
+
mode: "test",
|
|
27
|
+
userId,
|
|
28
|
+
}).catch((err) => {
|
|
29
|
+
throw err instanceof ModelUnavailableError ? new ServiceError("refused", err.message) : err;
|
|
30
|
+
});
|
|
31
|
+
if (autopilot) {
|
|
32
|
+
fly(runId, autopilot.answers, autopilot.acceptReviews);
|
|
33
|
+
}
|
|
34
|
+
return { runId };
|
|
35
|
+
}
|
|
36
|
+
function fly(runId, answers, acceptReviews) {
|
|
37
|
+
autopilots.add(runId);
|
|
38
|
+
unattended.add(runId);
|
|
39
|
+
let busy = false;
|
|
40
|
+
let again = false;
|
|
41
|
+
const land = () => {
|
|
42
|
+
autopilots.delete(runId);
|
|
43
|
+
unattended.delete(runId);
|
|
44
|
+
unsubscribe();
|
|
45
|
+
clearTimeout(timeout);
|
|
46
|
+
};
|
|
47
|
+
const step = async () => {
|
|
48
|
+
if (busy) {
|
|
49
|
+
again = true;
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
busy = true;
|
|
53
|
+
try {
|
|
54
|
+
do {
|
|
55
|
+
again = false;
|
|
56
|
+
const run = await db.query.run.findFirst({ where: eq(schema.run.id, runId) });
|
|
57
|
+
if (!run ||
|
|
58
|
+
run.status === "done" ||
|
|
59
|
+
run.status === "failed" ||
|
|
60
|
+
run.status === "cancelled") {
|
|
61
|
+
land();
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
if (run.status !== "waiting_input") {
|
|
65
|
+
continue;
|
|
66
|
+
}
|
|
67
|
+
const current = run.definition.steps.find((s) => s.id === run.cursor);
|
|
68
|
+
if (current?.type === "page") {
|
|
69
|
+
const values = Object.fromEntries(current.fields.filter((f) => f.id in answers).map((f) => [f.id, answers[f.id]]));
|
|
70
|
+
await submitPage(runId, current.id, values);
|
|
71
|
+
}
|
|
72
|
+
else if (current?.type === "review" && acceptReviews) {
|
|
73
|
+
await reviewStep(runId, current.id, { type: "accept" });
|
|
74
|
+
}
|
|
75
|
+
else {
|
|
76
|
+
land();
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
} while (again);
|
|
80
|
+
}
|
|
81
|
+
catch (err) {
|
|
82
|
+
const message = err instanceof RunInputError
|
|
83
|
+
? `Antworten fehlen oder passen nicht: ${err.errors.map((e) => `${e.field} – ${e.message}`).join("; ")}`
|
|
84
|
+
: err.message;
|
|
85
|
+
await emitEvent(runId, null, "error", message).catch(() => undefined);
|
|
86
|
+
land();
|
|
87
|
+
}
|
|
88
|
+
finally {
|
|
89
|
+
busy = false;
|
|
90
|
+
}
|
|
91
|
+
};
|
|
92
|
+
const unsubscribe = subscribe(runId, () => {
|
|
93
|
+
void step();
|
|
94
|
+
});
|
|
95
|
+
const timeout = setTimeout(land, 30 * 60_000);
|
|
96
|
+
void step();
|
|
97
|
+
}
|
|
98
|
+
async function ownedTestRun(_userId, runId) {
|
|
99
|
+
const run = await db.query.run.findFirst({
|
|
100
|
+
where: and(eq(schema.run.id, runId), eq(schema.run.tenantId, currentTenant()), eq(schema.run.mode, "test")),
|
|
101
|
+
});
|
|
102
|
+
if (!run) {
|
|
103
|
+
throw notFound();
|
|
104
|
+
}
|
|
105
|
+
return run;
|
|
106
|
+
}
|
|
107
|
+
/** Still moving: running, or waiting on a page the autopilot is about to fill. */
|
|
108
|
+
function moving(run) {
|
|
109
|
+
return run.status === "running" || (run.status === "waiting_input" && autopilots.has(run.id));
|
|
110
|
+
}
|
|
111
|
+
async function waitWhileMoving(userId, runId, seconds) {
|
|
112
|
+
const deadline = Date.now() + seconds * 1000;
|
|
113
|
+
let run = await ownedTestRun(userId, runId);
|
|
114
|
+
while (moving(run) && Date.now() < deadline) {
|
|
115
|
+
await new Promise((resolve) => {
|
|
116
|
+
const unsubscribe = subscribe(runId, () => {
|
|
117
|
+
unsubscribe();
|
|
118
|
+
clearTimeout(timer);
|
|
119
|
+
resolve();
|
|
120
|
+
});
|
|
121
|
+
const timer = setTimeout(() => {
|
|
122
|
+
unsubscribe();
|
|
123
|
+
resolve();
|
|
124
|
+
}, deadline - Date.now());
|
|
125
|
+
});
|
|
126
|
+
run = await ownedTestRun(userId, runId);
|
|
127
|
+
}
|
|
128
|
+
return run;
|
|
129
|
+
}
|
|
130
|
+
function clip(text, max) {
|
|
131
|
+
return text.length > max
|
|
132
|
+
? `${text.slice(0, max)} … [${text.length - max} more characters]`
|
|
133
|
+
: text;
|
|
134
|
+
}
|
|
135
|
+
function deliverableFormats(run, step) {
|
|
136
|
+
const possible = formatsFor(step);
|
|
137
|
+
const listed = run.definition.steps
|
|
138
|
+
.flatMap((s) => (s.type === "result" ? s.deliverables : []))
|
|
139
|
+
.find((d) => d.from === step.id);
|
|
140
|
+
return listed ? listed.formats.filter((f) => possible.includes(f)) : [];
|
|
141
|
+
}
|
|
142
|
+
/** What a test run did so far, for a client without the studio: outputs, links, cost, errors. */
|
|
143
|
+
export async function testRunReport(userId, runId, waitSeconds = 0) {
|
|
144
|
+
const run = await waitWhileMoving(userId, runId, waitSeconds);
|
|
145
|
+
const current = run.definition.steps.find((s) => s.id === run.cursor) ?? null;
|
|
146
|
+
const base = `/api/runs/${run.id}`;
|
|
147
|
+
const outputs = run.definition.steps.flatMap((step) => {
|
|
148
|
+
const output = run.state.outputs[step.id];
|
|
149
|
+
if (!output) {
|
|
150
|
+
return [];
|
|
151
|
+
}
|
|
152
|
+
return [
|
|
153
|
+
{
|
|
154
|
+
stepId: step.id,
|
|
155
|
+
title: step.title,
|
|
156
|
+
text: output.text ? clip(output.text, 1500) : undefined,
|
|
157
|
+
json: output.json === undefined ? undefined : clip(JSON.stringify(output.json), 1500),
|
|
158
|
+
assets: (output.assets ?? []).map((a) => ({
|
|
159
|
+
name: a.name,
|
|
160
|
+
mime: a.mime,
|
|
161
|
+
url: signedUrl(`${base}/assets/${a.id}`),
|
|
162
|
+
})),
|
|
163
|
+
downloads: Object.fromEntries(deliverableFormats(run, step).map((f) => [
|
|
164
|
+
f,
|
|
165
|
+
signedUrl(`${base}/steps/${step.id}/download?format=${f}`),
|
|
166
|
+
])),
|
|
167
|
+
},
|
|
168
|
+
];
|
|
169
|
+
});
|
|
170
|
+
const waitingFor = run.status === "waiting_input" && current?.type === "page"
|
|
171
|
+
? {
|
|
172
|
+
page: current.id,
|
|
173
|
+
fields: current.fields.map((f) => ({
|
|
174
|
+
id: f.id,
|
|
175
|
+
label: f.label,
|
|
176
|
+
kind: f.kind,
|
|
177
|
+
required: Boolean(f.required),
|
|
178
|
+
options: f.options,
|
|
179
|
+
})),
|
|
180
|
+
}
|
|
181
|
+
: run.status === "waiting_input" && current?.type === "review"
|
|
182
|
+
? { review: current.id, hint: "Accept or regenerate in the studio." }
|
|
183
|
+
: undefined;
|
|
184
|
+
return {
|
|
185
|
+
runId: run.id,
|
|
186
|
+
status: moving(run) ? "running" : run.status,
|
|
187
|
+
step: current ? { id: current.id, type: current.type, title: current.title } : null,
|
|
188
|
+
waitingFor,
|
|
189
|
+
error: run.error,
|
|
190
|
+
credits: Math.ceil(run.costMicros / MICROS_PER_CREDIT),
|
|
191
|
+
events: (await recentEvents(run.id, 0, 15)).map((e) => ({
|
|
192
|
+
step: e.stepId,
|
|
193
|
+
type: e.type,
|
|
194
|
+
message: e.message,
|
|
195
|
+
})),
|
|
196
|
+
outputs,
|
|
197
|
+
studioUrl: studioUrl(run.wizardId),
|
|
198
|
+
};
|
|
199
|
+
}
|
|
200
|
+
export async function listRuns(userId, wizardId) {
|
|
201
|
+
const w = await ownedWizard(userId, wizardId);
|
|
202
|
+
const rows = await db.query.run.findMany({
|
|
203
|
+
where: eq(schema.run.wizardId, w.id),
|
|
204
|
+
orderBy: [desc(schema.run.createdAt)],
|
|
205
|
+
limit: 100,
|
|
206
|
+
});
|
|
207
|
+
const assetCounts = rows.length
|
|
208
|
+
? await db
|
|
209
|
+
.select({ runId: schema.asset.runId, n: count() })
|
|
210
|
+
.from(schema.asset)
|
|
211
|
+
.where(inArray(schema.asset.runId, rows.map((r) => r.id)))
|
|
212
|
+
.groupBy(schema.asset.runId)
|
|
213
|
+
: [];
|
|
214
|
+
return rows.map((r) => ({
|
|
215
|
+
id: r.id,
|
|
216
|
+
mode: r.mode,
|
|
217
|
+
status: r.status,
|
|
218
|
+
version: r.version,
|
|
219
|
+
createdAt: r.createdAt.toISOString(),
|
|
220
|
+
credits: Math.ceil(r.costMicros / MICROS_PER_CREDIT),
|
|
221
|
+
assets: assetCounts.find((a) => a.runId === r.id)?.n ?? 0,
|
|
222
|
+
stepTitle: r.definition.steps.find((s) => s.id === r.cursor)?.title ?? null,
|
|
223
|
+
}));
|
|
224
|
+
}
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
import { and, eq, isNotNull, lt } from "drizzle-orm";
|
|
2
|
+
import { nanoid } from "nanoid";
|
|
3
|
+
import { db, schema } from "../db/client.js";
|
|
4
|
+
import { availableFormats } from "../engine/runner.js";
|
|
5
|
+
import { env } from "../env.js";
|
|
6
|
+
import { inlineAssetRefs, loadAsset, loadAssetText, removeAssetFiles, saveAsset, } from "../files/storage.js";
|
|
7
|
+
import { htmlToPng } from "../render/convert.js";
|
|
8
|
+
import { dropLinks, dropRuns, putLink } from "../tenants/control.js";
|
|
9
|
+
import { brandView } from "./brand.js";
|
|
10
|
+
import { ServiceError } from "./errors.js";
|
|
11
|
+
export const shareUrlFor = (token) => `${env.appUrl}/s/${token}`;
|
|
12
|
+
/** Shares a finished run. Idempotent: the same run keeps the same link. */
|
|
13
|
+
export async function shareRun(run) {
|
|
14
|
+
if (run.status !== "done") {
|
|
15
|
+
throw new ServiceError("refused", "Nur fertige Ergebnisse lassen sich teilen.");
|
|
16
|
+
}
|
|
17
|
+
let token = run.shareToken;
|
|
18
|
+
if (!token) {
|
|
19
|
+
token = nanoid(16);
|
|
20
|
+
await db
|
|
21
|
+
.update(schema.run)
|
|
22
|
+
.set({ shareToken: token, sharedAt: new Date() })
|
|
23
|
+
.where(eq(schema.run.id, run.id));
|
|
24
|
+
await putLink(token, "result", run.id);
|
|
25
|
+
}
|
|
26
|
+
return { url: shareUrlFor(token), expiresAt: run.expiresAt?.toISOString() ?? null };
|
|
27
|
+
}
|
|
28
|
+
export async function unshareRun(run) {
|
|
29
|
+
if (run.shareToken) {
|
|
30
|
+
await dropLinks([run.shareToken]);
|
|
31
|
+
}
|
|
32
|
+
await db
|
|
33
|
+
.update(schema.run)
|
|
34
|
+
.set({ shareToken: null, sharedAt: null })
|
|
35
|
+
.where(eq(schema.run.id, run.id));
|
|
36
|
+
}
|
|
37
|
+
/** The shared run behind a token, unless the link was withdrawn or the run expired. */
|
|
38
|
+
export async function sharedRun(token) {
|
|
39
|
+
const run = await db.query.run.findFirst({ where: eq(schema.run.shareToken, token) });
|
|
40
|
+
if (run?.status !== "done" || (run.expiresAt && run.expiresAt < new Date())) {
|
|
41
|
+
return null;
|
|
42
|
+
}
|
|
43
|
+
return run;
|
|
44
|
+
}
|
|
45
|
+
export async function shareView(run) {
|
|
46
|
+
const def = run.definition;
|
|
47
|
+
const result = def.steps.find((s) => s.id === run.cursor && s.type === "result") ??
|
|
48
|
+
def.steps.findLast((s) => s.type === "result");
|
|
49
|
+
const deliverables = result?.type === "result" ? result.deliverables : [];
|
|
50
|
+
const shown = await Promise.all(deliverables.flatMap((d) => {
|
|
51
|
+
const step = def.steps.find((s) => s.id === d.from);
|
|
52
|
+
const output = run.state.outputs[d.from];
|
|
53
|
+
return step && output
|
|
54
|
+
? [
|
|
55
|
+
(async () => ({
|
|
56
|
+
step,
|
|
57
|
+
output,
|
|
58
|
+
formats: await availableFormats(step, run, d.formats),
|
|
59
|
+
label: d.label ?? null,
|
|
60
|
+
}))(),
|
|
61
|
+
]
|
|
62
|
+
: [];
|
|
63
|
+
}));
|
|
64
|
+
const w = await db.query.wizard.findFirst({ where: eq(schema.wizard.id, run.wizardId) });
|
|
65
|
+
return {
|
|
66
|
+
token: run.shareToken ?? "",
|
|
67
|
+
title: result?.title ?? def.title,
|
|
68
|
+
message: result?.type === "result" ? (result.message ?? null) : null,
|
|
69
|
+
wizard: { title: def.title, description: def.description, avatar: def.avatar },
|
|
70
|
+
brand: await brandView(w?.projectId ?? ""),
|
|
71
|
+
shown,
|
|
72
|
+
createdAt: run.createdAt.toISOString(),
|
|
73
|
+
expiresAt: run.expiresAt?.toISOString() ?? null,
|
|
74
|
+
wizardUrl: w?.shareEnabled && w.publishedVersion !== null ? `${env.appUrl}/w/${w.shareToken}` : null,
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* The picture link previews (X, LinkedIn, WhatsApp, Slack) show: the first image of the result,
|
|
79
|
+
* a widget's poster, or a document's first screen. Rendered once and kept.
|
|
80
|
+
*/
|
|
81
|
+
export async function shareImage(run) {
|
|
82
|
+
const view = await shareView(run);
|
|
83
|
+
for (const { output } of view.shown) {
|
|
84
|
+
const assets = output?.assets ?? [];
|
|
85
|
+
const image = assets.find((a) => a.mime.startsWith("image/"));
|
|
86
|
+
if (image) {
|
|
87
|
+
const found = await loadAsset(image.id);
|
|
88
|
+
if (found) {
|
|
89
|
+
return { data: new Uint8Array(found.data), mime: found.row.mime };
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
const html = assets.find((a) => a.mime === "text/html");
|
|
93
|
+
if (html) {
|
|
94
|
+
const name = `${html.id}.og.png`;
|
|
95
|
+
const hit = await db.query.asset.findFirst({
|
|
96
|
+
where: and(eq(schema.asset.kind, "render"), eq(schema.asset.name, name)),
|
|
97
|
+
});
|
|
98
|
+
const cached = hit ? await loadAsset(hit.id) : null;
|
|
99
|
+
if (cached) {
|
|
100
|
+
return { data: new Uint8Array(cached.data), mime: "image/png" };
|
|
101
|
+
}
|
|
102
|
+
const page = await inlineAssetRefs(await loadAssetText(html.id));
|
|
103
|
+
const png = await htmlToPng(page, 1200, { height: 630, fullPage: false, scale: 1 });
|
|
104
|
+
await saveAsset({
|
|
105
|
+
runId: run.id,
|
|
106
|
+
kind: "render",
|
|
107
|
+
mime: "image/png",
|
|
108
|
+
name,
|
|
109
|
+
data: png,
|
|
110
|
+
});
|
|
111
|
+
return { data: png, mime: "image/png" };
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
return null;
|
|
115
|
+
}
|
|
116
|
+
/** Deletes runs past their expiry with everything they made. Runs hourly. */
|
|
117
|
+
export async function purgeExpiredRuns() {
|
|
118
|
+
const expired = await db
|
|
119
|
+
.select({ id: schema.run.id, shareToken: schema.run.shareToken })
|
|
120
|
+
.from(schema.run)
|
|
121
|
+
.where(and(isNotNull(schema.run.expiresAt), lt(schema.run.expiresAt, new Date())));
|
|
122
|
+
for (const { id } of expired) {
|
|
123
|
+
await removeAssetFiles(id);
|
|
124
|
+
await db.delete(schema.run).where(eq(schema.run.id, id));
|
|
125
|
+
}
|
|
126
|
+
await dropLinks(expired.map((r) => r.shareToken).filter((t) => Boolean(t)));
|
|
127
|
+
await dropRuns(expired.map((r) => r.id));
|
|
128
|
+
return expired.length;
|
|
129
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { widgetSize } from "@engenty-wizards/shared/definition";
|
|
2
|
+
import { probeWidget } from "../widgets/render.js";
|
|
3
|
+
import { widgetPreviewHtml } from "../widgets/step.js";
|
|
4
|
+
import { notFound, ServiceError } from "./errors.js";
|
|
5
|
+
import { draftFiles } from "./files.js";
|
|
6
|
+
import { ownedProject } from "./projects.js";
|
|
7
|
+
import { ownedWizard } from "./wizards.js";
|
|
8
|
+
async function draftWidget(userId, wizardId, stepId) {
|
|
9
|
+
const w = await ownedWizard(userId, wizardId);
|
|
10
|
+
const step = w.draft.steps.find((s) => s.id === stepId);
|
|
11
|
+
if (!step) {
|
|
12
|
+
throw notFound();
|
|
13
|
+
}
|
|
14
|
+
if (step.type !== "widget") {
|
|
15
|
+
throw new ServiceError("invalid", `Step "${stepId}" is not a widget.`);
|
|
16
|
+
}
|
|
17
|
+
const files = await draftFiles(w.id);
|
|
18
|
+
if (!files.some((f) => f.path === step.entry)) {
|
|
19
|
+
throw new ServiceError("invalid", `The widget file ${step.entry} is not in the workspace yet.`);
|
|
20
|
+
}
|
|
21
|
+
return { step, files, project: await ownedProject(userId, w.projectId) };
|
|
22
|
+
}
|
|
23
|
+
/** The draft widget with its sample data, as one HTML file — the studio shows it before any run. */
|
|
24
|
+
export async function draftWidgetPreview(userId, wizardId, stepId, data) {
|
|
25
|
+
const { step, files, project } = await draftWidget(userId, wizardId, stepId);
|
|
26
|
+
return widgetPreviewHtml(step, files, project, data);
|
|
27
|
+
}
|
|
28
|
+
/** Loads the draft widget in Chromium: what broke, whether it animates, and how it looks. */
|
|
29
|
+
export async function checkDraftWidget(userId, wizardId, stepId, data) {
|
|
30
|
+
const { step, files, project } = await draftWidget(userId, wizardId, stepId);
|
|
31
|
+
const html = await widgetPreviewHtml(step, files, project, data);
|
|
32
|
+
const probe = await probeWidget(html, widgetSize(step));
|
|
33
|
+
return { ...probe, size: widgetSize(step), bytes: Buffer.byteLength(html) };
|
|
34
|
+
}
|