Skip to content

Commit 87540d7

Browse files
Harden generated-agent scaffolding and onboarding for AI-assisted development.
Require explicit Arcade gateway configuration with actionable setup guidance, add doctor preflight checks, and improve template/docs ergonomics for Cursor/Claude Code/Codex customization workflows. Co-authored-by: Cursor <cursoragent@cursor.com>
1 parent ea89c1c commit 87540d7

File tree

41 files changed

+507
-38
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

41 files changed

+507
-38
lines changed

.github/workflows/ci.yml

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,16 @@ jobs:
131131
working-directory: ci-test-${{ matrix.template }}
132132
run: cp .env.example .env
133133

134+
- name: Doctor (TypeScript templates)
135+
if: matrix.template != 'langchain'
136+
working-directory: ci-test-${{ matrix.template }}
137+
run: npm run doctor
138+
139+
- name: Doctor (Python template)
140+
if: matrix.template == 'langchain'
141+
working-directory: ci-test-${{ matrix.template }}
142+
run: python -m app.doctor
143+
134144
- name: Type check (TypeScript templates)
135145
if: matrix.template != 'langchain'
136146
working-directory: ci-test-${{ matrix.template }}

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,3 +5,4 @@ dist/
55
.env.local
66
.next/
77
__pycache__/
8+
.tmp

AGENTS.md

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
# AGENTS.md
2+
3+
Guidance for AI coding agents working in this repository.
4+
5+
## Repo Purpose
6+
7+
`create-arcade-agent` scaffolds starter projects that teach Arcade MCP Gateway concepts and produce working, customizable agent apps.
8+
9+
## High-Signal Workflow
10+
11+
1. Edit source templates in `templates/**` and CLI logic in `src/**`.
12+
2. Run quality checks:
13+
- `npm run lint`
14+
- `npm run format:check`
15+
- `npm run typecheck`
16+
- `npm run build`
17+
3. Validate scaffolding behavior via CI-equivalent smoke flow:
18+
- `node dist/index.js <name> --template <ai-sdk|mastra|langchain>`
19+
20+
## Editing Rules
21+
22+
- Keep generated templates simple and beginner-friendly.
23+
- Prefer MCP Gateway patterns over direct SDK-specific concepts.
24+
- Preserve/extend `CUSTOMIZATION POINT` and `AI-EDIT-SAFE` markers.
25+
- Avoid introducing framework-specific complexity unless required.
26+
- Keep OAuth/security guidance aligned with Arcade docs (custom verifier for production user-facing apps).
27+
28+
## Important Locations
29+
30+
- `src/` — generator runtime logic
31+
- `templates/_shared/` — shared assets injected into generated projects
32+
- `templates/ai-sdk/`, `templates/mastra/`, `templates/langchain/` — template-specific code
33+
- `.github/workflows/ci.yml` — source of truth for validation gates

README.md

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,18 @@ The generated agent is a daily planning and triage assistant that connects to Sl
2121

2222
All three templates connect to Arcade's MCP Gateway for tool discovery and execution. The TypeScript templates (`ai-sdk` and `mastra`) share a common Next.js frontend. The Python template (`langchain`) uses server-rendered HTML with SSE streaming.
2323

24+
## What Is an MCP Gateway?
25+
26+
An MCP Gateway is a managed tool endpoint in Arcade that your agent connects to via one URL.
27+
28+
Benefits:
29+
30+
- **One connection point** -- use one `ARCADE_GATEWAY_URL` instead of wiring many tool servers
31+
- **Tool curation** -- choose exactly which tools your agent can see
32+
- **Faster iteration** -- update tool access in Arcade without changing integration code
33+
- **Cleaner model context** -- smaller, focused toolsets improve tool selection reliability
34+
- **Portable setup** -- same gateway pattern works across frameworks and MCP clients
35+
2436
## Usage
2537

2638
### Interactive mode
@@ -164,6 +176,15 @@ The default port is `8765` for the langchain template and `3000` for the Next.js
164176

165177
Make sure you are running Node.js >= 18. Check with `node --version`.
166178

179+
### `ARCADE_GATEWAY_URL` is missing
180+
181+
If the app says `ARCADE_GATEWAY_URL is missing`:
182+
183+
1. Create a gateway at [app.arcade.dev/mcp-gateways](https://app.arcade.dev/mcp-gateways)
184+
2. Add these toolkits: Slack, Google Calendar, Linear, GitHub, Gmail
185+
3. Copy the gateway URL into `.env` as `ARCADE_GATEWAY_URL`
186+
4. Retry connection in the app
187+
167188
### Python virtual environment issues
168189

169190
The CLI creates a `.venv` directory automatically. If it fails:

src/prompts.ts

Lines changed: 21 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,26 @@
11
import * as p from "@clack/prompts";
22
import { existsSync, readdirSync, readFileSync } from "fs";
33
import { resolve, join } from "path";
4+
import { parseArgs } from "util";
45
import type { TemplateMeta } from "./types.js";
56

7+
function parseCli(argv: string[]): { projectName?: string; template?: string } {
8+
const parsed = parseArgs({
9+
args: argv.slice(2),
10+
options: {
11+
template: { type: "string" },
12+
},
13+
allowPositionals: true,
14+
strict: false,
15+
});
16+
17+
const projectName = parsed.positionals.find((arg) => !arg.startsWith("-"));
18+
const template = typeof parsed.values.template === "string" ? parsed.values.template : undefined;
19+
return { projectName, template };
20+
}
21+
622
export async function getProjectName(argv: string[]): Promise<string> {
7-
let projectName = argv[2];
8-
if (projectName === "--template") projectName = "";
23+
let projectName = parseCli(argv).projectName ?? "";
924
if (!projectName) {
1025
const result = await p.text({
1126
message: "What is your project name?",
@@ -23,7 +38,7 @@ export async function getProjectName(argv: string[]): Promise<string> {
2338
}
2439
projectName = result;
2540
}
26-
return projectName;
41+
return projectName.trim();
2742
}
2843

2944
function discoverTemplates(templatesDir: string): { meta: TemplateMeta; dir: string }[] {
@@ -52,10 +67,9 @@ export async function getTemplate(
5267
process.exit(1);
5368
}
5469

55-
// Check --template flag
56-
const flagIdx = argv.indexOf("--template");
57-
if (flagIdx !== -1 && argv[flagIdx + 1]) {
58-
const val = argv[flagIdx + 1];
70+
const cliTemplate = parseCli(argv).template;
71+
if (cliTemplate) {
72+
const val = cliTemplate;
5973
const match = templates.find((t) => t.meta.name === val);
6074
if (!match) {
6175
const names = templates.map((t) => `"${t.meta.name}"`).join(", ");
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
# Agent Playbook
2+
3+
This project is intentionally structured so coding agents can safely customize it.
4+
5+
## Safe Edit Zones
6+
7+
Look for these markers:
8+
9+
- `CUSTOMIZATION POINT` — expected user customization area
10+
- `AI-EDIT-SAFE` — safe for automated edits
11+
- `AI-EDIT-CAUTION` — integration-sensitive; edit carefully
12+
13+
## First Customization Steps
14+
15+
{{#if (eq name "langchain")}}
16+
1. Edit `app/system-prompt.md` to change agent behavior.
17+
2. Change model choice in `app/agent.py`.
18+
3. Extend schema in `app/models.py` if you need app-specific data.
19+
4. Ensure `ARCADE_GATEWAY_URL` is set and the gateway has the expected tools.
20+
{{else if (eq name "mastra")}}
21+
1. Edit `src/mastra/agents/system-prompt.md` to change agent behavior.
22+
2. Change model choice in `src/mastra/agents/triage-agent.ts`.
23+
3. Extend schema in `lib/db/schema.ts` if you need app-specific data.
24+
4. Ensure `ARCADE_GATEWAY_URL` is set and the gateway has the expected tools.
25+
{{else}}
26+
1. Edit `lib/system-prompt.md` to change agent behavior.
27+
2. Change model choice in `lib/agent.ts`.
28+
3. Extend schema in `lib/db/schema.ts` if you need app-specific data.
29+
4. Ensure `ARCADE_GATEWAY_URL` is set and the gateway has the expected tools.
30+
{{/if}}
31+
32+
## Gateway Checklist
33+
34+
Create/configure your gateway at `https://app.arcade.dev/mcp-gateways` and add:
35+
36+
- Slack
37+
- Google Calendar
38+
- Linear
39+
- GitHub
40+
- Gmail
41+
42+
## Verification Commands
43+
44+
{{#if (eq name "langchain")}}
45+
```bash
46+
python -m app.doctor
47+
ruff check app/
48+
ty check .
49+
```
50+
{{else}}
51+
```bash
52+
npm run doctor
53+
npm run typecheck
54+
npm run lint
55+
```
56+
{{/if}}

templates/_shared/nextjs-ui/app/api/auth/login/route.ts

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,18 +4,28 @@ import { db } from "@/lib/db";
44
import { users } from "@/lib/db/schema";
55
import { verifyPassword, createSession } from "@/lib/auth";
66

7+
const EMAIL_REGEX = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
8+
79
export async function POST(request: Request) {
8-
let body;
10+
let body: unknown;
911
try {
1012
body = await request.json();
1113
} catch {
1214
return NextResponse.json({ error: "Invalid request body" }, { status: 400 });
1315
}
14-
const { email, password } = body;
16+
const email = typeof (body as { email?: unknown }).email === "string"
17+
? (body as { email: string }).email.trim().toLowerCase()
18+
: "";
19+
const password = typeof (body as { password?: unknown }).password === "string"
20+
? (body as { password: string }).password
21+
: "";
1522

1623
if (!email || !password) {
1724
return NextResponse.json({ error: "Email and password are required" }, { status: 400 });
1825
}
26+
if (!EMAIL_REGEX.test(email)) {
27+
return NextResponse.json({ error: "Invalid email address" }, { status: 400 });
28+
}
1929

2030
const user = await db.query.users.findFirst({
2131
where: eq(users.email, email),

templates/_shared/nextjs-ui/app/api/auth/register/route.ts

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,18 +4,31 @@ import { db } from "@/lib/db";
44
import { users } from "@/lib/db/schema";
55
import { hashPassword, createSession } from "@/lib/auth";
66

7+
const EMAIL_REGEX = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
8+
79
export async function POST(request: Request) {
8-
let body;
10+
let body: unknown;
911
try {
1012
body = await request.json();
1113
} catch {
1214
return NextResponse.json({ error: "Invalid request body" }, { status: 400 });
1315
}
14-
const { email, password } = body;
16+
const email = typeof (body as { email?: unknown }).email === "string"
17+
? (body as { email: string }).email.trim().toLowerCase()
18+
: "";
19+
const password = typeof (body as { password?: unknown }).password === "string"
20+
? (body as { password: string }).password
21+
: "";
1522

1623
if (!email || !password) {
1724
return NextResponse.json({ error: "Email and password are required" }, { status: 400 });
1825
}
26+
if (!EMAIL_REGEX.test(email)) {
27+
return NextResponse.json({ error: "Invalid email address" }, { status: 400 });
28+
}
29+
if (password.length < 8) {
30+
return NextResponse.json({ error: "Password must be at least 8 characters" }, { status: 400 });
31+
}
1932

2033
const existing = await db.query.users.findFirst({
2134
where: eq(users.email, email),

templates/_shared/nextjs-ui/app/dashboard/page.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,8 @@ function DashboardContent() {
127127
const messages: Record<string, string> = {
128128
auth_incomplete: "Authorization was not completed. Please try connecting again.",
129129
auth_failed: "Authorization failed. Please try again.",
130+
gateway_missing:
131+
"ARCADE_GATEWAY_URL is missing. Create one at https://app.arcade.dev/mcp-gateways, add Slack, Google Calendar, Linear, GitHub, and Gmail, then set ARCADE_GATEWAY_URL in .env.",
130132
verify_failed: "User verification failed. Please try again.",
131133
};
132134
setError(messages[urlError] || `Authentication error: ${urlError}`);

templates/_shared/nextjs-ui/lib/db/schema.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { sqliteTable, text, integer } from "drizzle-orm/sqlite-core";
22

3+
// AI-EDIT-SAFE: application schema extension
34
// --- CUSTOMIZATION POINT ---
45
// Add more tables here for your domain.
56
// See: https://orm.drizzle.team/docs/column-types/sqlite

0 commit comments

Comments
 (0)