@helloleo/plugins 0.2.0-beta.3 → 0.2.0-beta.5

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.
@@ -19,6 +19,9 @@ export const Route = createRootRouteWithContext<{ queryClient: QueryClient }>()(
19
19
  { name: 'viewport', content: 'width=device-width, initial-scale=1.0' },
20
20
  { title: '{{LEO_NAME}}' },
21
21
  { name: 'description', content: '{{LEO_DESCRIPTION}}' },
22
+ { property: 'og:title', content: '{{LEO_NAME}}' },
23
+ { property: 'og:description', content: '{{LEO_DESCRIPTION}}' },
24
+ { property: 'og:type', content: 'website' },
22
25
  ],
23
26
  links: [
24
27
  { rel: 'stylesheet', href: appCss },
@@ -33,8 +36,6 @@ export const Route = createRootRouteWithContext<{ queryClient: QueryClient }>()(
33
36
  href: 'https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=Space+Grotesk:wght@400;500;600;700&display=swap',
34
37
  },
35
38
  ],
36
- // Leo preview telemetry, do not delete
37
- scripts: [{ src: '/leo-runtime.js' }],
38
39
  }),
39
40
  component: RootComponent,
40
41
  })
@@ -3,11 +3,17 @@ import { tool } from "@opencode-ai/plugin";
3
3
 
4
4
  const z = tool.schema;
5
5
 
6
- const ALLOWED_BASE_HOSTS = [
7
- "helloleo.dev",
8
- "helloleo.app",
9
- "augustin-d60.workers.dev",
10
- ] as const;
6
+ const hosts = {
7
+ "helloleo.dev": {
8
+ private: true,
9
+ },
10
+ "helloleo.app": {
11
+ private: false,
12
+ },
13
+ "augustin-d60.workers.dev": {
14
+ private: false,
15
+ },
16
+ } as const;
11
17
 
12
18
  const DEFAULT_TIMEOUT_MS = 15_000;
13
19
  const MAX_TIMEOUT_MS = 30_000;
@@ -81,9 +87,15 @@ export const inspectHttp = tool({
81
87
 
82
88
  const timeoutMs = Math.max(1, Math.min(args.timeout, MAX_TIMEOUT_MS));
83
89
 
90
+ const previewGateSecret = process.env.PREVIEW_INSPECT_SECRET;
91
+ const defaultUserAgent =
92
+ isPrivateHost(url.hostname) && previewGateSecret
93
+ ? `LeoInspect/${previewGateSecret}`
94
+ : "leoInspect/0.1";
95
+
84
96
  const requestHeaders: Record<string, string> = {
85
97
  Accept: "application/json, text/plain, */*",
86
- "User-Agent": "helloleo-plugins-inspect-http/0.1",
98
+ "User-Agent": defaultUserAgent,
87
99
  Origin: "https://app.helloleo.dev",
88
100
  ...args.headers,
89
101
  };
@@ -176,16 +188,39 @@ function parseTargetUrl(raw: string): URL {
176
188
  return url;
177
189
  }
178
190
 
191
+ type HostBase = keyof typeof hosts;
192
+
193
+ function findHostBase(hostname: string): HostBase | null {
194
+ const host = hostname.toLowerCase().replace(/\.$/, "");
195
+ for (const base of Object.keys(hosts) as HostBase[]) {
196
+ if (host === base || host.endsWith(`.${base}`)) return base;
197
+ }
198
+ return null;
199
+ }
200
+
179
201
  function allowedHostsHuman(): string {
180
- return ALLOWED_BASE_HOSTS.map((base) => `**.${base}`).join(", ");
202
+ return (Object.keys(hosts) as HostBase[])
203
+ .map((base) => `**.${base}`)
204
+ .join(", ");
181
205
  }
182
206
 
183
207
  function isAllowedHost(hostname: string): boolean {
184
- const host = hostname.toLowerCase().replace(/\.$/, "");
185
- for (const base of ALLOWED_BASE_HOSTS) {
186
- if (host === base || host.endsWith(`.${base}`)) return true;
187
- }
188
- return false;
208
+ return findHostBase(hostname) !== null;
209
+ }
210
+
211
+ function isPrivateHost(hostname: string): boolean {
212
+ const base = findHostBase(hostname);
213
+ return base !== null && hosts[base].private;
214
+ }
215
+
216
+ // leo-core's nginx preview gate returns a static 403 page (see
217
+ // _PROXY_CONF_TMPL in sandbox/preview.py) when neither ?auth=<token>, the
218
+ // leo_auth_* cookie, nor the LeoInspect UA match. Recognize that exact page
219
+ // so a gated preview doesn't read as an app-level failure.
220
+ function isPreviewGate403(status: number, body: string): boolean {
221
+ return (
222
+ status === 403 && body.includes("403 Forbidden") && body.includes("nginx/")
223
+ );
189
224
  }
190
225
 
191
226
  function hasHeader(headers: Record<string, string>, name: string): boolean {
@@ -101,7 +101,7 @@ export const scaffold = tool({
101
101
  "- Add routes as files in src/routes/. Do not default export route components. Routing and layout conventions are documented in src/routes/README.md, read it before adding routes.",
102
102
  "- For JSON/CRUD APIs, add server routes under src/routes/api/. Read src/routes/api/health.ts first, add routes as needed.",
103
103
  "- shadcn/ui components are already in src/components/ui.",
104
- "- Design system: edit the tokens in src/styles.css (light and dark blocks).",
104
+ "- Design system: style exclusively with the token utilities, never hardcoded colors. To change the look, edit the token values in src/styles.css (light and dark blocks).",
105
105
  "- The database schema lives in src/db/schema.ts. Migrations run automatically.",
106
106
  "- Always fetch data with react-query (useQuery and useMutation).",
107
107
  "",