@guuey/create-agentic-app 0.1.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 (75) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +31 -0
  3. package/dist/chunk-JD4RAKEG.js +203 -0
  4. package/dist/chunk-U7JDNY6B.js +149 -0
  5. package/dist/cli.cjs +293 -0
  6. package/dist/cli.d.cts +1 -0
  7. package/dist/cli.d.ts +1 -0
  8. package/dist/cli.js +128 -0
  9. package/dist/index.cjs +233 -0
  10. package/dist/index.d.cts +76 -0
  11. package/dist/index.d.ts +76 -0
  12. package/dist/index.js +10 -0
  13. package/dist/templates/claude-agent-sdk/.env.example +4 -0
  14. package/dist/templates/claude-agent-sdk/.mcp.json +1 -0
  15. package/dist/templates/claude-agent-sdk/README.md +107 -0
  16. package/dist/templates/claude-agent-sdk/ggui/blueprints/.gitkeep +0 -0
  17. package/dist/templates/claude-agent-sdk/ggui/ggui.json +15 -0
  18. package/dist/templates/claude-agent-sdk/ggui/themes/.gitkeep +0 -0
  19. package/dist/templates/claude-agent-sdk/guuey.json +13 -0
  20. package/dist/templates/claude-agent-sdk/mcps/todo/Dockerfile +46 -0
  21. package/dist/templates/claude-agent-sdk/mcps/todo/package.json +22 -0
  22. package/dist/templates/claude-agent-sdk/mcps/todo/src/server.ts +173 -0
  23. package/dist/templates/claude-agent-sdk/mcps/todo/tsconfig.json +15 -0
  24. package/dist/templates/claude-agent-sdk/package.json +22 -0
  25. package/dist/templates/claude-agent-sdk/pnpm-workspace.yaml +24 -0
  26. package/dist/templates/claude-agent-sdk/prompts/system.md +4 -0
  27. package/dist/templates/claude-agent-sdk/scripts/dev.mjs +44 -0
  28. package/dist/templates/claude-agent-sdk/src/agent-config.ts +70 -0
  29. package/dist/templates/claude-agent-sdk/src/worker.ts +89 -0
  30. package/dist/templates/claude-agent-sdk/tsconfig.json +15 -0
  31. package/dist/templates/claude-agent-sdk/tsup.config.ts +9 -0
  32. package/dist/templates/claude-agent-sdk/web/.env.example +17 -0
  33. package/dist/templates/claude-agent-sdk/web/index.html +13 -0
  34. package/dist/templates/claude-agent-sdk/web/package.json +25 -0
  35. package/dist/templates/claude-agent-sdk/web/sandbox-proxy.ts +239 -0
  36. package/dist/templates/claude-agent-sdk/web/src/App.tsx +272 -0
  37. package/dist/templates/claude-agent-sdk/web/src/main.tsx +12 -0
  38. package/dist/templates/claude-agent-sdk/web/src/useAgentChat.ts +122 -0
  39. package/dist/templates/claude-agent-sdk/web/src/vite-env.d.ts +27 -0
  40. package/dist/templates/claude-agent-sdk/web/tsconfig.json +20 -0
  41. package/dist/templates/claude-agent-sdk/web/vite.config.ts +12 -0
  42. package/dist/templates/mcp-base/Dockerfile +46 -0
  43. package/dist/templates/mcp-base/package.json +22 -0
  44. package/dist/templates/mcp-base/src/server.ts +113 -0
  45. package/dist/templates/mcp-base/tsconfig.json +15 -0
  46. package/dist/templates/openai-agents-sdk/.env.example +4 -0
  47. package/dist/templates/openai-agents-sdk/.mcp.json +1 -0
  48. package/dist/templates/openai-agents-sdk/README.md +107 -0
  49. package/dist/templates/openai-agents-sdk/ggui/blueprints/.gitkeep +0 -0
  50. package/dist/templates/openai-agents-sdk/ggui/ggui.json +15 -0
  51. package/dist/templates/openai-agents-sdk/ggui/themes/.gitkeep +0 -0
  52. package/dist/templates/openai-agents-sdk/guuey.json +13 -0
  53. package/dist/templates/openai-agents-sdk/mcps/todo/Dockerfile +46 -0
  54. package/dist/templates/openai-agents-sdk/mcps/todo/package.json +22 -0
  55. package/dist/templates/openai-agents-sdk/mcps/todo/src/server.ts +173 -0
  56. package/dist/templates/openai-agents-sdk/mcps/todo/tsconfig.json +15 -0
  57. package/dist/templates/openai-agents-sdk/package.json +22 -0
  58. package/dist/templates/openai-agents-sdk/pnpm-workspace.yaml +24 -0
  59. package/dist/templates/openai-agents-sdk/prompts/system.md +4 -0
  60. package/dist/templates/openai-agents-sdk/scripts/dev.mjs +44 -0
  61. package/dist/templates/openai-agents-sdk/src/agent-config.ts +70 -0
  62. package/dist/templates/openai-agents-sdk/src/worker.ts +75 -0
  63. package/dist/templates/openai-agents-sdk/tsconfig.json +15 -0
  64. package/dist/templates/openai-agents-sdk/tsup.config.ts +9 -0
  65. package/dist/templates/openai-agents-sdk/web/.env.example +17 -0
  66. package/dist/templates/openai-agents-sdk/web/index.html +13 -0
  67. package/dist/templates/openai-agents-sdk/web/package.json +25 -0
  68. package/dist/templates/openai-agents-sdk/web/sandbox-proxy.ts +239 -0
  69. package/dist/templates/openai-agents-sdk/web/src/App.tsx +272 -0
  70. package/dist/templates/openai-agents-sdk/web/src/main.tsx +12 -0
  71. package/dist/templates/openai-agents-sdk/web/src/useAgentChat.ts +122 -0
  72. package/dist/templates/openai-agents-sdk/web/src/vite-env.d.ts +27 -0
  73. package/dist/templates/openai-agents-sdk/web/tsconfig.json +20 -0
  74. package/dist/templates/openai-agents-sdk/web/vite.config.ts +12 -0
  75. package/package.json +45 -0
@@ -0,0 +1,46 @@
1
+ # todo-mcp — guuey hosted MCP server image.
2
+ #
3
+ # Self-sufficient multi-stage build: `guuey mcp deploy` packs this directory
4
+ # into a tarball and hands it straight to the builder with no external
5
+ # pre-build step and no build args — this Dockerfile installs its own deps
6
+ # and builds itself. (Different from guuey's own first-party MCP servers,
7
+ # which use a "copy-prebuilt" pattern that expects `dist/` + `node_modules/`
8
+ # to already exist before `docker build` runs — that pattern does not apply
9
+ # to a standalone project like this one.)
10
+ #
11
+ # Build + run locally:
12
+ #
13
+ # docker build -t todo-mcp .
14
+ # docker run -p 8080:8080 todo-mcp
15
+ # curl http://localhost:8080/health
16
+ #
17
+ # Deploy to guuey:
18
+ #
19
+ # cd mcps/todo && guuey mcp deploy
20
+
21
+ FROM node:22-slim AS build
22
+ WORKDIR /app
23
+ COPY package.json ./
24
+ RUN npm install
25
+ COPY tsconfig.json ./
26
+ COPY src ./src
27
+ RUN npm run build
28
+
29
+ FROM node:22-slim
30
+ WORKDIR /app
31
+ ENV NODE_ENV=production
32
+ # guuey's hosted-MCP runtime injects PORT=8080 and probes GET /health at that
33
+ # port — this default matches so `docker run` alone also works.
34
+ ENV PORT=8080
35
+
36
+ COPY package.json ./
37
+ RUN npm install --omit=dev
38
+ COPY --from=build /app/dist ./dist
39
+
40
+ USER node
41
+ EXPOSE 8080
42
+
43
+ HEALTHCHECK --interval=30s --timeout=5s --start-period=10s --retries=3 \
44
+ CMD node -e "fetch('http://localhost:' + (process.env.PORT || 8080) + '/health').then(r => { if (!r.ok) throw 1 }).catch(() => process.exit(1))"
45
+
46
+ CMD ["node", "dist/server.js"]
@@ -0,0 +1,22 @@
1
+ {
2
+ "name": "@agentic-app-template/todo-mcp",
3
+ "version": "0.0.0",
4
+ "private": true,
5
+ "type": "module",
6
+ "scripts": {
7
+ "dev": "tsx src/server.ts",
8
+ "build": "tsup src/server.ts --format esm -d dist",
9
+ "start": "node dist/server.js",
10
+ "typecheck": "tsc --noEmit"
11
+ },
12
+ "dependencies": {
13
+ "@modelcontextprotocol/sdk": "^1.27.0",
14
+ "zod": "^4.0.0"
15
+ },
16
+ "devDependencies": {
17
+ "@types/node": "^22.0.0",
18
+ "tsx": "^4.19.0",
19
+ "tsup": "^8.5.0",
20
+ "typescript": "^5.8.0"
21
+ }
22
+ }
@@ -0,0 +1,173 @@
1
+ /**
2
+ * todo-mcp — the copy-me example MCP server for @agentic-app-template.
3
+ *
4
+ * A minimal, stateless Streamable HTTP MCP server: four tools over an
5
+ * in-memory todo list. Swap the `Map` for a real database when you outgrow
6
+ * a single replica; everything else (transport wiring, tool shape) stays
7
+ * the same.
8
+ *
9
+ * Transport pattern mirrors guuey's canonical hosted-MCP scaffold
10
+ * (`mcp-servers/_template` in the guuey monorepo): a fresh `McpServer` +
11
+ * `StreamableHTTPServerTransport` are created **per request** and torn down
12
+ * when the response closes. Stateless mode — the guuey proxy (and, locally,
13
+ * whatever MCP client you point at this server) handles sessions, so this
14
+ * server never keeps request state around between calls.
15
+ *
16
+ * Runtime contract when deployed via `guuey mcp deploy` (see mcps/todo's
17
+ * Dockerfile): the platform injects `PORT=8080` and probes `GET /health`
18
+ * for both liveness and readiness. Locally, `pnpm dev` runs this on
19
+ * `PORT=6782` (see ../../scripts/dev.mjs).
20
+ */
21
+ import { createServer, type IncomingMessage, type ServerResponse } from "node:http";
22
+ import { randomUUID } from "node:crypto";
23
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
24
+ import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
25
+ import { z } from "zod";
26
+
27
+ interface Todo {
28
+ id: string;
29
+ title: string;
30
+ done: boolean;
31
+ }
32
+
33
+ const todos = new Map<string, Todo>();
34
+
35
+ const todoShape = {
36
+ id: z.string(),
37
+ title: z.string(),
38
+ done: z.boolean(),
39
+ };
40
+
41
+ /** Wrap a structured result as both `structuredContent` and a text block. */
42
+ function toolResult(data: Record<string, unknown>) {
43
+ return {
44
+ structuredContent: data,
45
+ content: [{ type: "text" as const, text: JSON.stringify(data) }],
46
+ };
47
+ }
48
+
49
+ function buildMcpServer(): McpServer {
50
+ const server = new McpServer({
51
+ name: "todo-mcp",
52
+ version: "0.0.0",
53
+ description: "Create, list, toggle, and delete todos.",
54
+ });
55
+
56
+ server.registerTool(
57
+ "todo_list",
58
+ {
59
+ title: "List todos",
60
+ description: "List every todo, in creation order.",
61
+ inputSchema: {},
62
+ outputSchema: { todos: z.array(z.object(todoShape)) },
63
+ },
64
+ async () => toolResult({ todos: [...todos.values()] }),
65
+ );
66
+
67
+ server.registerTool(
68
+ "todo_create",
69
+ {
70
+ title: "Create todo",
71
+ description: "Create a new todo item.",
72
+ inputSchema: { title: z.string().min(1).describe("Todo title.") },
73
+ outputSchema: todoShape,
74
+ },
75
+ async ({ title }) => {
76
+ const todo: Todo = { id: randomUUID(), title, done: false };
77
+ todos.set(todo.id, todo);
78
+ // Spread into a fresh object literal — `toolResult` takes
79
+ // `Record<string, unknown>`, and a named interface like `Todo` isn't
80
+ // structurally assignable to that without an (unwanted) index
81
+ // signature of its own; a fresh literal is.
82
+ return toolResult({ ...todo });
83
+ },
84
+ );
85
+
86
+ server.registerTool(
87
+ "todo_toggle",
88
+ {
89
+ title: "Toggle todo",
90
+ description: "Flip a todo's done state.",
91
+ inputSchema: { id: z.string().describe("Todo id.") },
92
+ outputSchema: todoShape,
93
+ },
94
+ async ({ id }) => {
95
+ const todo = todos.get(id);
96
+ if (!todo) throw new Error(`todo not found: ${id}`);
97
+ todo.done = !todo.done;
98
+ return toolResult({ ...todo }); // fresh literal — see the create() comment above
99
+ },
100
+ );
101
+
102
+ server.registerTool(
103
+ "todo_delete",
104
+ {
105
+ title: "Delete todo",
106
+ description: "Delete a todo by id.",
107
+ inputSchema: { id: z.string().describe("Todo id.") },
108
+ outputSchema: { id: z.string(), deleted: z.boolean() },
109
+ },
110
+ async ({ id }) => {
111
+ const deleted = todos.delete(id);
112
+ return toolResult({ id, deleted });
113
+ },
114
+ );
115
+
116
+ return server;
117
+ }
118
+
119
+ const PORT = Number(process.env.PORT ?? 6782);
120
+
121
+ const httpServer = createServer(async (req: IncomingMessage, res: ServerResponse) => {
122
+ if (req.method === "GET" && req.url === "/health") {
123
+ res.writeHead(200, { "content-type": "application/json" });
124
+ res.end(JSON.stringify({ status: "ok", server: "todo-mcp", version: "0.0.0" }));
125
+ return;
126
+ }
127
+
128
+ if (req.url !== "/mcp") {
129
+ res.writeHead(404, { "content-type": "application/json" }).end(
130
+ JSON.stringify({ error: "not found" }),
131
+ );
132
+ return;
133
+ }
134
+
135
+ // Fresh server + transport per request (stateless mode) — see module doc.
136
+ const mcp = buildMcpServer();
137
+ const transport = new StreamableHTTPServerTransport({ sessionIdGenerator: undefined });
138
+
139
+ res.on("close", () => {
140
+ transport.close().catch(() => undefined);
141
+ mcp.close().catch(() => undefined);
142
+ });
143
+
144
+ try {
145
+ await mcp.connect(transport);
146
+ await transport.handleRequest(req, res);
147
+ } catch (err) {
148
+ console.error("mcp_handle_failed", err);
149
+ if (!res.headersSent) {
150
+ res.writeHead(500, { "content-type": "application/json" });
151
+ res.end(
152
+ JSON.stringify({
153
+ jsonrpc: "2.0",
154
+ error: { code: -32603, message: "Internal server error" },
155
+ id: null,
156
+ }),
157
+ );
158
+ }
159
+ }
160
+ });
161
+
162
+ httpServer.listen(PORT, () => {
163
+ console.log(`todo-mcp listening on :${PORT}`);
164
+ });
165
+
166
+ function shutdown(signal: string): void {
167
+ console.log(`${signal} received — shutting down`);
168
+ httpServer.close(() => process.exit(0));
169
+ // Force-exit if connections don't drain in time.
170
+ setTimeout(() => process.exit(1), 10_000).unref();
171
+ }
172
+ process.on("SIGTERM", () => shutdown("SIGTERM"));
173
+ process.on("SIGINT", () => shutdown("SIGINT"));
@@ -0,0 +1,15 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2023",
4
+ "module": "ESNext",
5
+ "moduleResolution": "bundler",
6
+ "strict": true,
7
+ "esModuleInterop": true,
8
+ "skipLibCheck": true,
9
+ "outDir": "dist",
10
+ "rootDir": "src",
11
+ "resolveJsonModule": true
12
+ },
13
+ "include": ["src/**/*"],
14
+ "exclude": ["node_modules", "dist"]
15
+ }
@@ -0,0 +1,22 @@
1
+ {
2
+ "name": "agentic-app-template",
3
+ "version": "0.0.0",
4
+ "private": true,
5
+ "type": "module",
6
+ "scripts": {
7
+ "dev": "node scripts/dev.mjs",
8
+ "build": "tsup",
9
+ "typecheck": "tsc --noEmit"
10
+ },
11
+ "dependencies": {
12
+ "@anthropic-ai/claude-agent-sdk": "^0.3.199",
13
+ "@guuey/config": "0.0.1",
14
+ "@guuey/worker": "0.0.1"
15
+ },
16
+ "devDependencies": {
17
+ "@guuey/cli": "0.0.1",
18
+ "tsup": "^8.5.0",
19
+ "tsx": "^4.19.0",
20
+ "typescript": "^5.8.0"
21
+ }
22
+ }
@@ -0,0 +1,24 @@
1
+ packages:
2
+ - "mcps/*"
3
+ - "web"
4
+
5
+ # pnpm 11 default `strictDepBuilds: true` blocks install scripts for any
6
+ # dependency with a build script (esbuild's native binary fetch, sharp,
7
+ # etc.) unless individually allowlisted, and fails the whole `install`
8
+ # with ERR_PNPM_IGNORED_BUILDS otherwise. Scaffolded apps pull in build-
9
+ # script deps transitively (tsup/vite -> esbuild, mcp/ui deps, etc.) that
10
+ # we can't enumerate up front, so match pnpm 9/10 behavior here.
11
+ strictDepBuilds: false
12
+
13
+ # pnpm 11 default `verifyDepsBeforeRun: 'install'` re-verifies the
14
+ # lockfile against supply-chain policies (including minimumReleaseAge)
15
+ # before every `pnpm run <script>`, hitting the registry even for
16
+ # already-installed deps. That check 404s for any dependency that only
17
+ # exists as a local tarball (not yet published), which is expected
18
+ # during local/dev/CI installs. Restore pnpm 9/10 behavior: only verify
19
+ # on an explicit `pnpm install`.
20
+ verifyDepsBeforeRun: false
21
+
22
+ # See above — same registry-lookback problem hits a plain `pnpm install`
23
+ # too when minimumReleaseAge is nonzero; disable it for scaffolded apps.
24
+ minimumReleaseAge: 0
@@ -0,0 +1,4 @@
1
+ You are the agentic-app-template assistant. You have a todo tool server —
2
+ use it to create, list, toggle, and delete the user's todos. When the ggui
3
+ server is available, prefer rendering rich UI for lists and forms over
4
+ plain text.
@@ -0,0 +1,44 @@
1
+ #!/usr/bin/env node
2
+ // pnpm dev — boots the whole local stack. Ctrl-C tears everything down.
3
+ import { spawn } from "node:child_process";
4
+ import { existsSync } from "node:fs";
5
+
6
+ const procs = [];
7
+ function boot(name, command, args, opts = {}) {
8
+ const child = spawn(command, args, { stdio: ["ignore", "pipe", "pipe"], ...opts });
9
+ const prefix = `[${name}]`.padEnd(9);
10
+ child.stdout.on("data", (d) => process.stdout.write(String(d).replace(/^/gm, prefix)));
11
+ child.stderr.on("data", (d) => process.stderr.write(String(d).replace(/^/gm, prefix)));
12
+ child.on("exit", (code) => {
13
+ if (code !== 0 && !shuttingDown) shutdown(`${name} exited (${code})`, 1);
14
+ });
15
+ procs.push(child);
16
+ }
17
+ let shuttingDown = false;
18
+ function shutdown(reason, code = 0) {
19
+ if (shuttingDown) return;
20
+ shuttingDown = true;
21
+ console.log(`\n${reason} — shutting down`);
22
+ for (const p of procs) p.kill("SIGTERM");
23
+ setTimeout(() => process.exit(code), 500);
24
+ }
25
+ process.on("SIGINT", () => shutdown("interrupted"));
26
+ process.on("SIGTERM", () => shutdown("terminated"));
27
+
28
+ if (!existsSync(".env.local"))
29
+ console.warn("hint: cp .env.example .env.local and set your LLM key");
30
+
31
+ boot("worker", "pnpm", ["exec", "tsup", "--watch"]); // rebuilds guuey.worker.js on change
32
+ boot("agent", "pnpm", ["exec", "guuey", "dev", "--serve", "--port", "6790"]);
33
+ boot("todo", "pnpm", ["--filter", "@agentic-app-template/todo-mcp", "dev"], {
34
+ env: { ...process.env, PORT: "6782" },
35
+ });
36
+ boot("ggui", "pnpm", ["exec", "ggui", "serve", "--mcp-only", "--dev-allow-all", "--port", "6781"], {
37
+ cwd: "ggui",
38
+ });
39
+ boot("web", "pnpm", ["--filter", "@agentic-app-template/web", "dev"], {
40
+ env: { ...process.env, PORT: "6890" },
41
+ });
42
+
43
+ console.log("\n agent http://localhost:6790 todo-mcp http://localhost:6782");
44
+ console.log(" ggui http://localhost:6781 web http://localhost:6890\n");
@@ -0,0 +1,70 @@
1
+ /**
2
+ * Snapshot + system-prompt + MCP-endpoint resolution shared by both framework
3
+ * overlays. Duplicated verbatim in `templates-src/frameworks/openai-agents-sdk`
4
+ * by design — template code must be self-contained (no cross-overlay imports),
5
+ * so keep the two copies byte-identical when editing either one.
6
+ */
7
+ import { readFileSync } from "node:fs";
8
+ import { join } from "node:path";
9
+ import { parseGuueyJson, type GuueyAgent } from "@guuey/config";
10
+ import type { Invoke } from "@guuey/worker";
11
+
12
+ /** GUUEY_AGENT_SNAPSHOT (set by the platform pod and by `guuey dev`) wins; guuey.json is the fallback. */
13
+ export function loadAgent(invoke: Invoke): GuueyAgent {
14
+ const env = process.env.GUUEY_AGENT_SNAPSHOT;
15
+ if (env) return JSON.parse(env) as GuueyAgent;
16
+ const raw = JSON.parse(readFileSync(join(invoke.fs.app, "guuey.json"), "utf8"));
17
+ return parseGuueyJson(raw).agent;
18
+ }
19
+
20
+ export function systemPrompt(invoke: Invoke, agent: GuueyAgent): string | undefined {
21
+ const sp = agent.systemPrompt;
22
+ if (typeof sp === "string") return sp;
23
+ if (sp?.file) return readFileSync(join(invoke.fs.app, sp.file), "utf8");
24
+ return undefined;
25
+ }
26
+
27
+ /**
28
+ * One resolved MCP endpoint this worker may connect to. `transport` rides
29
+ * alongside `url`/`headers` (not hardcoded to `"http"`) because a federation
30
+ * credential file — `<session>/.guuey/credentials/<name>.json`, shape
31
+ * `{url, transport, headers}` per `@guuey/host`'s `CredentialFile` — may
32
+ * resolve a server onto the `sse` transport arm.
33
+ */
34
+ export interface McpEndpoint {
35
+ url: string;
36
+ transport: "http" | "sse";
37
+ headers: Record<string, string>;
38
+ }
39
+
40
+ /** Lowered entries are `external`; federation credentials (if any) arrive as per-session files. */
41
+ export function mcpEndpoints(invoke: Invoke, agent: GuueyAgent): Record<string, McpEndpoint> {
42
+ const out: Record<string, McpEndpoint> = {};
43
+ for (const [name, entry] of Object.entries(agent.mcpServers ?? {})) {
44
+ if (entry.kind !== "external") continue; // hosted/proxied are lowered to external before a worker ever runs
45
+ let url = entry.url;
46
+ let transport: "http" | "sse" = entry.transport ?? "http";
47
+ let headers: Record<string, string> = { ...(entry.headers ?? {}) };
48
+ try {
49
+ const cred = JSON.parse(
50
+ readFileSync(join(invoke.fs.session, ".guuey", "credentials", `${name}.json`), "utf8")
51
+ ) as { url: string; transport: "http" | "sse"; headers: Record<string, string> };
52
+ url = cred.url;
53
+ transport = cred.transport;
54
+ headers = cred.headers;
55
+ } catch {
56
+ // no credential file — plain external endpoint; static headers apply
57
+ }
58
+ out[name] = { url, transport, headers };
59
+ }
60
+ return out;
61
+ }
62
+
63
+ /** Fold prior turns into a prompt preamble (v1 keep-it-simple history handling). */
64
+ export function withHistory(invoke: Invoke): string {
65
+ if (invoke.history.length === 0) return invoke.input;
66
+ const lines = invoke.history.map(
67
+ (h) => `${h.role === "agent" ? "Assistant" : "User"}: ${h.text}`
68
+ );
69
+ return `<conversation_history>\n${lines.join("\n")}\n</conversation_history>\n\n${invoke.input}`;
70
+ }
@@ -0,0 +1,89 @@
1
+ /**
2
+ * agentic-app-template's code-mode worker entry (Claude Agent SDK). Reads the
3
+ * resolved agent snapshot + MCP endpoints via `./agent-config.js`, drives one
4
+ * `query()` per invoke, and streams every native SDK message to the Router
5
+ * via `emit.native` (the field spellings below — `mcpServers`/`systemPrompt`/
6
+ * `allowedTools`/`maxTurns`, and the `type === "result" && subtype ===
7
+ * "success"` result-detection — mirror `@guuey/host`'s `buildOptions` /
8
+ * `runInvoke`, the platform's own consumer of this exact snapshot shape).
9
+ */
10
+ import { createRequire } from "node:module";
11
+ import { query } from "@anthropic-ai/claude-agent-sdk";
12
+ import { serveNative } from "@guuey/worker";
13
+ import { loadAgent, systemPrompt, mcpEndpoints, withHistory } from "./agent-config.js";
14
+
15
+ /**
16
+ * This worker is tsup-bundled (`noExternal`), which breaks the SDK's own
17
+ * native-CLI lookup: the SDK resolves `claude-agent-sdk-<platform>-<arch>`
18
+ * relative to its OWN module location, and once inlined into
19
+ * `guuey.worker.js` that location is the bundle's — where no node_modules
20
+ * has the binary. Resolution order here:
21
+ * 1. `GUUEY_CLAUDE_CODE_EXECUTABLE` — set by the guuey pod Router, pointing
22
+ * at the platform image's binary (a deployed `/worker` has no
23
+ * node_modules at all).
24
+ * 2. Anchored resolve — find the *installed* SDK from the bundle's location
25
+ * (it is this app's direct dependency), then resolve the platform binary
26
+ * package from the SDK's real location, exactly as the unbundled SDK
27
+ * would. Covers local `guuey dev --serve`, where the bundle runs next to
28
+ * the app's node_modules.
29
+ * 3. undefined — let the SDK try (and fail with its actionable error).
30
+ */
31
+ function claudeExecutable(): string | undefined {
32
+ if (process.env.GUUEY_CLAUDE_CODE_EXECUTABLE) return process.env.GUUEY_CLAUDE_CODE_EXECUTABLE;
33
+ try {
34
+ const sdkEntry = createRequire(import.meta.url).resolve("@anthropic-ai/claude-agent-sdk");
35
+ return createRequire(sdkEntry).resolve(
36
+ `@anthropic-ai/claude-agent-sdk-${process.platform}-${process.arch}/claude`
37
+ );
38
+ } catch {
39
+ return undefined;
40
+ }
41
+ }
42
+ const pathToClaudeCodeExecutable = claudeExecutable();
43
+
44
+ await serveNative(
45
+ async (invoke, emit) => {
46
+ const agent = loadAgent(invoke);
47
+ const mcpServers = Object.fromEntries(
48
+ Object.entries(mcpEndpoints(invoke, agent)).map(([name, ep]) => [
49
+ name,
50
+ // `alwaysLoad` — this agent's declared MCP servers ARE its tool
51
+ // surface; without it the CLI defers MCP tools behind its ToolSearch
52
+ // built-in (absent here — see `tools: []` below), leaving the model
53
+ // tool-less.
54
+ { type: ep.transport, url: ep.url, headers: ep.headers, alwaysLoad: true },
55
+ ])
56
+ );
57
+ let result = "";
58
+ for await (const message of query({
59
+ prompt: withHistory(invoke),
60
+ options: {
61
+ ...(agent.model ? { model: agent.model } : {}),
62
+ ...(systemPrompt(invoke, agent) ? { systemPrompt: systemPrompt(invoke, agent) } : {}),
63
+ mcpServers,
64
+ // Same posture as `@guuey/host`'s `buildOptions` (options.ts):
65
+ // - allowedTools: the snapshot allowlist verbatim, else every tool
66
+ // from every declared server (`mcp__<server>` prefix match) — MCP
67
+ // calls would otherwise hit the SDK's interactive ask stage, which
68
+ // nothing answers headless, so they'd be silently denied.
69
+ // - tools: [] — purely MCP-driven; no Bash/file/search built-ins.
70
+ // - settingSources: [] — never load the machine's ~/.claude or
71
+ // project settings into this worker (a dev box's logged-in Claude
72
+ // Code MCPs/plugins would leak into the tool catalog).
73
+ // - strictMcpConfig: true — the snapshot's servers only; ignore
74
+ // .mcp.json / user settings / plugins.
75
+ allowedTools: agent.tools?.allowlist ?? Object.keys(mcpServers).map((s) => `mcp__${s}`),
76
+ tools: [],
77
+ settingSources: [],
78
+ strictMcpConfig: true,
79
+ ...(agent.runtime?.maxTurns ? { maxTurns: agent.runtime.maxTurns } : {}),
80
+ ...(pathToClaudeCodeExecutable ? { pathToClaudeCodeExecutable } : {}),
81
+ },
82
+ })) {
83
+ emit.native(JSON.parse(JSON.stringify(message)));
84
+ if (message.type === "result" && message.subtype === "success") result = message.result;
85
+ }
86
+ return result;
87
+ },
88
+ { framework: "claude-agent-sdk", sdkName: "@anthropic-ai/claude-agent-sdk" }
89
+ );
@@ -0,0 +1,15 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2023",
4
+ "module": "ESNext",
5
+ "moduleResolution": "bundler",
6
+ "strict": true,
7
+ "esModuleInterop": true,
8
+ "skipLibCheck": true,
9
+ "noEmit": true,
10
+ "resolveJsonModule": true,
11
+ "isolatedModules": true
12
+ },
13
+ "include": ["src"],
14
+ "exclude": ["node_modules", "dist"]
15
+ }
@@ -0,0 +1,9 @@
1
+ import { defineConfig } from "tsup";
2
+
3
+ export default defineConfig({
4
+ entry: { "guuey.worker": "src/worker.ts" },
5
+ format: ["esm"],
6
+ outDir: ".",
7
+ clean: false,
8
+ noExternal: [/./], // bundle everything — the deploy tarball must be runnable via `node guuey.worker.js`
9
+ });
@@ -0,0 +1,17 @@
1
+ # Vite env for the web chat client. Copy to `web/.env.local` and UNCOMMENT
2
+ # the vars you want to override. (Vite reads env files from this directory —
3
+ # not the project root. Keep unused vars commented out: an empty
4
+ # `VITE_X=` declaration loads as "" — not undefined.)
5
+
6
+ # Agent base URL. Defaults to the local `guuey dev` router (:6790).
7
+ # VITE_AGENT_ENDPOINT_URL=http://localhost:6790
8
+
9
+ # Second-origin MCP-Apps sandbox page for @mcp-ui/client's <AppRenderer>.
10
+ # The MCP Apps spec requires UI resources to mount from a DIFFERENT origin
11
+ # than the app itself (double-iframe sandbox). In local dev this is served
12
+ # automatically on http://127.0.0.1:6891/sandbox.html by the
13
+ # sandboxProxyPlugin in vite.config.ts — leave unset. For a DEPLOYED build,
14
+ # host ../sandbox-proxy.ts's sandbox page on its own origin and point this
15
+ # at it; if unset in production, UI resources show a plain-text notice
16
+ # instead of rendering.
17
+ # VITE_SANDBOX_URL=https://sandbox.example.com/sandbox.html
@@ -0,0 +1,13 @@
1
+ <!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
+ <title>agentic-app-template</title>
7
+ <meta name="description" content="Local dev chat client for your guuey agent." />
8
+ </head>
9
+ <body>
10
+ <div id="root"></div>
11
+ <script type="module" src="/src/main.tsx"></script>
12
+ </body>
13
+ </html>
@@ -0,0 +1,25 @@
1
+ {
2
+ "name": "@agentic-app-template/web",
3
+ "version": "0.0.0",
4
+ "private": true,
5
+ "type": "module",
6
+ "scripts": {
7
+ "dev": "vite --port 6890",
8
+ "build": "tsc && vite build",
9
+ "typecheck": "tsc --noEmit"
10
+ },
11
+ "dependencies": {
12
+ "@mcp-ui/client": "^7.1.1",
13
+ "@silverprotocol/core": "0.1.0",
14
+ "react": "^19.0.0",
15
+ "react-dom": "^19.0.0"
16
+ },
17
+ "devDependencies": {
18
+ "@types/node": "^22.0.0",
19
+ "@types/react": "^19.0.0",
20
+ "@types/react-dom": "^19.0.0",
21
+ "@vitejs/plugin-react": "^4.3.0",
22
+ "typescript": "^5.8.0",
23
+ "vite": "^6.0.0"
24
+ }
25
+ }