Skip to content

Commit 15c02a2

Browse files
committed
create server integration script
1 parent 30ead77 commit 15c02a2

File tree

2 files changed

+188
-0
lines changed

2 files changed

+188
-0
lines changed
Lines changed: 187 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,187 @@
1+
#!/usr/bin/env node
2+
3+
const args = process.argv.slice(2);
4+
5+
const hasMiddleware = args.includes("--middleware");
6+
const hasServer = args.includes("--server");
7+
8+
function showHelp() {
9+
console.log(`
10+
Usage: pnpm create-integration [OPTIONS] <integration-name>
11+
12+
Create a new AG-UI integration
13+
14+
OPTIONS:
15+
--middleware Create a middleware-based integration
16+
--server Create a server-based integration
17+
18+
ARGUMENTS:
19+
<integration-name> Name of the integration in kebab-case (e.g., my-integration)
20+
21+
EXAMPLES:
22+
pnpm create-integration --middleware my-integration
23+
pnpm create-integration --server my-api-integration
24+
`);
25+
}
26+
27+
if (!hasMiddleware && !hasServer) {
28+
showHelp();
29+
process.exit(1);
30+
}
31+
32+
if (hasMiddleware && hasServer) {
33+
console.error("Error: Cannot specify both --middleware and --server");
34+
showHelp();
35+
process.exit(1);
36+
}
37+
38+
// Get the integration name (should be after the flag)
39+
const integrationName = args.find((arg) => !arg.startsWith("--"));
40+
41+
if (!integrationName) {
42+
console.error("Error: Integration name is required");
43+
showHelp();
44+
process.exit(1);
45+
}
46+
47+
// Validate kebab-case format: lowercase letters, numbers, and hyphens only
48+
// Must start with a letter, cannot start or end with hyphen, no consecutive hyphens
49+
const kebabCaseRegex = /^[a-z][a-z0-9]*(-[a-z0-9]+)*$/;
50+
51+
if (!kebabCaseRegex.test(integrationName)) {
52+
console.error(`Error: Integration name "${integrationName}" is not in valid kebab-case format`);
53+
console.error("Valid kebab-case examples: my-integration, api-client, my-api-123");
54+
showHelp();
55+
process.exit(1);
56+
}
57+
58+
if (hasMiddleware) {
59+
console.log(`Creating middleware-based integration: ${integrationName}`);
60+
// TODO: Implement middleware integration creation
61+
}
62+
63+
if (hasServer) {
64+
console.log(`Creating server-based integration: ${integrationName}`);
65+
66+
const { execSync } = require("child_process");
67+
const path = require("path");
68+
const fs = require("fs");
69+
70+
const integrationsDir = path.join(__dirname, "integrations");
71+
const sourceDir = path.join(integrationsDir, "server-starter");
72+
const targetDir = path.join(integrationsDir, integrationName);
73+
74+
// Check if source directory exists
75+
if (!fs.existsSync(sourceDir)) {
76+
console.error(`Error: Template directory not found: ${sourceDir}`);
77+
process.exit(1);
78+
}
79+
80+
// Check if target directory already exists
81+
if (fs.existsSync(targetDir)) {
82+
console.error(`Error: Integration directory already exists: ${targetDir}`);
83+
process.exit(1);
84+
}
85+
86+
try {
87+
console.log(
88+
`Copying template from integrations/server-starter to integrations/${integrationName}...`,
89+
);
90+
execSync(`cp -r "${sourceDir}" "${targetDir}"`, { stdio: "inherit" });
91+
console.log(`✓ Created integration at integrations/${integrationName}`);
92+
93+
// Update package.json
94+
const packageJsonPath = path.join(targetDir, "package.json");
95+
console.log(`Updating package.json...`);
96+
97+
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, "utf8"));
98+
packageJson.name = `@ag-ui/${integrationName}`;
99+
100+
fs.writeFileSync(packageJsonPath, JSON.stringify(packageJson, null, 2) + "\n");
101+
console.log(`✓ Updated package name to @ag-ui/${integrationName}`);
102+
103+
// Convert kebab-case to PascalCase
104+
const pascalCaseName = integrationName
105+
.split("-")
106+
.map((word) => word.charAt(0).toUpperCase() + word.slice(1))
107+
.join("");
108+
const agentClassName = `${pascalCaseName}Agent`;
109+
110+
// Update src/index.ts
111+
const indexPath = path.join(targetDir, "src", "index.ts");
112+
console.log(`Updating src/index.ts...`);
113+
114+
let indexContent = fs.readFileSync(indexPath, "utf8");
115+
indexContent = indexContent.replace(
116+
/ServerStarterAgent/g,
117+
agentClassName
118+
);
119+
120+
fs.writeFileSync(indexPath, indexContent);
121+
console.log(`✓ Updated class name to ${agentClassName}`);
122+
123+
// Update apps/dojo/src/menu.ts
124+
const menuPath = path.join(__dirname, "apps", "dojo", "src", "menu.ts");
125+
console.log(`Updating apps/dojo/src/menu.ts...`);
126+
127+
let menuContent = fs.readFileSync(menuPath, "utf8");
128+
129+
const newIntegration = ` {
130+
id: "${integrationName}",
131+
name: "${pascalCaseName}",
132+
features: ["agentic_chat"],
133+
},\n`;
134+
135+
// Find the menuIntegrations array and prepend the new integration
136+
menuContent = menuContent.replace(
137+
/(export const menuIntegrations: MenuIntegrationConfig\[\] = \[\n)/,
138+
`$1${newIntegration}`
139+
);
140+
141+
fs.writeFileSync(menuPath, menuContent);
142+
console.log(`✓ Registered integration in dojo menu`);
143+
144+
// Update apps/dojo/src/agents.ts
145+
const agentsPath = path.join(__dirname, "apps", "dojo", "src", "agents.ts");
146+
console.log(`Updating apps/dojo/src/agents.ts...`);
147+
148+
let agentsContent = fs.readFileSync(agentsPath, "utf8");
149+
150+
const newAgentIntegration = ` {
151+
id: "${integrationName}",
152+
agents: async () => {
153+
return {
154+
agentic_chat: new ${agentClassName}(),
155+
}
156+
},
157+
},\n`;
158+
159+
// Find the agentsIntegrations array and prepend the new integration
160+
agentsContent = agentsContent.replace(
161+
/(export const agentsIntegrations: AgentIntegrationConfig\[\] = \[\n)/,
162+
`$1${newAgentIntegration}`
163+
);
164+
165+
fs.writeFileSync(agentsPath, agentsContent);
166+
console.log(`✓ Registered agent in dojo agents`);
167+
168+
// Update apps/dojo/package.json
169+
const dojoPackageJsonPath = path.join(__dirname, "apps", "dojo", "package.json");
170+
console.log(`Updating apps/dojo/package.json...`);
171+
172+
const dojoPackageJson = JSON.parse(fs.readFileSync(dojoPackageJsonPath, "utf8"));
173+
174+
// Add the new integration as a dependency at the beginning
175+
const newDependencies = {
176+
[`@ag-ui/${integrationName}`]: "workspace:*",
177+
...dojoPackageJson.dependencies
178+
};
179+
dojoPackageJson.dependencies = newDependencies;
180+
181+
fs.writeFileSync(dojoPackageJsonPath, JSON.stringify(dojoPackageJson, null, 2) + "\n");
182+
console.log(`✓ Added @ag-ui/${integrationName} to dojo dependencies`);
183+
} catch (error) {
184+
console.error("Error creating integration:", error);
185+
process.exit(1);
186+
}
187+
}

typescript-sdk/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
"format": "prettier --write \"**/*.{ts,tsx,md,mdx}\"",
1313
"check-types": "turbo run check-types",
1414
"test": "turbo run test",
15+
"create-integration": "pnpm dlx tsx create-integration.ts",
1516
"bump": "pnpm --filter './packages/*' exec -- pnpm version",
1617
"bump:alpha": "pnpm --filter './packages/*' exec -- pnpm version --preid alpha",
1718
"publish": "pnpm -r clean && pnpm install && turbo run build && pnpm publish -r --filter='./packages/*'",

0 commit comments

Comments
 (0)