@vendoai/vendo 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +202 -0
- package/README.md +28 -0
- package/bin/vendo.mjs +4 -0
- package/dist/ai-sdk.d.ts +35 -0
- package/dist/ai-sdk.js +62 -0
- package/dist/auth-presets/auth-js.d.ts +14 -0
- package/dist/auth-presets/auth-js.js +91 -0
- package/dist/auth-presets/auth0.d.ts +23 -0
- package/dist/auth-presets/auth0.js +98 -0
- package/dist/auth-presets/clerk.d.ts +23 -0
- package/dist/auth-presets/clerk.js +65 -0
- package/dist/auth-presets/conformance.d.ts +42 -0
- package/dist/auth-presets/conformance.js +139 -0
- package/dist/auth-presets/identity.d.ts +56 -0
- package/dist/auth-presets/identity.js +152 -0
- package/dist/auth-presets/index.d.ts +14 -0
- package/dist/auth-presets/index.js +12 -0
- package/dist/auth-presets/jwt.d.ts +16 -0
- package/dist/auth-presets/jwt.js +54 -0
- package/dist/auth-presets/shared.d.ts +35 -0
- package/dist/auth-presets/shared.js +1 -0
- package/dist/auth-presets/supabase.d.ts +42 -0
- package/dist/auth-presets/supabase.js +229 -0
- package/dist/byo-approvals.d.ts +37 -0
- package/dist/byo-approvals.js +189 -0
- package/dist/capability-misses.d.ts +38 -0
- package/dist/capability-misses.js +172 -0
- package/dist/catalog.d.ts +19 -0
- package/dist/catalog.js +152 -0
- package/dist/cli/cloud/args.d.ts +3 -0
- package/dist/cli/cloud/args.js +32 -0
- package/dist/cli/cloud/auth.d.ts +17 -0
- package/dist/cli/cloud/auth.js +123 -0
- package/dist/cli/cloud/client.d.ts +28 -0
- package/dist/cli/cloud/client.js +133 -0
- package/dist/cli/cloud/command.d.ts +22 -0
- package/dist/cli/cloud/command.js +73 -0
- package/dist/cli/cloud/deploy.d.ts +26 -0
- package/dist/cli/cloud/deploy.js +187 -0
- package/dist/cli/cloud/device-login.d.ts +32 -0
- package/dist/cli/cloud/device-login.js +259 -0
- package/dist/cli/cloud/index.d.ts +5 -0
- package/dist/cli/cloud/index.js +59 -0
- package/dist/cli/cloud/keys.d.ts +2 -0
- package/dist/cli/cloud/keys.js +31 -0
- package/dist/cli/cloud/members.d.ts +3 -0
- package/dist/cli/cloud/members.js +25 -0
- package/dist/cli/cloud/output.d.ts +4 -0
- package/dist/cli/cloud/output.js +12 -0
- package/dist/cli/cloud/pending-claim.d.ts +29 -0
- package/dist/cli/cloud/pending-claim.js +38 -0
- package/dist/cli/cloud/read.d.ts +3 -0
- package/dist/cli/cloud/read.js +12 -0
- package/dist/cli/cloud/services.d.ts +2 -0
- package/dist/cli/cloud/services.js +9 -0
- package/dist/cli/cloud/session.d.ts +13 -0
- package/dist/cli/cloud/session.js +41 -0
- package/dist/cli/cloud-init.d.ts +60 -0
- package/dist/cli/cloud-init.js +149 -0
- package/dist/cli/dep-versions.d.ts +26 -0
- package/dist/cli/dep-versions.js +77 -0
- package/dist/cli/doctor-codes.d.ts +58 -0
- package/dist/cli/doctor-codes.js +60 -0
- package/dist/cli/doctor-live.d.ts +67 -0
- package/dist/cli/doctor-live.js +169 -0
- package/dist/cli/doctor.d.ts +52 -0
- package/dist/cli/doctor.js +467 -0
- package/dist/cli/eject.d.ts +29 -0
- package/dist/cli/eject.js +141 -0
- package/dist/cli/extract/apply.d.ts +26 -0
- package/dist/cli/extract/apply.js +123 -0
- package/dist/cli/extract/claude-cli-harness.d.ts +18 -0
- package/dist/cli/extract/claude-cli-harness.js +132 -0
- package/dist/cli/extract/claude-harness.d.ts +14 -0
- package/dist/cli/extract/claude-harness.js +127 -0
- package/dist/cli/extract/codex-cli-harness.d.ts +31 -0
- package/dist/cli/extract/codex-cli-harness.js +154 -0
- package/dist/cli/extract/delegate.d.ts +21 -0
- package/dist/cli/extract/delegate.js +77 -0
- package/dist/cli/extract/extraction.d.ts +65 -0
- package/dist/cli/extract/extraction.js +300 -0
- package/dist/cli/extract/gateway-fuel.d.ts +69 -0
- package/dist/cli/extract/gateway-fuel.js +69 -0
- package/dist/cli/extract/harness.d.ts +114 -0
- package/dist/cli/extract/harness.js +84 -0
- package/dist/cli/extract/index.d.ts +9 -0
- package/dist/cli/extract/index.js +9 -0
- package/dist/cli/extract/npx-engine-harness.d.ts +73 -0
- package/dist/cli/extract/npx-engine-harness.js +225 -0
- package/dist/cli/extract/stages.d.ts +149 -0
- package/dist/cli/extract/stages.js +398 -0
- package/dist/cli/framework.d.ts +8 -0
- package/dist/cli/framework.js +36 -0
- package/dist/cli/init-auth.d.ts +67 -0
- package/dist/cli/init-auth.js +142 -0
- package/dist/cli/init-scaffolds.d.ts +28 -0
- package/dist/cli/init-scaffolds.js +235 -0
- package/dist/cli/init.d.ts +115 -0
- package/dist/cli/init.js +1008 -0
- package/dist/cli/mcp/index.d.ts +7 -0
- package/dist/cli/mcp/index.js +59 -0
- package/dist/cli/mcp/registry.d.ts +9 -0
- package/dist/cli/mcp/registry.js +117 -0
- package/dist/cli/mcp/server-json.d.ts +12 -0
- package/dist/cli/mcp/server-json.js +65 -0
- package/dist/cli/mcp/server.schema.json +574 -0
- package/dist/cli/mcp/verify-domain.d.ts +11 -0
- package/dist/cli/mcp/verify-domain.js +48 -0
- package/dist/cli/playground/app/embed-entry.d.ts +20 -0
- package/dist/cli/playground/app/embed-entry.js +46 -0
- package/dist/cli/playground/app/fake-client.d.ts +3 -0
- package/dist/cli/playground/app/fake-client.js +215 -0
- package/dist/cli/playground/app/fixtures.d.ts +47 -0
- package/dist/cli/playground/app/fixtures.js +472 -0
- package/dist/cli/playground/app/main.d.ts +1 -0
- package/dist/cli/playground/app/main.js +108 -0
- package/dist/cli/playground/app/scenario-mount.d.ts +13 -0
- package/dist/cli/playground/app/scenario-mount.js +48 -0
- package/dist/cli/playground/app/scenarios.d.ts +25 -0
- package/dist/cli/playground/app/scenarios.js +152 -0
- package/dist/cli/playground/app/theme-editor.d.ts +13 -0
- package/dist/cli/playground/app/theme-editor.js +149 -0
- package/dist/cli/playground/app/theme-state.d.ts +29 -0
- package/dist/cli/playground/app/theme-state.js +151 -0
- package/dist/cli/playground/bundle.gen.d.ts +2 -0
- package/dist/cli/playground/bundle.gen.js +2 -0
- package/dist/cli/playground/embed-bundle.gen.d.ts +2 -0
- package/dist/cli/playground/embed-bundle.gen.js +2 -0
- package/dist/cli/playground.d.ts +33 -0
- package/dist/cli/playground.js +120 -0
- package/dist/cli/pretty.d.ts +48 -0
- package/dist/cli/pretty.js +330 -0
- package/dist/cli/refine.d.ts +43 -0
- package/dist/cli/refine.js +181 -0
- package/dist/cli/semantics.d.ts +31 -0
- package/dist/cli/semantics.js +122 -0
- package/dist/cli/shared.d.ts +68 -0
- package/dist/cli/shared.js +184 -0
- package/dist/cli/sync.d.ts +36 -0
- package/dist/cli/sync.js +202 -0
- package/dist/cli/theme/color.d.ts +15 -0
- package/dist/cli/theme/color.js +188 -0
- package/dist/cli/theme/css-vars.d.ts +13 -0
- package/dist/cli/theme/css-vars.js +40 -0
- package/dist/cli/theme/entry-candidates.d.ts +5 -0
- package/dist/cli/theme/entry-candidates.js +22 -0
- package/dist/cli/theme/extract-theme.d.ts +184 -0
- package/dist/cli/theme/extract-theme.js +358 -0
- package/dist/cli/theme/walk.d.ts +2 -0
- package/dist/cli/theme/walk.js +24 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +333 -0
- package/dist/cloud-apps.d.ts +17 -0
- package/dist/cloud-apps.js +46 -0
- package/dist/cloud-console.d.ts +32 -0
- package/dist/cloud-console.js +72 -0
- package/dist/cloud-tools.d.ts +23 -0
- package/dist/cloud-tools.js +196 -0
- package/dist/connections.d.ts +71 -0
- package/dist/connections.js +192 -0
- package/dist/deployment-identity.d.ts +12 -0
- package/dist/deployment-identity.js +69 -0
- package/dist/dev-creds/model.d.ts +65 -0
- package/dist/dev-creds/model.js +159 -0
- package/dist/dev-creds/resolve.d.ts +36 -0
- package/dist/dev-creds/resolve.js +57 -0
- package/dist/hosted-store.d.ts +52 -0
- package/dist/hosted-store.js +322 -0
- package/dist/index.d.ts +13 -0
- package/dist/index.js +1 -0
- package/dist/mastra.d.ts +46 -0
- package/dist/mastra.js +88 -0
- package/dist/react.d.ts +10 -0
- package/dist/react.js +32 -0
- package/dist/refine.d.ts +244 -0
- package/dist/refine.js +599 -0
- package/dist/remixable.d.ts +18 -0
- package/dist/remixable.js +42 -0
- package/dist/runtime-capture.d.ts +18 -0
- package/dist/runtime-capture.js +111 -0
- package/dist/sandbox-wire.d.ts +101 -0
- package/dist/sandbox-wire.js +101 -0
- package/dist/sandbox.d.ts +38 -0
- package/dist/sandbox.js +389 -0
- package/dist/server.d.ts +216 -0
- package/dist/server.js +1320 -0
- package/dist/sync-impact.d.ts +14 -0
- package/dist/sync-impact.js +75 -0
- package/dist/turn-liveness.d.ts +33 -0
- package/dist/turn-liveness.js +105 -0
- package/dist/wire/approvals.d.ts +10 -0
- package/dist/wire/approvals.js +59 -0
- package/dist/wire/apps.d.ts +5 -0
- package/dist/wire/apps.js +155 -0
- package/dist/wire/automations.d.ts +5 -0
- package/dist/wire/automations.js +57 -0
- package/dist/wire/box.d.ts +3 -0
- package/dist/wire/box.js +268 -0
- package/dist/wire/connections.d.ts +5 -0
- package/dist/wire/connections.js +47 -0
- package/dist/wire/context.d.ts +22 -0
- package/dist/wire/context.js +190 -0
- package/dist/wire/doctor.d.ts +19 -0
- package/dist/wire/doctor.js +116 -0
- package/dist/wire/misc.d.ts +18 -0
- package/dist/wire/misc.js +227 -0
- package/dist/wire/shared.d.ts +158 -0
- package/dist/wire/shared.js +134 -0
- package/dist/wire/threads.d.ts +3 -0
- package/dist/wire/threads.js +68 -0
- package/package.json +151 -0
- package/skills/vendo-setup/SKILL.md +128 -0
|
@@ -0,0 +1,472 @@
|
|
|
1
|
+
const PRINCIPAL = { kind: "user", subject: "user_playground", display: "Avery Quinn" };
|
|
2
|
+
/** Friendly labels so build beats speak like a host would make them speak. */
|
|
3
|
+
export const playgroundToolMeta = {
|
|
4
|
+
host_listRenewals: { label: "Reading upcoming renewals" },
|
|
5
|
+
host_listAccounts: { label: "Reading your accounts" },
|
|
6
|
+
vendo_apps_create: { label: "Building your view" },
|
|
7
|
+
slack_SLACK_SEND_MESSAGE: { label: "Post to #renewals in Slack" },
|
|
8
|
+
};
|
|
9
|
+
/* ------------------------------------------------------------------ */
|
|
10
|
+
/* Generated views (vendo-genui/v2) */
|
|
11
|
+
/* ------------------------------------------------------------------ */
|
|
12
|
+
const HERO_SOURCE = `
|
|
13
|
+
import * as React from "react";
|
|
14
|
+
const S = { ink: "#14151a", soft: "#4c4d55", faint: "#8a8b92", line: "#e8e6e1", green: "#1e7f53", greenBg: "#e7f4ee" };
|
|
15
|
+
export default function RenewalHero() {
|
|
16
|
+
const tile = (label, value, sub) => (
|
|
17
|
+
<div style={{ flex: 1, border: "1px solid " + S.line, borderRadius: 10, padding: "10px 12px", background: "#fff" }}>
|
|
18
|
+
<div style={{ fontSize: 11.5, color: S.soft }}>{label}</div>
|
|
19
|
+
<div style={{ fontSize: 24, fontWeight: 650, color: S.ink, letterSpacing: "-0.02em" }}>{value}</div>
|
|
20
|
+
<div style={{ fontSize: 10.5, color: S.faint }}>{sub}</div>
|
|
21
|
+
</div>
|
|
22
|
+
);
|
|
23
|
+
return (
|
|
24
|
+
<div style={{ fontFamily: "Inter, system-ui", display: "flex", flexDirection: "column", gap: 10 }}>
|
|
25
|
+
<div style={{ display: "flex", justifyContent: "flex-end" }}>
|
|
26
|
+
<span style={{ display: "inline-flex", alignItems: "center", gap: 6, fontSize: 11, fontWeight: 600, color: "#15573a", background: S.greenBg, borderRadius: 999, padding: "3px 10px" }}>
|
|
27
|
+
<span style={{ width: 6, height: 6, borderRadius: 999, background: S.green }} />
|
|
28
|
+
next 30 days
|
|
29
|
+
</span>
|
|
30
|
+
</div>
|
|
31
|
+
<div style={{ display: "flex", gap: 10 }}>
|
|
32
|
+
{tile("Renewals due", 7, "of 42 active accounts")}
|
|
33
|
+
{tile("At risk", 2, "no touchpoint in 60 days")}
|
|
34
|
+
{tile("Next up", "3d", "Northwind \\u00b7 Jul 21")}
|
|
35
|
+
</div>
|
|
36
|
+
</div>
|
|
37
|
+
);
|
|
38
|
+
}
|
|
39
|
+
`;
|
|
40
|
+
const LIST_SOURCE = `
|
|
41
|
+
import * as React from "react";
|
|
42
|
+
const S = { ink: "#14151a", soft: "#4c4d55", faint: "#8a8b92", line: "#e8e6e1", red: "#b0473a", redBg: "#fbede9", amber: "#a16207", amberBg: "#faf3e3", green: "#15573a", greenBg: "#e7f4ee" };
|
|
43
|
+
const ROWS = [
|
|
44
|
+
{ name: "Northwind Traders", plan: "Scale \\u00b7 $18k", due: "Jul 21", state: "at risk" },
|
|
45
|
+
{ name: "Fabrikam", plan: "Growth \\u00b7 $9k", due: "Jul 24", state: "on track" },
|
|
46
|
+
{ name: "Contoso Ltd", plan: "Scale \\u00b7 $22k", due: "Jul 29", state: "at risk" },
|
|
47
|
+
{ name: "Adventure Works", plan: "Starter \\u00b7 $3k", due: "Aug 02", state: "on track" },
|
|
48
|
+
];
|
|
49
|
+
export default function RenewalList() {
|
|
50
|
+
const badge = (state) => state === "at risk"
|
|
51
|
+
? { color: S.red, background: S.redBg }
|
|
52
|
+
: { color: S.green, background: S.greenBg };
|
|
53
|
+
return (
|
|
54
|
+
<div style={{ fontFamily: "Inter, system-ui", border: "1px solid " + S.line, borderRadius: 10, background: "#fff" }}>
|
|
55
|
+
{ROWS.map((row, index) => (
|
|
56
|
+
<div key={row.name} style={{ display: "flex", alignItems: "center", gap: 10, padding: "10px 12px", borderTop: index ? "1px solid " + S.line : "none" }}>
|
|
57
|
+
<div style={{ flex: 1 }}>
|
|
58
|
+
<div style={{ fontSize: 13, fontWeight: 600, color: S.ink }}>{row.name}</div>
|
|
59
|
+
<div style={{ fontSize: 11, color: S.faint }}>{row.plan}</div>
|
|
60
|
+
</div>
|
|
61
|
+
<div style={{ fontSize: 11.5, color: S.soft }}>{row.due}</div>
|
|
62
|
+
<span style={{ fontSize: 10.5, fontWeight: 600, borderRadius: 999, padding: "3px 9px", ...badge(row.state) }}>{row.state}</span>
|
|
63
|
+
</div>
|
|
64
|
+
))}
|
|
65
|
+
</div>
|
|
66
|
+
);
|
|
67
|
+
}
|
|
68
|
+
`;
|
|
69
|
+
/** The finished "Renewals radar" view — also the slot's pinned payload. */
|
|
70
|
+
export function renewalsViewPayload() {
|
|
71
|
+
return {
|
|
72
|
+
formatVersion: "vendo-genui/v2",
|
|
73
|
+
name: "Renewals radar",
|
|
74
|
+
root: "root",
|
|
75
|
+
nodes: [
|
|
76
|
+
{ id: "root", component: "Stack", props: { gap: 14 }, children: ["hero", "list"] },
|
|
77
|
+
{ id: "hero", component: "RenewalHero", source: "generated" },
|
|
78
|
+
{ id: "list", component: "RenewalList", source: "generated" },
|
|
79
|
+
],
|
|
80
|
+
components: { RenewalHero: HERO_SOURCE, RenewalList: LIST_SOURCE },
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
/** A deliberately broken payload. The renderer is fail-soft about CONTENT
|
|
84
|
+
* problems (bad root, missing components → contained notices), so to reach
|
|
85
|
+
* the slot's recovery path this has to crash the renderer itself: a partial
|
|
86
|
+
* (`streaming: true`) payload whose `nodes` is malformed throws host-side in
|
|
87
|
+
* TreeView, outside the per-node boundaries — and VendoSlot's PinMount
|
|
88
|
+
* boundary keeps the host's original children visible (06-apps §8). */
|
|
89
|
+
export function brokenViewPayload() {
|
|
90
|
+
return {
|
|
91
|
+
formatVersion: "vendo-genui/v2",
|
|
92
|
+
name: "Broken view",
|
|
93
|
+
root: "root",
|
|
94
|
+
streaming: true,
|
|
95
|
+
nodes: "corrupted-in-transit",
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
function streamingViewChunk(nodeIds, streaming) {
|
|
99
|
+
const payload = renewalsViewPayload();
|
|
100
|
+
payload.nodes = payload.nodes.filter((node) => node.id === "root" || nodeIds.includes(node.id));
|
|
101
|
+
payload.nodes.find((node) => node.id === "root").children = nodeIds;
|
|
102
|
+
if (streaming)
|
|
103
|
+
payload.streaming = true;
|
|
104
|
+
return {
|
|
105
|
+
type: "data-vendo-view",
|
|
106
|
+
id: "vendo-view:app_renewals",
|
|
107
|
+
data: { appId: "app_renewals", payload },
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
/* ------------------------------------------------------------------ */
|
|
111
|
+
/* Director scripts */
|
|
112
|
+
/* ------------------------------------------------------------------ */
|
|
113
|
+
const chunk = (delay, value) => ({
|
|
114
|
+
delay,
|
|
115
|
+
chunk: value,
|
|
116
|
+
});
|
|
117
|
+
const STREAMED_ANSWER = [
|
|
118
|
+
"Here's where your renewals stand. ",
|
|
119
|
+
"Seven accounts come up for renewal in the next thirty days, ",
|
|
120
|
+
"with a combined contract value of about $61k. ",
|
|
121
|
+
"Two of them — Northwind Traders and Contoso — look at risk: ",
|
|
122
|
+
"no touchpoint in over sixty days and declining seat usage. ",
|
|
123
|
+
"Fabrikam and Adventure Works are healthy; both expanded last quarter. ",
|
|
124
|
+
"If you'd like, I can build you a small view that tracks all of this live, ",
|
|
125
|
+
"sorted by renewal date with the at-risk accounts pinned on top. ",
|
|
126
|
+
"I can also draft the outreach for the two at-risk accounts ",
|
|
127
|
+
"and post a heads-up to your team in Slack every morning at 8:00 ",
|
|
128
|
+
"so nothing slips through. ",
|
|
129
|
+
"Just say the word and I'll wire it up.",
|
|
130
|
+
];
|
|
131
|
+
/** A long, slowly streaming text turn — the "streaming" screenshot state. */
|
|
132
|
+
export function streamingScript() {
|
|
133
|
+
return {
|
|
134
|
+
turns: [
|
|
135
|
+
{
|
|
136
|
+
cues: [
|
|
137
|
+
chunk(0, { type: "start" }),
|
|
138
|
+
chunk(100, { type: "start-step" }),
|
|
139
|
+
chunk(400, { type: "tool-input-start", toolCallId: "call_renewals", toolName: "host_listRenewals" }),
|
|
140
|
+
chunk(300, { type: "tool-input-available", toolCallId: "call_renewals", toolName: "host_listRenewals", input: {} }),
|
|
141
|
+
chunk(900, { type: "tool-output-available", toolCallId: "call_renewals", output: { ok: true, count: 7 } }),
|
|
142
|
+
chunk(500, { type: "text-start", id: "txt_stream" }),
|
|
143
|
+
...STREAMED_ANSWER.map((delta) => chunk(950, { type: "text-delta", id: "txt_stream", delta })),
|
|
144
|
+
chunk(300, { type: "text-end", id: "txt_stream" }),
|
|
145
|
+
chunk(100, { type: "finish-step" }),
|
|
146
|
+
chunk(50, { type: "finish" }),
|
|
147
|
+
],
|
|
148
|
+
},
|
|
149
|
+
],
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
/** Tool beats → a generated view streaming in → a closing summary. */
|
|
153
|
+
export function viewScript() {
|
|
154
|
+
return {
|
|
155
|
+
turns: [
|
|
156
|
+
{
|
|
157
|
+
cues: [
|
|
158
|
+
chunk(0, { type: "start" }),
|
|
159
|
+
chunk(100, { type: "start-step" }),
|
|
160
|
+
chunk(400, { type: "tool-input-start", toolCallId: "call_renewals", toolName: "host_listRenewals" }),
|
|
161
|
+
chunk(300, { type: "tool-input-available", toolCallId: "call_renewals", toolName: "host_listRenewals", input: {} }),
|
|
162
|
+
chunk(1000, { type: "tool-output-available", toolCallId: "call_renewals", output: { ok: true, count: 7 } }),
|
|
163
|
+
chunk(400, { type: "tool-input-start", toolCallId: "call_build", toolName: "vendo_apps_create" }),
|
|
164
|
+
chunk(300, { type: "tool-input-available", toolCallId: "call_build", toolName: "vendo_apps_create", input: { prompt: "renewals radar" } }),
|
|
165
|
+
chunk(700, streamingViewChunk(["hero"], true)),
|
|
166
|
+
chunk(1600, streamingViewChunk(["hero", "list"], true)),
|
|
167
|
+
chunk(1800, streamingViewChunk(["hero", "list"], false)),
|
|
168
|
+
chunk(700, { type: "tool-output-available", toolCallId: "call_build", output: { appId: "app_renewals" } }),
|
|
169
|
+
chunk(500, { type: "text-start", id: "txt_view" }),
|
|
170
|
+
chunk(150, { type: "text-delta", id: "txt_view", delta: "Your Renewals radar is live — " }),
|
|
171
|
+
chunk(170, { type: "text-delta", id: "txt_view", delta: "at-risk accounts stay pinned on top, " }),
|
|
172
|
+
chunk(170, { type: "text-delta", id: "txt_view", delta: "and it refreshes from your account data as things change." }),
|
|
173
|
+
chunk(120, { type: "text-end", id: "txt_view" }),
|
|
174
|
+
chunk(100, { type: "finish-step" }),
|
|
175
|
+
chunk(50, { type: "finish" }),
|
|
176
|
+
],
|
|
177
|
+
},
|
|
178
|
+
],
|
|
179
|
+
};
|
|
180
|
+
}
|
|
181
|
+
/** Turn 0 parks on a write approval; deciding it resumes turn 1 (03-agent §4). */
|
|
182
|
+
export function approvalScript() {
|
|
183
|
+
return {
|
|
184
|
+
turns: [
|
|
185
|
+
{
|
|
186
|
+
cues: [
|
|
187
|
+
chunk(0, { type: "start" }),
|
|
188
|
+
chunk(100, { type: "start-step" }),
|
|
189
|
+
chunk(400, { type: "tool-input-start", toolCallId: "call_renewals", toolName: "host_listRenewals" }),
|
|
190
|
+
chunk(300, { type: "tool-input-available", toolCallId: "call_renewals", toolName: "host_listRenewals", input: {} }),
|
|
191
|
+
chunk(900, { type: "tool-output-available", toolCallId: "call_renewals", output: { ok: true, atRisk: 2 } }),
|
|
192
|
+
chunk(500, { type: "tool-input-start", toolCallId: "call_slack", toolName: "slack_SLACK_SEND_MESSAGE" }),
|
|
193
|
+
chunk(300, {
|
|
194
|
+
type: "tool-input-available",
|
|
195
|
+
toolCallId: "call_slack",
|
|
196
|
+
toolName: "slack_SLACK_SEND_MESSAGE",
|
|
197
|
+
input: { channel: "#renewals", message: "Heads up: Northwind and Contoso renew this month with no recent touchpoint." },
|
|
198
|
+
}),
|
|
199
|
+
chunk(300, { type: "data-vendo-approval", id: "risk:call_slack", data: { toolCallId: "call_slack", risk: "write" } }),
|
|
200
|
+
chunk(600, { type: "tool-approval-request", approvalId: "appr_slack", toolCallId: "call_slack" }),
|
|
201
|
+
chunk(200, { type: "finish-step" }),
|
|
202
|
+
chunk(50, { type: "finish" }),
|
|
203
|
+
],
|
|
204
|
+
},
|
|
205
|
+
{
|
|
206
|
+
cues: [
|
|
207
|
+
chunk(0, { type: "start" }),
|
|
208
|
+
chunk(100, { type: "start-step" }),
|
|
209
|
+
chunk(800, { type: "tool-output-available", toolCallId: "call_slack", output: { ok: true, channel: "#renewals" } }),
|
|
210
|
+
chunk(400, { type: "text-start", id: "txt_resumed" }),
|
|
211
|
+
chunk(150, { type: "text-delta", id: "txt_resumed", delta: "Posted to #renewals — " }),
|
|
212
|
+
chunk(170, { type: "text-delta", id: "txt_resumed", delta: "your team knows about both at-risk renewals, " }),
|
|
213
|
+
chunk(170, { type: "text-delta", id: "txt_resumed", delta: "and I'll flag any account that goes quiet from here on." }),
|
|
214
|
+
chunk(120, { type: "text-end", id: "txt_resumed" }),
|
|
215
|
+
chunk(100, { type: "finish-step" }),
|
|
216
|
+
chunk(50, { type: "finish" }),
|
|
217
|
+
],
|
|
218
|
+
},
|
|
219
|
+
],
|
|
220
|
+
};
|
|
221
|
+
}
|
|
222
|
+
/** A connector call that needs a per-user connected account first (04 §3). */
|
|
223
|
+
export function connectScript() {
|
|
224
|
+
return {
|
|
225
|
+
turns: [
|
|
226
|
+
{
|
|
227
|
+
cues: [
|
|
228
|
+
chunk(0, { type: "start" }),
|
|
229
|
+
chunk(100, { type: "start-step" }),
|
|
230
|
+
chunk(500, { type: "tool-input-start", toolCallId: "call_slack", toolName: "slack_SLACK_SEND_MESSAGE" }),
|
|
231
|
+
chunk(300, {
|
|
232
|
+
type: "tool-input-available",
|
|
233
|
+
toolCallId: "call_slack",
|
|
234
|
+
toolName: "slack_SLACK_SEND_MESSAGE",
|
|
235
|
+
input: { channel: "#renewals", message: "Renewals digest for the week" },
|
|
236
|
+
}),
|
|
237
|
+
chunk(800, {
|
|
238
|
+
type: "tool-output-available",
|
|
239
|
+
toolCallId: "call_slack",
|
|
240
|
+
output: {
|
|
241
|
+
status: "connect-required",
|
|
242
|
+
connect: { connector: "slack", toolkit: "slack", message: "Connect Slack to post the digest to #renewals." },
|
|
243
|
+
},
|
|
244
|
+
}),
|
|
245
|
+
chunk(400, { type: "text-start", id: "txt_connect" }),
|
|
246
|
+
chunk(150, { type: "text-delta", id: "txt_connect", delta: "I drafted the digest — " }),
|
|
247
|
+
chunk(170, { type: "text-delta", id: "txt_connect", delta: "connect your Slack and I'll post it to #renewals." }),
|
|
248
|
+
chunk(120, { type: "text-end", id: "txt_connect" }),
|
|
249
|
+
chunk(100, { type: "finish-step" }),
|
|
250
|
+
chunk(50, { type: "finish" }),
|
|
251
|
+
],
|
|
252
|
+
},
|
|
253
|
+
],
|
|
254
|
+
};
|
|
255
|
+
}
|
|
256
|
+
/** The Activities empty state: a workspace where the agent has not acted yet —
|
|
257
|
+
* no pending approvals, no audit trail. Everything else stays populated so
|
|
258
|
+
* the surface sits in a realistic host page. */
|
|
259
|
+
export function emptyActivitiesFixtures() {
|
|
260
|
+
return { ...playgroundFixtures(), approvals: [], activity: [] };
|
|
261
|
+
}
|
|
262
|
+
function textTurn(id, role, text) {
|
|
263
|
+
return { id, role, parts: [{ type: "text", text }] };
|
|
264
|
+
}
|
|
265
|
+
/** Fresh copies every call: scenario state never leaks between mounts. */
|
|
266
|
+
export function playgroundFixtures() {
|
|
267
|
+
const renewalsApp = {
|
|
268
|
+
format: "vendo/app@1",
|
|
269
|
+
id: "app_renewals",
|
|
270
|
+
name: "Renewals radar",
|
|
271
|
+
description: "Upcoming renewals sorted by date, at-risk accounts pinned on top.",
|
|
272
|
+
ui: "tree",
|
|
273
|
+
tree: renewalsViewPayload(),
|
|
274
|
+
};
|
|
275
|
+
const digestApp = {
|
|
276
|
+
format: "vendo/app@1",
|
|
277
|
+
id: "app_digest",
|
|
278
|
+
name: "Morning renewals digest",
|
|
279
|
+
description: "Posts the renewals digest to #renewals every morning at 8:00.",
|
|
280
|
+
trigger: { on: { kind: "schedule", every: "1d" }, run: { kind: "agentic", prompt: "Summarize renewal changes and post to #renewals." } },
|
|
281
|
+
};
|
|
282
|
+
return {
|
|
283
|
+
status: { posture: "rules", version: "0.3.0-playground", blocks: { agent: {}, guard: {}, apps: {} } },
|
|
284
|
+
threads: [
|
|
285
|
+
{
|
|
286
|
+
id: "thr_renewals",
|
|
287
|
+
title: "Renewals radar",
|
|
288
|
+
updatedAt: "2026-07-18T09:12:00.000Z",
|
|
289
|
+
thread: {
|
|
290
|
+
id: "thr_renewals",
|
|
291
|
+
subject: "Renewals radar",
|
|
292
|
+
createdAt: "2026-07-18T09:02:00.000Z",
|
|
293
|
+
updatedAt: "2026-07-18T09:12:00.000Z",
|
|
294
|
+
messages: [
|
|
295
|
+
textTurn("msg_r1", "user", "Which accounts renew this month, and which of them are at risk?"),
|
|
296
|
+
textTurn("msg_r2", "assistant", "Seven accounts renew in the next thirty days. Two look at risk — Northwind Traders and Contoso — no touchpoint in over sixty days."),
|
|
297
|
+
textTurn("msg_r3", "user", "Build me a view that tracks this live."),
|
|
298
|
+
{
|
|
299
|
+
id: "msg_r4",
|
|
300
|
+
role: "assistant",
|
|
301
|
+
parts: [
|
|
302
|
+
{
|
|
303
|
+
type: "data-vendo-view",
|
|
304
|
+
id: "vendo-view:app_renewals",
|
|
305
|
+
data: { appId: "app_renewals", payload: renewalsViewPayload() },
|
|
306
|
+
},
|
|
307
|
+
{ type: "text", text: "Your Renewals radar is live — at-risk accounts stay pinned on top." },
|
|
308
|
+
],
|
|
309
|
+
},
|
|
310
|
+
],
|
|
311
|
+
},
|
|
312
|
+
},
|
|
313
|
+
{
|
|
314
|
+
id: "thr_onboarding",
|
|
315
|
+
title: "Onboarding checklist",
|
|
316
|
+
updatedAt: "2026-07-17T15:40:00.000Z",
|
|
317
|
+
thread: {
|
|
318
|
+
id: "thr_onboarding",
|
|
319
|
+
subject: "Onboarding checklist",
|
|
320
|
+
createdAt: "2026-07-17T15:31:00.000Z",
|
|
321
|
+
updatedAt: "2026-07-17T15:40:00.000Z",
|
|
322
|
+
messages: [
|
|
323
|
+
textTurn("msg_o1", "user", "What's left before the Fabrikam workspace goes live?"),
|
|
324
|
+
textTurn("msg_o2", "assistant", "Two steps: their SSO domain is unverified, and the billing contact hasn't accepted the invite. Both owners were nudged yesterday."),
|
|
325
|
+
],
|
|
326
|
+
},
|
|
327
|
+
},
|
|
328
|
+
{
|
|
329
|
+
id: "thr_billing",
|
|
330
|
+
title: "Billing question",
|
|
331
|
+
updatedAt: "2026-07-16T11:05:00.000Z",
|
|
332
|
+
thread: {
|
|
333
|
+
id: "thr_billing",
|
|
334
|
+
subject: "Billing question",
|
|
335
|
+
createdAt: "2026-07-16T11:00:00.000Z",
|
|
336
|
+
updatedAt: "2026-07-16T11:05:00.000Z",
|
|
337
|
+
messages: [
|
|
338
|
+
textTurn("msg_b1", "user", "Why did the Contoso invoice change this cycle?"),
|
|
339
|
+
textTurn("msg_b2", "assistant", "They added 14 seats on July 3rd — the delta is prorated on this invoice and rolls into the base from next cycle."),
|
|
340
|
+
],
|
|
341
|
+
},
|
|
342
|
+
},
|
|
343
|
+
],
|
|
344
|
+
apps: [renewalsApp, digestApp],
|
|
345
|
+
automations: [{ app: digestApp, enabled: true }],
|
|
346
|
+
connections: [
|
|
347
|
+
{ id: "conn_slack", connector: "composio", toolkit: "slack", status: "active", createdAt: "2026-06-30T08:00:00.000Z" },
|
|
348
|
+
{ id: "conn_github", connector: "composio", toolkit: "github", status: "expired", createdAt: "2026-05-12T10:00:00.000Z" },
|
|
349
|
+
],
|
|
350
|
+
approvals: [
|
|
351
|
+
{
|
|
352
|
+
id: "appr_digest_slack",
|
|
353
|
+
call: {
|
|
354
|
+
id: "call_digest_slack",
|
|
355
|
+
tool: "slack_SLACK_SEND_MESSAGE",
|
|
356
|
+
args: { channel: "#renewals", message: "Morning digest: 7 renewals in the next 30 days, 2 at risk." },
|
|
357
|
+
},
|
|
358
|
+
descriptor: {
|
|
359
|
+
name: "slack_SLACK_SEND_MESSAGE",
|
|
360
|
+
description: "Post a message to a Slack channel.",
|
|
361
|
+
inputSchema: { type: "object" },
|
|
362
|
+
risk: "write",
|
|
363
|
+
},
|
|
364
|
+
inputPreview: "channel: #renewals\nmessage: Morning digest: 7 renewals in the next 30 days, 2 at risk.",
|
|
365
|
+
ctx: { principal: PRINCIPAL, venue: "automation", presence: "away", appId: "app_digest", trigger: { runId: "run_02", kind: "schedule" } },
|
|
366
|
+
createdAt: "2026-07-18T08:00:00.000Z",
|
|
367
|
+
},
|
|
368
|
+
],
|
|
369
|
+
grants: [
|
|
370
|
+
{
|
|
371
|
+
id: "grant_renewals_read",
|
|
372
|
+
subject: PRINCIPAL.subject,
|
|
373
|
+
tool: "host_listRenewals",
|
|
374
|
+
descriptorHash: "sha256:playground",
|
|
375
|
+
scope: { kind: "tool" },
|
|
376
|
+
duration: "standing",
|
|
377
|
+
source: "chat",
|
|
378
|
+
grantedAt: "2026-07-01T09:00:00.000Z",
|
|
379
|
+
},
|
|
380
|
+
],
|
|
381
|
+
activity: [
|
|
382
|
+
{
|
|
383
|
+
id: "aud_01",
|
|
384
|
+
at: "2026-07-18T08:00:05.000Z",
|
|
385
|
+
kind: "run",
|
|
386
|
+
principal: PRINCIPAL,
|
|
387
|
+
venue: "automation",
|
|
388
|
+
presence: "away",
|
|
389
|
+
appId: "app_digest",
|
|
390
|
+
tool: "host_listRenewals",
|
|
391
|
+
inputPreview: "{}",
|
|
392
|
+
outcome: "ok",
|
|
393
|
+
decidedBy: "grant",
|
|
394
|
+
},
|
|
395
|
+
{
|
|
396
|
+
id: "aud_02",
|
|
397
|
+
at: "2026-07-18T08:00:09.000Z",
|
|
398
|
+
kind: "approval",
|
|
399
|
+
principal: PRINCIPAL,
|
|
400
|
+
venue: "automation",
|
|
401
|
+
presence: "away",
|
|
402
|
+
appId: "app_digest",
|
|
403
|
+
tool: "slack_SLACK_SEND_MESSAGE",
|
|
404
|
+
inputPreview: "channel: #renewals",
|
|
405
|
+
outcome: "pending-approval",
|
|
406
|
+
decidedBy: "rule",
|
|
407
|
+
},
|
|
408
|
+
{
|
|
409
|
+
id: "aud_03",
|
|
410
|
+
at: "2026-07-18T09:04:00.000Z",
|
|
411
|
+
kind: "tool-call",
|
|
412
|
+
principal: PRINCIPAL,
|
|
413
|
+
venue: "chat",
|
|
414
|
+
presence: "present",
|
|
415
|
+
tool: "host_listRenewals",
|
|
416
|
+
inputPreview: "{}",
|
|
417
|
+
outcome: "ok",
|
|
418
|
+
decidedBy: "grant",
|
|
419
|
+
},
|
|
420
|
+
{
|
|
421
|
+
id: "aud_04",
|
|
422
|
+
at: "2026-07-18T09:05:30.000Z",
|
|
423
|
+
kind: "app-lifecycle",
|
|
424
|
+
principal: PRINCIPAL,
|
|
425
|
+
venue: "chat",
|
|
426
|
+
presence: "present",
|
|
427
|
+
appId: "app_renewals",
|
|
428
|
+
inputPreview: "created “Renewals radar”",
|
|
429
|
+
outcome: "ok",
|
|
430
|
+
},
|
|
431
|
+
{
|
|
432
|
+
id: "aud_05",
|
|
433
|
+
at: "2026-07-18T09:06:10.000Z",
|
|
434
|
+
kind: "tool-call",
|
|
435
|
+
principal: PRINCIPAL,
|
|
436
|
+
venue: "chat",
|
|
437
|
+
presence: "present",
|
|
438
|
+
tool: "host_deleteAccount",
|
|
439
|
+
inputPreview: "account: Contoso Ltd",
|
|
440
|
+
outcome: "blocked",
|
|
441
|
+
decidedBy: "rule",
|
|
442
|
+
},
|
|
443
|
+
],
|
|
444
|
+
runs: [
|
|
445
|
+
{
|
|
446
|
+
id: "run_01",
|
|
447
|
+
appId: "app_digest",
|
|
448
|
+
trigger: { kind: "schedule" },
|
|
449
|
+
status: "ok",
|
|
450
|
+
startedAt: "2026-07-17T08:00:00.000Z",
|
|
451
|
+
finishedAt: "2026-07-17T08:00:21.000Z",
|
|
452
|
+
steps: [
|
|
453
|
+
{ id: "step_1", tool: "host_listRenewals", outcome: "ok", at: "2026-07-17T08:00:04.000Z" },
|
|
454
|
+
{ id: "step_2", tool: "slack_SLACK_SEND_MESSAGE", outcome: "ok", at: "2026-07-17T08:00:19.000Z" },
|
|
455
|
+
],
|
|
456
|
+
summary: "Digest posted to #renewals.",
|
|
457
|
+
},
|
|
458
|
+
{
|
|
459
|
+
id: "run_02",
|
|
460
|
+
appId: "app_digest",
|
|
461
|
+
trigger: { kind: "schedule" },
|
|
462
|
+
status: "pending-approval",
|
|
463
|
+
startedAt: "2026-07-18T08:00:00.000Z",
|
|
464
|
+
steps: [
|
|
465
|
+
{ id: "step_1", tool: "host_listRenewals", outcome: "ok", at: "2026-07-18T08:00:04.000Z" },
|
|
466
|
+
{ id: "step_2", tool: "slack_SLACK_SEND_MESSAGE", outcome: "pending-approval", at: "2026-07-18T08:00:09.000Z" },
|
|
467
|
+
],
|
|
468
|
+
summary: "Waiting on the Slack post approval.",
|
|
469
|
+
},
|
|
470
|
+
],
|
|
471
|
+
};
|
|
472
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { defaultVendoTheme } from "@vendoai/ui";
|
|
3
|
+
import { StrictMode, useEffect, useState } from "react";
|
|
4
|
+
import { createRoot } from "react-dom/client";
|
|
5
|
+
import { ScenarioMount } from "./scenario-mount.js";
|
|
6
|
+
import { scenarios } from "./scenarios.js";
|
|
7
|
+
import { ThemeEditor, useGoogleFont } from "./theme-editor.js";
|
|
8
|
+
import { decodeThemeParam, encodeThemeParam } from "./theme-state.js";
|
|
9
|
+
const SHELL_CSS = `
|
|
10
|
+
:root { color-scheme: light; }
|
|
11
|
+
* { box-sizing: border-box; }
|
|
12
|
+
body { margin: 0; font-family: Inter, system-ui, -apple-system, sans-serif; background: #f3ede2; color: #14151a; }
|
|
13
|
+
.pg-shell { display: grid; grid-template-columns: 250px minmax(0, 1fr); min-height: 100vh; }
|
|
14
|
+
.pg-nav { border-right: 1px solid #e3ddd2; background: #faf6ee; padding: 18px 14px; position: sticky; top: 0; height: 100vh; overflow-y: auto; }
|
|
15
|
+
.pg-brand { font-size: 15px; font-weight: 700; letter-spacing: -0.01em; margin: 4px 6px 2px; }
|
|
16
|
+
.pg-brand small { display: block; font-weight: 500; font-size: 11px; color: #8a8b92; margin-top: 3px; }
|
|
17
|
+
.pg-group { font-size: 10.5px; font-weight: 650; text-transform: uppercase; letter-spacing: 0.08em; color: #8a8b92; margin: 18px 6px 6px; }
|
|
18
|
+
.pg-link { display: block; padding: 7px 10px; border-radius: 8px; font-size: 13px; color: #34353b; text-decoration: none; }
|
|
19
|
+
.pg-link:hover { background: #f0e9dc; }
|
|
20
|
+
.pg-link[aria-current="page"] { background: #14151a; color: #fffdf9; }
|
|
21
|
+
.pg-main { padding: 26px 30px 60px; min-width: 0; }
|
|
22
|
+
@media (max-width: 640px) {
|
|
23
|
+
.pg-shell { grid-template-columns: 1fr; }
|
|
24
|
+
.pg-nav { position: static; height: auto; border-right: none; border-bottom: 1px solid #e3ddd2; }
|
|
25
|
+
.pg-main { padding: 18px 16px 80px; }
|
|
26
|
+
}
|
|
27
|
+
.pg-head { max-width: 860px; margin-bottom: 20px; }
|
|
28
|
+
.pg-head h1 { font-size: 19px; letter-spacing: -0.01em; margin: 0 0 6px; }
|
|
29
|
+
.pg-head p { font-size: 13.5px; color: #5c5d64; line-height: 1.55; margin: 0; }
|
|
30
|
+
.pg-stage { max-width: 860px; }
|
|
31
|
+
.pg-embed { padding: 0; }
|
|
32
|
+
`;
|
|
33
|
+
const THEME_MESSAGE = "vendo-playground-theme";
|
|
34
|
+
const THEME_REQUEST = "vendo-playground-theme-request";
|
|
35
|
+
/** Validate a postMessage payload through the same gate as the URL param. */
|
|
36
|
+
function themeFromMessage(data) {
|
|
37
|
+
const message = data;
|
|
38
|
+
if (!message || message.type !== THEME_MESSAGE)
|
|
39
|
+
return undefined;
|
|
40
|
+
return decodeThemeParam(JSON.stringify(message.theme));
|
|
41
|
+
}
|
|
42
|
+
/** Shared theme state: seeded from `?theme=`, written back to the URL (so
|
|
43
|
+
* links share the look), and mirrored into embed iframes via postMessage so
|
|
44
|
+
* the phone-viewport scenario re-themes live without reloading. */
|
|
45
|
+
function useThemeState(embed) {
|
|
46
|
+
const [theme, setTheme] = useState(() => decodeThemeParam(new URLSearchParams(window.location.search).get("theme")) ?? defaultVendoTheme);
|
|
47
|
+
useGoogleFont(theme.typography.fontFamily);
|
|
48
|
+
useEffect(() => {
|
|
49
|
+
if (embed)
|
|
50
|
+
return;
|
|
51
|
+
const params = new URLSearchParams(window.location.search);
|
|
52
|
+
if (theme === defaultVendoTheme)
|
|
53
|
+
params.delete("theme");
|
|
54
|
+
else
|
|
55
|
+
params.set("theme", encodeThemeParam(theme));
|
|
56
|
+
const query = params.toString();
|
|
57
|
+
window.history.replaceState(null, "", `${window.location.pathname}${query ? `?${query}` : ""}${window.location.hash}`);
|
|
58
|
+
for (const frame of document.querySelectorAll("iframe")) {
|
|
59
|
+
frame.contentWindow?.postMessage({ type: THEME_MESSAGE, theme }, "*");
|
|
60
|
+
}
|
|
61
|
+
}, [embed, theme]);
|
|
62
|
+
useEffect(() => {
|
|
63
|
+
const onMessage = (event) => {
|
|
64
|
+
if (embed) {
|
|
65
|
+
const next = themeFromMessage(event.data);
|
|
66
|
+
if (next)
|
|
67
|
+
setTheme(next);
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
// An embed iframe finished mounting and wants the current state.
|
|
71
|
+
if (event.data?.type === THEME_REQUEST && event.source) {
|
|
72
|
+
event.source.postMessage({ type: THEME_MESSAGE, theme }, "*");
|
|
73
|
+
}
|
|
74
|
+
};
|
|
75
|
+
window.addEventListener("message", onMessage);
|
|
76
|
+
return () => window.removeEventListener("message", onMessage);
|
|
77
|
+
}, [embed, theme]);
|
|
78
|
+
useEffect(() => {
|
|
79
|
+
if (embed && window.parent !== window)
|
|
80
|
+
window.parent.postMessage({ type: THEME_REQUEST }, "*");
|
|
81
|
+
}, [embed]);
|
|
82
|
+
return [theme, setTheme];
|
|
83
|
+
}
|
|
84
|
+
function useHashScenario() {
|
|
85
|
+
const [hash, setHash] = useState(() => window.location.hash.slice(1));
|
|
86
|
+
useEffect(() => {
|
|
87
|
+
const onChange = () => setHash(window.location.hash.slice(1));
|
|
88
|
+
window.addEventListener("hashchange", onChange);
|
|
89
|
+
return () => window.removeEventListener("hashchange", onChange);
|
|
90
|
+
}, []);
|
|
91
|
+
return scenarios.find((scenario) => scenario.id === hash) ?? scenarios[0];
|
|
92
|
+
}
|
|
93
|
+
function App() {
|
|
94
|
+
const scenario = useHashScenario();
|
|
95
|
+
const embed = new URLSearchParams(window.location.search).has("embed");
|
|
96
|
+
const [theme, setTheme] = useThemeState(embed);
|
|
97
|
+
if (embed) {
|
|
98
|
+
return (_jsx("div", { className: "pg-embed", children: _jsx(ScenarioMount, { scenario: scenario, theme: theme }, scenario.id) }));
|
|
99
|
+
}
|
|
100
|
+
const groups = [...new Set(scenarios.map((entry) => entry.group))];
|
|
101
|
+
return (_jsxs("div", { className: "pg-shell", children: [_jsxs("nav", { className: "pg-nav", "aria-label": "Scenarios", children: [_jsxs("div", { className: "pg-brand", children: ["Vendo playground", _jsx("small", { children: "every surface \u00B7 scripted data \u00B7 no model key" })] }), groups.map((group) => (_jsxs("div", { children: [_jsx("div", { className: "pg-group", children: group }), scenarios
|
|
102
|
+
.filter((entry) => entry.group === group)
|
|
103
|
+
.map((entry) => (_jsx("a", { className: "pg-link", href: `#${entry.id}`, "aria-current": entry.id === scenario.id ? "page" : undefined, children: entry.title }, entry.id)))] }, group)))] }), _jsxs("main", { className: "pg-main", children: [_jsxs("header", { className: "pg-head", children: [_jsx("h1", { children: scenario.title }), _jsx("p", { children: scenario.description })] }), _jsx("div", { className: "pg-stage", children: _jsx(ScenarioMount, { scenario: scenario, theme: theme }, scenario.id) })] }), _jsx(ThemeEditor, { theme: theme, onChange: setTheme })] }));
|
|
104
|
+
}
|
|
105
|
+
const style = document.createElement("style");
|
|
106
|
+
style.textContent = SHELL_CSS;
|
|
107
|
+
document.head.append(style);
|
|
108
|
+
createRoot(document.getElementById("root")).render(_jsx(StrictMode, { children: _jsx(App, {}) }));
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The shared scenario harness: one REAL chrome surface mounted against the
|
|
3
|
+
* fake wire client + scripted transport. Used by the playground page
|
|
4
|
+
* (main.tsx) and the docs inline embeds (embed-entry.tsx).
|
|
5
|
+
*/
|
|
6
|
+
import type { VendoTheme } from "@vendoai/core";
|
|
7
|
+
import type { PlaygroundScenario } from "./scenarios.js";
|
|
8
|
+
export declare function ScenarioMount({ scenario, theme, root }: {
|
|
9
|
+
scenario: PlaygroundScenario;
|
|
10
|
+
theme: VendoTheme;
|
|
11
|
+
/** The embed container, when mounted inline in a host page (docs). */
|
|
12
|
+
root?: HTMLElement;
|
|
13
|
+
}): import("react").JSX.Element;
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { ScriptedTransport, VendoProvider } from "@vendoai/ui";
|
|
3
|
+
import { useEffect, useMemo } from "react";
|
|
4
|
+
import { createFakeClient } from "./fake-client.js";
|
|
5
|
+
import { playgroundFixtures, playgroundToolMeta } from "./fixtures.js";
|
|
6
|
+
/**
|
|
7
|
+
* Types the scenario's opening turn into the mounted chrome's own composer and
|
|
8
|
+
* submits it — the send travels the REAL path (draft state, user bubble,
|
|
9
|
+
* transport). Scoped to `root` first so several embeds on one page each drive
|
|
10
|
+
* their own composer; falls back to the document for surfaces that portal to
|
|
11
|
+
* the body (the overlay). Retries briefly while the surface mounts.
|
|
12
|
+
*/
|
|
13
|
+
function useAutoSend(scenario, root) {
|
|
14
|
+
useEffect(() => {
|
|
15
|
+
const prompt = scenario.autoSend;
|
|
16
|
+
if (!prompt)
|
|
17
|
+
return;
|
|
18
|
+
let tries = 0;
|
|
19
|
+
let submitTimer;
|
|
20
|
+
const timer = setInterval(() => {
|
|
21
|
+
const scope = root ?? document;
|
|
22
|
+
const textarea = scope.querySelector("form.fl-composer textarea") ??
|
|
23
|
+
(root ? document.querySelector("form.fl-composer textarea") : null);
|
|
24
|
+
const form = textarea?.closest("form");
|
|
25
|
+
if (textarea && form) {
|
|
26
|
+
clearInterval(timer);
|
|
27
|
+
const setter = Object.getOwnPropertyDescriptor(HTMLTextAreaElement.prototype, "value")?.set;
|
|
28
|
+
setter?.call(textarea, prompt);
|
|
29
|
+
textarea.dispatchEvent(new Event("input", { bubbles: true }));
|
|
30
|
+
// Let React commit the draft before the submit reads it.
|
|
31
|
+
submitTimer = setTimeout(() => form.requestSubmit(), 120);
|
|
32
|
+
}
|
|
33
|
+
else if ((tries += 1) > 50) {
|
|
34
|
+
clearInterval(timer);
|
|
35
|
+
}
|
|
36
|
+
}, 100);
|
|
37
|
+
return () => {
|
|
38
|
+
clearInterval(timer);
|
|
39
|
+
clearTimeout(submitTimer);
|
|
40
|
+
};
|
|
41
|
+
}, [scenario, root]);
|
|
42
|
+
}
|
|
43
|
+
export function ScenarioMount({ scenario, theme, root }) {
|
|
44
|
+
const client = useMemo(() => createFakeClient((scenario.fixtures ?? playgroundFixtures)()), [scenario]);
|
|
45
|
+
const transport = useMemo(() => (scenario.script ? new ScriptedTransport(scenario.script, { speed: scenario.speed ?? 1 }) : undefined), [scenario]);
|
|
46
|
+
useAutoSend(scenario, root);
|
|
47
|
+
return (_jsx(VendoProvider, { client: client, transport: transport, theme: theme, tools: playgroundToolMeta, connectors: [{ toolkit: "slack", label: "Slack" }, { toolkit: "github", label: "GitHub" }], children: scenario.render() }));
|
|
48
|
+
}
|