@guuey/create-agentic-app 0.10.0 → 0.11.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (71) hide show
  1. package/dist/templates/agentic-app/claude-agent-sdk/README.md +6 -4
  2. package/dist/templates/agentic-app/claude-agent-sdk/package.json +3 -3
  3. package/dist/templates/agentic-app/claude-agent-sdk/scripts/bootstrap.mjs +22 -0
  4. package/dist/templates/agentic-app/claude-agent-sdk/web/package.json +2 -1
  5. package/dist/templates/agentic-app/claude-agent-sdk/web/src/components/AgentChat.tsx +41 -3
  6. package/dist/templates/agentic-app/claude-agent-sdk/web/src/components/AppShell.tsx +95 -21
  7. package/dist/templates/agentic-app/claude-agent-sdk/web/src/components/QrLink.tsx +25 -0
  8. package/dist/templates/agentic-app/claude-agent-sdk/web/src/main.tsx +2 -3
  9. package/dist/templates/agentic-app/claude-agent-sdk/web/src/pages/Dashboard.tsx +2 -5
  10. package/dist/templates/agentic-app/claude-agent-sdk/web/src/pages/Landing.tsx +17 -2
  11. package/dist/templates/agentic-app/claude-agent-sdk/web/src/pages/TalkOnMobile.tsx +2 -18
  12. package/dist/templates/agentic-app/claude-agent-sdk/web/src/routes.ts +3 -3
  13. package/dist/templates/agentic-app/claude-agent-sdk/web/src/styles-app.css +22 -24
  14. package/dist/templates/agentic-app/claude-agent-sdk/web/src/vite-env.d.ts +13 -0
  15. package/dist/templates/agentic-app/google-adk/package.json +2 -2
  16. package/dist/templates/agentic-app/google-adk/scripts/bootstrap.mjs +22 -0
  17. package/dist/templates/agentic-app/google-adk/web/package.json +2 -1
  18. package/dist/templates/agentic-app/google-adk/web/src/components/AgentChat.tsx +41 -3
  19. package/dist/templates/agentic-app/google-adk/web/src/components/AppShell.tsx +95 -21
  20. package/dist/templates/agentic-app/google-adk/web/src/components/QrLink.tsx +25 -0
  21. package/dist/templates/agentic-app/google-adk/web/src/main.tsx +2 -3
  22. package/dist/templates/agentic-app/google-adk/web/src/pages/Dashboard.tsx +2 -5
  23. package/dist/templates/agentic-app/google-adk/web/src/pages/Landing.tsx +17 -2
  24. package/dist/templates/agentic-app/google-adk/web/src/pages/TalkOnMobile.tsx +2 -18
  25. package/dist/templates/agentic-app/google-adk/web/src/routes.ts +3 -3
  26. package/dist/templates/agentic-app/google-adk/web/src/styles-app.css +22 -24
  27. package/dist/templates/agentic-app/google-adk/web/src/vite-env.d.ts +13 -0
  28. package/dist/templates/agentic-app/openai-agents-sdk/README.md +6 -4
  29. package/dist/templates/agentic-app/openai-agents-sdk/package.json +3 -3
  30. package/dist/templates/agentic-app/openai-agents-sdk/scripts/bootstrap.mjs +22 -0
  31. package/dist/templates/agentic-app/openai-agents-sdk/web/package.json +2 -1
  32. package/dist/templates/agentic-app/openai-agents-sdk/web/src/components/AgentChat.tsx +41 -3
  33. package/dist/templates/agentic-app/openai-agents-sdk/web/src/components/AppShell.tsx +95 -21
  34. package/dist/templates/agentic-app/openai-agents-sdk/web/src/components/QrLink.tsx +25 -0
  35. package/dist/templates/agentic-app/openai-agents-sdk/web/src/main.tsx +2 -3
  36. package/dist/templates/agentic-app/openai-agents-sdk/web/src/pages/Dashboard.tsx +2 -5
  37. package/dist/templates/agentic-app/openai-agents-sdk/web/src/pages/Landing.tsx +17 -2
  38. package/dist/templates/agentic-app/openai-agents-sdk/web/src/pages/TalkOnMobile.tsx +2 -18
  39. package/dist/templates/agentic-app/openai-agents-sdk/web/src/routes.ts +3 -3
  40. package/dist/templates/agentic-app/openai-agents-sdk/web/src/styles-app.css +22 -24
  41. package/dist/templates/agentic-app/openai-agents-sdk/web/src/vite-env.d.ts +13 -0
  42. package/dist/templates/base/claude-agent-sdk/README.md +6 -4
  43. package/dist/templates/base/claude-agent-sdk/package.json +3 -3
  44. package/dist/templates/base/claude-agent-sdk/scripts/bootstrap.mjs +22 -0
  45. package/dist/templates/base/claude-agent-sdk/web/package.json +3 -1
  46. package/dist/templates/base/claude-agent-sdk/web/src/components/AgentChat.tsx +41 -3
  47. package/dist/templates/base/claude-agent-sdk/web/src/components/QrLink.tsx +25 -0
  48. package/dist/templates/base/claude-agent-sdk/web/src/main.tsx +14 -0
  49. package/dist/templates/base/claude-agent-sdk/web/src/pages/Landing.tsx +17 -2
  50. package/dist/templates/base/claude-agent-sdk/web/src/vite-env.d.ts +13 -0
  51. package/dist/templates/base/google-adk/package.json +2 -2
  52. package/dist/templates/base/google-adk/scripts/bootstrap.mjs +22 -0
  53. package/dist/templates/base/google-adk/web/package.json +3 -1
  54. package/dist/templates/base/google-adk/web/src/components/AgentChat.tsx +41 -3
  55. package/dist/templates/base/google-adk/web/src/components/QrLink.tsx +25 -0
  56. package/dist/templates/base/google-adk/web/src/main.tsx +14 -0
  57. package/dist/templates/base/google-adk/web/src/pages/Landing.tsx +17 -2
  58. package/dist/templates/base/google-adk/web/src/vite-env.d.ts +13 -0
  59. package/dist/templates/base/openai-agents-sdk/README.md +6 -4
  60. package/dist/templates/base/openai-agents-sdk/package.json +3 -3
  61. package/dist/templates/base/openai-agents-sdk/scripts/bootstrap.mjs +22 -0
  62. package/dist/templates/base/openai-agents-sdk/web/package.json +3 -1
  63. package/dist/templates/base/openai-agents-sdk/web/src/components/AgentChat.tsx +41 -3
  64. package/dist/templates/base/openai-agents-sdk/web/src/components/QrLink.tsx +25 -0
  65. package/dist/templates/base/openai-agents-sdk/web/src/main.tsx +14 -0
  66. package/dist/templates/base/openai-agents-sdk/web/src/pages/Landing.tsx +17 -2
  67. package/dist/templates/base/openai-agents-sdk/web/src/vite-env.d.ts +13 -0
  68. package/package.json +3 -3
  69. package/dist/templates/agentic-app/claude-agent-sdk/web/src/pages/AgentCanvas.tsx +0 -14
  70. package/dist/templates/agentic-app/google-adk/web/src/pages/AgentCanvas.tsx +0 -14
  71. package/dist/templates/agentic-app/openai-agents-sdk/web/src/pages/AgentCanvas.tsx +0 -14
@@ -8,6 +8,7 @@ import { Link } from "react-router-dom";
8
8
  import { appConfig, isLinked } from "../config";
9
9
  import { CHAT_PATH, HOME_PATH } from "../routes";
10
10
  import { mountWidget, type WidgetOutcome } from "../lib/widget";
11
+ import { QrLink } from "../components/QrLink";
11
12
 
12
13
  export function Landing() {
13
14
  const [widget, setWidget] = useState<WidgetOutcome | "unlinked" | "loading">(
@@ -19,19 +20,33 @@ export function Landing() {
19
20
  mountWidget((outcome) => setWidget(outcome));
20
21
  }, []);
21
22
 
23
+ // The demo posture (spec §2.3): a REAL product site whose hero's one job
24
+ // is getting the visitor INTO the demo — big CTA to the console page +
25
+ // a QR to continue on mobile via the portal.
26
+ const portalTarget =
27
+ appConfig.link !== null
28
+ ? `${appConfig.link.portalUrl}/agent/${appConfig.link.slug ?? appConfig.link.appId}`
29
+ : null;
30
+
22
31
  return (
23
32
  <main className="page landing">
24
- <section className="hero">
33
+ <section className="hero" data-tour="hero">
25
34
  <h1>{appConfig.copy.landing.headline}</h1>
26
35
  <p>{appConfig.copy.landing.sub}</p>
27
36
  <div className="hero-actions">
28
37
  <Link to={CHAT_PATH} className="btn btn-accent">
29
- Open the chat
38
+ {appConfig.demoMode ? "Enter the demo" : "Open the chat"}
30
39
  </Link>
31
40
  <Link to={HOME_PATH} className="btn">
32
41
  How this app is wired
33
42
  </Link>
34
43
  </div>
44
+ {appConfig.demoMode && portalTarget !== null ? (
45
+ <div className="hero-qr">
46
+ <p className="hint">Or continue on your phone:</p>
47
+ <QrLink url={portalTarget} size={140} />
48
+ </div>
49
+ ) : null}
35
50
  {widget === "unlinked" ? (
36
51
  <p className="hint">
37
52
  The floating agent launcher appears here once the app is bound to a
@@ -1 +1,14 @@
1
1
  /// <reference types="vite/client" />
2
+
3
+ interface ImportMetaEnv {
4
+ /**
5
+ * Optional external-script hook (guuey#303): a URL loaded at boot — the
6
+ * demo tour's bundle rides this in the demo apps; unset in customer
7
+ * builds.
8
+ */
9
+ readonly VITE_DEMO_TOUR_SRC?: string;
10
+ }
11
+
12
+ interface ImportMeta {
13
+ readonly env: ImportMetaEnv;
14
+ }
@@ -16,8 +16,8 @@
16
16
  },
17
17
  "devDependencies": {
18
18
  "@ggui-ai/cli": "0.6.3",
19
- "@guuey/cli": "0.10.0",
20
- "@guuey/config": "0.10.0",
19
+ "@guuey/cli": "0.11.0",
20
+ "@guuey/config": "0.11.0",
21
21
  "tsup": "^8.5.0",
22
22
  "tsx": "^4.19.0",
23
23
  "typescript": "^5.8.0"
@@ -121,6 +121,27 @@ async function guuey(args) {
121
121
  }
122
122
  }
123
123
 
124
+ // ── ggui.json theme sync (guuey#302 scaffold hygiene) ───────────────────────
125
+
126
+ /**
127
+ * Keep `ggui/ggui.json`'s theme MODE in step with guuey.app.json so the
128
+ * local `ggui serve` preview matches the site (the scaffold used to ship
129
+ * the two contradicting each other: light site, dark ggui preview). Only
130
+ * `mode` syncs — `preset` is ggui-side vocabulary and stays the author's
131
+ * choice. NOTE: the deployed render's theme is platform data (`guuey
132
+ * deploy` deliberately ignores this block; guuey#304 owns the production
133
+ * side) — this sync is about local-preview honesty.
134
+ */
135
+ function syncGguiTheme(config) {
136
+ const gguiJsonPath = join(projectRoot, "ggui", "ggui.json");
137
+ if (!existsSync(gguiJsonPath)) return;
138
+ const ggui = JSON.parse(readFileSync(gguiJsonPath, "utf8"));
139
+ if (ggui.theme?.mode === config.theme.mode) return;
140
+ ggui.theme = { ...(ggui.theme ?? {}), mode: config.theme.mode };
141
+ writeFileSync(gguiJsonPath, `${JSON.stringify(ggui, null, 2)}\n`, "utf8");
142
+ console.log(`ggui/ggui.json theme mode → ${config.theme.mode} (local preview matches the site).`);
143
+ }
144
+
124
145
  // ── AGENTS.md managed block ─────────────────────────────────────────────────
125
146
 
126
147
  function managedBlock(config) {
@@ -367,6 +388,7 @@ async function main() {
367
388
  // extraction (`--example`), the demo chrome turns off here.
368
389
  config.demoMode = false;
369
390
  writeConfig(config);
391
+ syncGguiTheme(config);
370
392
  regenerateAgentsMd(config);
371
393
 
372
394
  console.log(`\nConfigured "${name}" — guuey.app.json written, AGENTS.md updated.`);
@@ -9,14 +9,16 @@
9
9
  "typecheck": "tsc --noEmit"
10
10
  },
11
11
  "dependencies": {
12
- "@guuey/chat": "0.10.0",
12
+ "@guuey/chat": "0.11.0",
13
13
  "oidc-client-ts": "^3.1.0",
14
+ "qrcode": "^1.5.4",
14
15
  "react": "^19.0.0",
15
16
  "react-dom": "^19.0.0",
16
17
  "react-router-dom": "^7.1.0"
17
18
  },
18
19
  "devDependencies": {
19
20
  "@types/node": "^24.0.0",
21
+ "@types/qrcode": "^1.5.5",
20
22
  "@types/react": "^19.0.0",
21
23
  "@types/react-dom": "^19.0.0",
22
24
  "@vitejs/plugin-react": "^4.3.4",
@@ -10,11 +10,39 @@
10
10
  */
11
11
  import { useEffect, useState, type CSSProperties } from "react";
12
12
  import { GuueyChat } from "@guuey/chat/react";
13
+ import type { PlanViewSummary, ViewRefItem } from "@guuey/chat";
13
14
  import { agentEndpointUrl, appConfig, historyBaseUrl } from "../config";
14
15
  import { currentIdentityMode, ensureGuestSecret } from "../lib/identity";
15
16
  import { getBearerToken, currentUser, oidcConfigured } from "../lib/oidc";
16
17
 
17
- export function AgentChat({ className, style }: { className?: string; style?: CSSProperties }) {
18
+ /**
19
+ * The chat-rail bridge (agentic-app shell): with `viewsBridge` set, the
20
+ * chat runs the kit's CHIPS presentation — generative views collapse to
21
+ * compact chips in the rail, the full renders belong to the host's main
22
+ * canvas (fed by `onViewsChange`), and chip clicks re-select
23
+ * (`onViewRef` → `promotedViewKey`, the browser-history mechanic).
24
+ */
25
+ export interface ViewsBridge {
26
+ promotedViewKey?: string | undefined;
27
+ onViewRef: (item: ViewRefItem) => void;
28
+ onViewsChange: (views: PlanViewSummary[]) => void;
29
+ }
30
+
31
+ // Module scope on purpose: props want STABLE identities. An inline
32
+ // `policy={{ … }}` literal (or per-render arrow getters) re-mints every
33
+ // render; the kit stabilizes structurally since 0.11.0, but the template
34
+ // models the correct shape — one identity for the app's whole life.
35
+ const RAIL_POLICY = { view: { timeoutMs: 8000, presentation: "chips" as const } };
36
+
37
+ export function AgentChat({
38
+ className,
39
+ style,
40
+ viewsBridge,
41
+ }: {
42
+ className?: string;
43
+ style?: CSSProperties;
44
+ viewsBridge?: ViewsBridge;
45
+ }) {
18
46
  // The user's CHOSEN mode wins: an explicit "Continue as guest" must never
19
47
  // be shadowed by a cached OIDC session. Only when no choice is recorded
20
48
  // does a live session imply oidc.
@@ -43,11 +71,21 @@ export function AgentChat({ className, style }: { className?: string; style?: CS
43
71
  mode: appConfig.theme.mode,
44
72
  className,
45
73
  style,
74
+ ...(viewsBridge !== undefined
75
+ ? {
76
+ policy: RAIL_POLICY,
77
+ promotedViewKey: viewsBridge.promotedViewKey,
78
+ onViewRef: viewsBridge.onViewRef,
79
+ onViewsChange: viewsBridge.onViewsChange,
80
+ }
81
+ : {}),
46
82
  };
47
83
 
84
+ // The lib functions ARE the stable getters — no wrapping arrows (a fresh
85
+ // closure per render is the identity churn RAIL_POLICY exists to avoid).
48
86
  return identity === "oidc" ? (
49
- <GuueyChat {...shared} getAccessToken={() => getBearerToken()} />
87
+ <GuueyChat {...shared} getAccessToken={getBearerToken} />
50
88
  ) : (
51
- <GuueyChat {...shared} getGuestSecret={() => ensureGuestSecret()} />
89
+ <GuueyChat {...shared} getGuestSecret={ensureGuestSecret} />
52
90
  );
53
91
  }
@@ -0,0 +1,25 @@
1
+ /** A QR code for a URL, with the plain link beneath — mobile hand-off. */
2
+ import { useEffect, useRef, useState } from "react";
3
+ import QRCode from "qrcode";
4
+
5
+ export function QrLink({ url, size = 200 }: { url: string; size?: number }) {
6
+ const canvasRef = useRef<HTMLCanvasElement | null>(null);
7
+ const [error, setError] = useState<string | null>(null);
8
+
9
+ useEffect(() => {
10
+ if (!canvasRef.current) return;
11
+ QRCode.toCanvas(canvasRef.current, url, { width: size, margin: 1 }).catch((err: unknown) => {
12
+ setError(err instanceof Error ? err.message : String(err));
13
+ });
14
+ }, [url, size]);
15
+
16
+ return (
17
+ <span className="qr-link">
18
+ <canvas ref={canvasRef} className="qr" />
19
+ <a href={url} target="_blank" rel="noreferrer">
20
+ {url}
21
+ </a>
22
+ {error ? <span className="error">QR render failed: {error}</span> : null}
23
+ </span>
24
+ );
25
+ }
@@ -17,6 +17,20 @@ document.documentElement.dataset.mode = appConfig.theme.mode;
17
17
  document.documentElement.style.setProperty("--app-accent", appConfig.theme.accent);
18
18
  document.title = appConfig.brand.name;
19
19
 
20
+ // Optional external script (the analytics-snippet pattern): a build-time
21
+ // env names a script URL and this app loads it — the demo tour rides this
22
+ // hook (its bundle lives OUTSIDE the template; guuey#303). The script can
23
+ // anchor on the `data-tour` attributes the pages carry and listen for the
24
+ // `demo:render-complete` CustomEvent the app shell dispatches when a
25
+ // generative view first lands on the canvas.
26
+ const tourSrc = import.meta.env.VITE_DEMO_TOUR_SRC;
27
+ if (tourSrc !== undefined && tourSrc !== "") {
28
+ const script = document.createElement("script");
29
+ script.src = tourSrc;
30
+ script.defer = true;
31
+ document.head.appendChild(script);
32
+ }
33
+
20
34
  const router = createBrowserRouter([
21
35
  {
22
36
  element: <Shell />,
@@ -8,6 +8,7 @@ import { Link } from "react-router-dom";
8
8
  import { appConfig, isLinked } from "../config";
9
9
  import { CHAT_PATH, HOME_PATH } from "../routes";
10
10
  import { mountWidget, type WidgetOutcome } from "../lib/widget";
11
+ import { QrLink } from "../components/QrLink";
11
12
 
12
13
  export function Landing() {
13
14
  const [widget, setWidget] = useState<WidgetOutcome | "unlinked" | "loading">(
@@ -19,19 +20,33 @@ export function Landing() {
19
20
  mountWidget((outcome) => setWidget(outcome));
20
21
  }, []);
21
22
 
23
+ // The demo posture (spec §2.3): a REAL product site whose hero's one job
24
+ // is getting the visitor INTO the demo — big CTA to the console page +
25
+ // a QR to continue on mobile via the portal.
26
+ const portalTarget =
27
+ appConfig.link !== null
28
+ ? `${appConfig.link.portalUrl}/agent/${appConfig.link.slug ?? appConfig.link.appId}`
29
+ : null;
30
+
22
31
  return (
23
32
  <main className="page landing">
24
- <section className="hero">
33
+ <section className="hero" data-tour="hero">
25
34
  <h1>{appConfig.copy.landing.headline}</h1>
26
35
  <p>{appConfig.copy.landing.sub}</p>
27
36
  <div className="hero-actions">
28
37
  <Link to={CHAT_PATH} className="btn btn-accent">
29
- Open the chat
38
+ {appConfig.demoMode ? "Enter the demo" : "Open the chat"}
30
39
  </Link>
31
40
  <Link to={HOME_PATH} className="btn">
32
41
  How this app is wired
33
42
  </Link>
34
43
  </div>
44
+ {appConfig.demoMode && portalTarget !== null ? (
45
+ <div className="hero-qr">
46
+ <p className="hint">Or continue on your phone:</p>
47
+ <QrLink url={portalTarget} size={140} />
48
+ </div>
49
+ ) : null}
35
50
  {widget === "unlinked" ? (
36
51
  <p className="hint">
37
52
  The floating agent launcher appears here once the app is bound to a
@@ -1 +1,14 @@
1
1
  /// <reference types="vite/client" />
2
+
3
+ interface ImportMetaEnv {
4
+ /**
5
+ * Optional external-script hook (guuey#303): a URL loaded at boot — the
6
+ * demo tour's bundle rides this in the demo apps; unset in customer
7
+ * builds.
8
+ */
9
+ readonly VITE_DEMO_TOUR_SRC?: string;
10
+ }
11
+
12
+ interface ImportMeta {
13
+ readonly env: ImportMetaEnv;
14
+ }
@@ -15,9 +15,11 @@ locally with one command, and deployable to guuey with one more.
15
15
  ├── prompts/system.md # system prompt, referenced from guuey.json#agent.systemPrompt.file
16
16
  ├── mcps/todo/ # the "copy-me" custom MCP server — @modelcontextprotocol/sdk,
17
17
  │ # Streamable HTTP, in-memory todo list, port :6782
18
- ├── ggui/ # ggui.json + blueprints/ + themes/ — generative-UI config.
19
- │ # `ggui serve` runs against this locally; it's pushed to your
20
- │ # deployed app's guuey-managed ggui instance on `guuey deploy`.
18
+ ├── ggui/ # ggui.json + blueprints/ — generative-UI config.
19
+ │ # `ggui serve` runs against this locally; `guuey deploy` pushes
20
+ │ # generation config + blueprints (the deployed RENDER THEME is
21
+ │ # platform data, not this dir — ggui.json#theme is local-preview
22
+ │ # only, kept in step with guuey.app.json by `pnpm bootstrap`).
21
23
  ├── web/ # the product frontend (Vite + React on @guuey/chat):
22
24
  │ # landing (widget) · login (guest + BYO-OIDC seam) · home
23
25
  │ # (status + distribution guide) · the chat surface
@@ -127,7 +129,7 @@ anything user-visible changes:
127
129
  as its own hosted MCP server (build → deploy → registry), then writes the
128
130
  resulting server id back into `guuey.json`. This scaffold has none — the
129
131
  todo MCP is `colocated`, so this leg is a no-op here.
130
- 2. **ggui asset leg** — pushes `ggui/` (ggui.json, blueprints, themes) to your
132
+ 2. **ggui asset leg** — pushes `ggui/` (generation config, blueprints) to your
131
133
  app's guuey-managed ggui instance.
132
134
  3. **Agent leg** — builds `src/worker.ts` into `guuey.worker.js`, packs the
133
135
  project root (including `mcps/`), and deploys it as a gVisor-isolated,
@@ -12,12 +12,12 @@
12
12
  },
13
13
  "dependencies": {
14
14
  "@openai/agents": "^0.12.0",
15
- "@guuey/config": "0.10.0",
16
- "@guuey/worker": "0.10.0"
15
+ "@guuey/config": "0.11.0",
16
+ "@guuey/worker": "0.11.0"
17
17
  },
18
18
  "devDependencies": {
19
19
  "@ggui-ai/cli": "0.6.3",
20
- "@guuey/cli": "0.10.0",
20
+ "@guuey/cli": "0.11.0",
21
21
  "tsup": "^8.5.0",
22
22
  "tsx": "^4.19.0",
23
23
  "typescript": "^5.8.0"
@@ -121,6 +121,27 @@ async function guuey(args) {
121
121
  }
122
122
  }
123
123
 
124
+ // ── ggui.json theme sync (guuey#302 scaffold hygiene) ───────────────────────
125
+
126
+ /**
127
+ * Keep `ggui/ggui.json`'s theme MODE in step with guuey.app.json so the
128
+ * local `ggui serve` preview matches the site (the scaffold used to ship
129
+ * the two contradicting each other: light site, dark ggui preview). Only
130
+ * `mode` syncs — `preset` is ggui-side vocabulary and stays the author's
131
+ * choice. NOTE: the deployed render's theme is platform data (`guuey
132
+ * deploy` deliberately ignores this block; guuey#304 owns the production
133
+ * side) — this sync is about local-preview honesty.
134
+ */
135
+ function syncGguiTheme(config) {
136
+ const gguiJsonPath = join(projectRoot, "ggui", "ggui.json");
137
+ if (!existsSync(gguiJsonPath)) return;
138
+ const ggui = JSON.parse(readFileSync(gguiJsonPath, "utf8"));
139
+ if (ggui.theme?.mode === config.theme.mode) return;
140
+ ggui.theme = { ...(ggui.theme ?? {}), mode: config.theme.mode };
141
+ writeFileSync(gguiJsonPath, `${JSON.stringify(ggui, null, 2)}\n`, "utf8");
142
+ console.log(`ggui/ggui.json theme mode → ${config.theme.mode} (local preview matches the site).`);
143
+ }
144
+
124
145
  // ── AGENTS.md managed block ─────────────────────────────────────────────────
125
146
 
126
147
  function managedBlock(config) {
@@ -367,6 +388,7 @@ async function main() {
367
388
  // extraction (`--example`), the demo chrome turns off here.
368
389
  config.demoMode = false;
369
390
  writeConfig(config);
391
+ syncGguiTheme(config);
370
392
  regenerateAgentsMd(config);
371
393
 
372
394
  console.log(`\nConfigured "${name}" — guuey.app.json written, AGENTS.md updated.`);
@@ -9,14 +9,16 @@
9
9
  "typecheck": "tsc --noEmit"
10
10
  },
11
11
  "dependencies": {
12
- "@guuey/chat": "0.10.0",
12
+ "@guuey/chat": "0.11.0",
13
13
  "oidc-client-ts": "^3.1.0",
14
+ "qrcode": "^1.5.4",
14
15
  "react": "^19.0.0",
15
16
  "react-dom": "^19.0.0",
16
17
  "react-router-dom": "^7.1.0"
17
18
  },
18
19
  "devDependencies": {
19
20
  "@types/node": "^24.0.0",
21
+ "@types/qrcode": "^1.5.5",
20
22
  "@types/react": "^19.0.0",
21
23
  "@types/react-dom": "^19.0.0",
22
24
  "@vitejs/plugin-react": "^4.3.4",
@@ -10,11 +10,39 @@
10
10
  */
11
11
  import { useEffect, useState, type CSSProperties } from "react";
12
12
  import { GuueyChat } from "@guuey/chat/react";
13
+ import type { PlanViewSummary, ViewRefItem } from "@guuey/chat";
13
14
  import { agentEndpointUrl, appConfig, historyBaseUrl } from "../config";
14
15
  import { currentIdentityMode, ensureGuestSecret } from "../lib/identity";
15
16
  import { getBearerToken, currentUser, oidcConfigured } from "../lib/oidc";
16
17
 
17
- export function AgentChat({ className, style }: { className?: string; style?: CSSProperties }) {
18
+ /**
19
+ * The chat-rail bridge (agentic-app shell): with `viewsBridge` set, the
20
+ * chat runs the kit's CHIPS presentation — generative views collapse to
21
+ * compact chips in the rail, the full renders belong to the host's main
22
+ * canvas (fed by `onViewsChange`), and chip clicks re-select
23
+ * (`onViewRef` → `promotedViewKey`, the browser-history mechanic).
24
+ */
25
+ export interface ViewsBridge {
26
+ promotedViewKey?: string | undefined;
27
+ onViewRef: (item: ViewRefItem) => void;
28
+ onViewsChange: (views: PlanViewSummary[]) => void;
29
+ }
30
+
31
+ // Module scope on purpose: props want STABLE identities. An inline
32
+ // `policy={{ … }}` literal (or per-render arrow getters) re-mints every
33
+ // render; the kit stabilizes structurally since 0.11.0, but the template
34
+ // models the correct shape — one identity for the app's whole life.
35
+ const RAIL_POLICY = { view: { timeoutMs: 8000, presentation: "chips" as const } };
36
+
37
+ export function AgentChat({
38
+ className,
39
+ style,
40
+ viewsBridge,
41
+ }: {
42
+ className?: string;
43
+ style?: CSSProperties;
44
+ viewsBridge?: ViewsBridge;
45
+ }) {
18
46
  // The user's CHOSEN mode wins: an explicit "Continue as guest" must never
19
47
  // be shadowed by a cached OIDC session. Only when no choice is recorded
20
48
  // does a live session imply oidc.
@@ -43,11 +71,21 @@ export function AgentChat({ className, style }: { className?: string; style?: CS
43
71
  mode: appConfig.theme.mode,
44
72
  className,
45
73
  style,
74
+ ...(viewsBridge !== undefined
75
+ ? {
76
+ policy: RAIL_POLICY,
77
+ promotedViewKey: viewsBridge.promotedViewKey,
78
+ onViewRef: viewsBridge.onViewRef,
79
+ onViewsChange: viewsBridge.onViewsChange,
80
+ }
81
+ : {}),
46
82
  };
47
83
 
84
+ // The lib functions ARE the stable getters — no wrapping arrows (a fresh
85
+ // closure per render is the identity churn RAIL_POLICY exists to avoid).
48
86
  return identity === "oidc" ? (
49
- <GuueyChat {...shared} getAccessToken={() => getBearerToken()} />
87
+ <GuueyChat {...shared} getAccessToken={getBearerToken} />
50
88
  ) : (
51
- <GuueyChat {...shared} getGuestSecret={() => ensureGuestSecret()} />
89
+ <GuueyChat {...shared} getGuestSecret={ensureGuestSecret} />
52
90
  );
53
91
  }
@@ -0,0 +1,25 @@
1
+ /** A QR code for a URL, with the plain link beneath — mobile hand-off. */
2
+ import { useEffect, useRef, useState } from "react";
3
+ import QRCode from "qrcode";
4
+
5
+ export function QrLink({ url, size = 200 }: { url: string; size?: number }) {
6
+ const canvasRef = useRef<HTMLCanvasElement | null>(null);
7
+ const [error, setError] = useState<string | null>(null);
8
+
9
+ useEffect(() => {
10
+ if (!canvasRef.current) return;
11
+ QRCode.toCanvas(canvasRef.current, url, { width: size, margin: 1 }).catch((err: unknown) => {
12
+ setError(err instanceof Error ? err.message : String(err));
13
+ });
14
+ }, [url, size]);
15
+
16
+ return (
17
+ <span className="qr-link">
18
+ <canvas ref={canvasRef} className="qr" />
19
+ <a href={url} target="_blank" rel="noreferrer">
20
+ {url}
21
+ </a>
22
+ {error ? <span className="error">QR render failed: {error}</span> : null}
23
+ </span>
24
+ );
25
+ }
@@ -17,6 +17,20 @@ document.documentElement.dataset.mode = appConfig.theme.mode;
17
17
  document.documentElement.style.setProperty("--app-accent", appConfig.theme.accent);
18
18
  document.title = appConfig.brand.name;
19
19
 
20
+ // Optional external script (the analytics-snippet pattern): a build-time
21
+ // env names a script URL and this app loads it — the demo tour rides this
22
+ // hook (its bundle lives OUTSIDE the template; guuey#303). The script can
23
+ // anchor on the `data-tour` attributes the pages carry and listen for the
24
+ // `demo:render-complete` CustomEvent the app shell dispatches when a
25
+ // generative view first lands on the canvas.
26
+ const tourSrc = import.meta.env.VITE_DEMO_TOUR_SRC;
27
+ if (tourSrc !== undefined && tourSrc !== "") {
28
+ const script = document.createElement("script");
29
+ script.src = tourSrc;
30
+ script.defer = true;
31
+ document.head.appendChild(script);
32
+ }
33
+
20
34
  const router = createBrowserRouter([
21
35
  {
22
36
  element: <Shell />,
@@ -8,6 +8,7 @@ import { Link } from "react-router-dom";
8
8
  import { appConfig, isLinked } from "../config";
9
9
  import { CHAT_PATH, HOME_PATH } from "../routes";
10
10
  import { mountWidget, type WidgetOutcome } from "../lib/widget";
11
+ import { QrLink } from "../components/QrLink";
11
12
 
12
13
  export function Landing() {
13
14
  const [widget, setWidget] = useState<WidgetOutcome | "unlinked" | "loading">(
@@ -19,19 +20,33 @@ export function Landing() {
19
20
  mountWidget((outcome) => setWidget(outcome));
20
21
  }, []);
21
22
 
23
+ // The demo posture (spec §2.3): a REAL product site whose hero's one job
24
+ // is getting the visitor INTO the demo — big CTA to the console page +
25
+ // a QR to continue on mobile via the portal.
26
+ const portalTarget =
27
+ appConfig.link !== null
28
+ ? `${appConfig.link.portalUrl}/agent/${appConfig.link.slug ?? appConfig.link.appId}`
29
+ : null;
30
+
22
31
  return (
23
32
  <main className="page landing">
24
- <section className="hero">
33
+ <section className="hero" data-tour="hero">
25
34
  <h1>{appConfig.copy.landing.headline}</h1>
26
35
  <p>{appConfig.copy.landing.sub}</p>
27
36
  <div className="hero-actions">
28
37
  <Link to={CHAT_PATH} className="btn btn-accent">
29
- Open the chat
38
+ {appConfig.demoMode ? "Enter the demo" : "Open the chat"}
30
39
  </Link>
31
40
  <Link to={HOME_PATH} className="btn">
32
41
  How this app is wired
33
42
  </Link>
34
43
  </div>
44
+ {appConfig.demoMode && portalTarget !== null ? (
45
+ <div className="hero-qr">
46
+ <p className="hint">Or continue on your phone:</p>
47
+ <QrLink url={portalTarget} size={140} />
48
+ </div>
49
+ ) : null}
35
50
  {widget === "unlinked" ? (
36
51
  <p className="hint">
37
52
  The floating agent launcher appears here once the app is bound to a
@@ -1 +1,14 @@
1
1
  /// <reference types="vite/client" />
2
+
3
+ interface ImportMetaEnv {
4
+ /**
5
+ * Optional external-script hook (guuey#303): a URL loaded at boot — the
6
+ * demo tour's bundle rides this in the demo apps; unset in customer
7
+ * builds.
8
+ */
9
+ readonly VITE_DEMO_TOUR_SRC?: string;
10
+ }
11
+
12
+ interface ImportMeta {
13
+ readonly env: ImportMetaEnv;
14
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@guuey/create-agentic-app",
3
- "version": "0.10.0",
3
+ "version": "0.11.0",
4
4
  "description": "Scaffold a guuey agentic app: code-mode agent + custom MCP + ggui + web, local pnpm dev, one-command guuey deploy.",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -25,8 +25,8 @@
25
25
  "tsup": "^8.5.0",
26
26
  "typescript": "^5.8.0",
27
27
  "vitest": "^3.2.4",
28
- "@guuey/config": "0.10.0",
29
- "@guuey/worker": "0.10.0"
28
+ "@guuey/config": "0.11.0",
29
+ "@guuey/worker": "0.11.0"
30
30
  },
31
31
  "publishConfig": {
32
32
  "access": "public"
@@ -1,14 +0,0 @@
1
- /**
2
- * The fullscreen agent — the whole main canvas is the chat, so generative
3
- * UI cards render at real size. This is the same configured `<AgentChat>`
4
- * every chat surface in this template uses.
5
- */
6
- import { AgentChat } from "../components/AgentChat";
7
-
8
- export function AgentCanvas() {
9
- return (
10
- <div className="agent-canvas">
11
- <AgentChat className="chat-fill" />
12
- </div>
13
- );
14
- }
@@ -1,14 +0,0 @@
1
- /**
2
- * The fullscreen agent — the whole main canvas is the chat, so generative
3
- * UI cards render at real size. This is the same configured `<AgentChat>`
4
- * every chat surface in this template uses.
5
- */
6
- import { AgentChat } from "../components/AgentChat";
7
-
8
- export function AgentCanvas() {
9
- return (
10
- <div className="agent-canvas">
11
- <AgentChat className="chat-fill" />
12
- </div>
13
- );
14
- }
@@ -1,14 +0,0 @@
1
- /**
2
- * The fullscreen agent — the whole main canvas is the chat, so generative
3
- * UI cards render at real size. This is the same configured `<AgentChat>`
4
- * every chat surface in this template uses.
5
- */
6
- import { AgentChat } from "../components/AgentChat";
7
-
8
- export function AgentCanvas() {
9
- return (
10
- <div className="agent-canvas">
11
- <AgentChat className="chat-fill" />
12
- </div>
13
- );
14
- }