create-adk-agent 0.0.2 → 0.0.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.
Files changed (37) hide show
  1. package/README.md +10 -1
  2. package/bin/create-adk-agent.js +361 -35
  3. package/dist/generators/init/files/.env.example.template +16 -0
  4. package/dist/generators/init/files/.eslintrc.json.template +20 -0
  5. package/dist/generators/init/files/.gitignore.template +27 -0
  6. package/dist/generators/init/files/.prettierrc.template +7 -0
  7. package/dist/generators/init/files/README.md.template +243 -0
  8. package/dist/generators/init/files/jest.config.ts.template +7 -0
  9. package/dist/generators/init/files/package.json.template +41 -0
  10. package/dist/generators/init/files/src/agents/basic/agent.ts.template +34 -0
  11. package/dist/generators/init/files/src/agents/multi-tool/agent.ts.template +83 -0
  12. package/dist/generators/init/files/src/agents/streaming/agent.ts.template +36 -0
  13. package/dist/generators/init/files/src/agents/team/farewell-agent.ts.template +43 -0
  14. package/dist/generators/init/files/src/agents/team/greeting-agent.ts.template +43 -0
  15. package/dist/generators/init/files/src/agents/team/root-agent.ts.template +18 -0
  16. package/dist/generators/init/files/src/agents/workflow/agent.ts.template +69 -0
  17. package/dist/generators/init/files/src/index.ts.template +61 -0
  18. package/dist/generators/init/files/tests/agents.test.ts.template +80 -0
  19. package/dist/generators/init/files/tsconfig.json.template +20 -0
  20. package/dist/generators/init/files/vite.config.ts.template +36 -0
  21. package/dist/generators/init/generator.js +3 -0
  22. package/dist/generators/init/generator.js.map +1 -1
  23. package/dist/generators/init/schema.json +124 -0
  24. package/package.json +20 -4
  25. package/src/generators/init/files/README.md.template +3 -2
  26. package/src/generators/init/files/package.json.template +8 -6
  27. package/src/generators/init/files/vite.config.ts.template +36 -0
  28. package/templates/basic/.env.example +16 -0
  29. package/templates/basic/.eslintrc.json +13 -0
  30. package/templates/basic/.prettierrc +5 -0
  31. package/templates/basic/README.md +155 -0
  32. package/templates/basic/_gitignore +8 -0
  33. package/templates/basic/jest.config.ts +8 -0
  34. package/templates/basic/package.json +41 -0
  35. package/templates/basic/src/index.ts +60 -0
  36. package/templates/basic/tests/agents.test.ts +19 -0
  37. package/templates/basic/tsconfig.json +21 -0
package/README.md CHANGED
@@ -12,8 +12,15 @@ Scaffold production-ready ADK TypeScript projects in seconds with best practices
12
12
  npx create-adk-agent my-agent
13
13
  ```
14
14
 
15
+ Or use it non-interactively:
16
+
17
+ ```bash
18
+ npx create-adk-agent my-agent --template=basic --modelProvider=gemini --model=gemini-2.0-flash
19
+ ```
20
+
15
21
  That's it! You'll get a fully configured ADK project with:
16
- - ✅ TypeScript configured for ADK
22
+
23
+ - ✅ TypeScript configured for ADK (ES2022, NodeNext)
17
24
  - ✅ Environment setup with API key validation
18
25
  - ✅ Working agent examples from official docs
19
26
  - ✅ Hot reload development with tsx
@@ -25,6 +32,7 @@ That's it! You'll get a fully configured ADK project with:
25
32
  Instead of manually setting up TypeScript, configuring ESM, managing API keys, and copying examples from docs, `create-adk-agent` does it all for you in one command.
26
33
 
27
34
  **Perfect for:**
35
+
28
36
  - 🎓 Learning ADK quickly with working examples
29
37
  - 🚀 Starting new ADK projects without boilerplate hassle
30
38
  - 📦 Getting ADK best practices out of the box
@@ -76,6 +84,7 @@ npx create-adk-agent my-agent
76
84
  ```
77
85
 
78
86
  You'll choose:
87
+
79
88
  - Which agent templates to include
80
89
  - Your preferred model provider (Gemini, OpenAI, or Anthropic)
81
90
  - Whether to install dependencies now
@@ -1,38 +1,364 @@
1
1
  #!/usr/bin/env node
2
2
 
3
- import { execSync } from 'child_process';
4
- import { fileURLToPath } from 'url';
5
- import { dirname, resolve } from 'path';
6
-
7
- const __filename = fileURLToPath(import.meta.url);
8
- const __dirname = dirname(__filename);
9
-
10
- // Get the arguments passed to the script (excluding node and script path)
11
- const args = process.argv.slice(2);
12
-
13
- // Check if a project name was provided
14
- if (args.length === 0) {
15
- console.error('❌ Error: Please provide a project name');
16
- console.error('Usage: npx create-adk-agent <project-name> [options]');
17
- console.error('');
18
- console.error('Example:');
19
- console.error(' npx create-adk-agent my-agent');
20
- console.error(' npx create-adk-agent my-agent --templates=basic,multi-tool --no-interactive');
21
- process.exit(1);
22
- }
23
-
24
- // Build the nx command
25
- const generatorPath = resolve(__dirname, '..');
26
- const command = `npx --yes nx g ${generatorPath}:init ${args.join(' ')}`;
27
-
28
- console.log('🚀 Creating your ADK agent project...');
29
- console.log('');
30
-
31
- try {
32
- // Execute the nx generator command
33
- execSync(command, { stdio: 'inherit', cwd: process.cwd() });
34
- } catch (error) {
35
- console.error('');
36
- console.error(' Failed to create project');
37
- process.exit(1);
3
+ // @ts-check
4
+ import { blue, cyan, green, red, reset, yellow } from 'kolorist';
5
+ import minimist from 'minimist';
6
+ import fs from 'node:fs';
7
+ import path from 'node:path';
8
+ import { fileURLToPath } from 'node:url';
9
+ import prompts from 'prompts';
10
+
11
+ const argv = minimist(process.argv.slice(2), { string: ['_'] });
12
+ const cwd = process.cwd();
13
+
14
+ // Template configurations
15
+ const TEMPLATES = [
16
+ {
17
+ name: 'basic',
18
+ display: 'Basic Agent',
19
+ description: 'Single agent with one tool (time example)',
20
+ color: green,
21
+ },
22
+ {
23
+ name: 'multi-tool',
24
+ display: 'Multi-Tool Agent',
25
+ description: 'Agent with multiple tools (recommended)',
26
+ color: blue,
27
+ },
28
+ {
29
+ name: 'streaming',
30
+ display: 'Streaming Agent',
31
+ description: 'Agent with Live API streaming support',
32
+ color: cyan,
33
+ },
34
+ {
35
+ name: 'team',
36
+ display: 'Multi-Agent Team',
37
+ description: 'Multiple agents working together',
38
+ color: yellow,
39
+ },
40
+ {
41
+ name: 'workflow',
42
+ display: 'Workflow Agent',
43
+ description: 'Sequential and parallel execution patterns',
44
+ color: yellow,
45
+ },
46
+ ];
47
+
48
+ // Model provider configurations
49
+ const MODEL_PROVIDERS = {
50
+ gemini: {
51
+ name: 'Google Gemini',
52
+ models: [
53
+ 'gemini-2.0-flash',
54
+ 'gemini-2.0-flash-thinking-exp-01-21',
55
+ 'gemini-1.5-pro',
56
+ ],
57
+ apiKeyVar: 'GEMINI_API_KEY',
58
+ importStatement: "import { LlmAgent, FunctionTool } from '@google/adk';",
59
+ modelConfig: (model) => `'${model}'`,
60
+ },
61
+ openai: {
62
+ name: 'OpenAI',
63
+ models: ['openai/gpt-4o', 'openai/gpt-4o-mini', 'openai/gpt-4-turbo'],
64
+ apiKeyVar: 'OPENAI_API_KEY',
65
+ importStatement:
66
+ "import { LlmAgent, FunctionTool, LiteLlm } from '@google/adk';",
67
+ modelConfig: (model) => `new LiteLlm({ model: '${model}' })`,
68
+ },
69
+ anthropic: {
70
+ name: 'Anthropic (Claude)',
71
+ models: [
72
+ 'anthropic/claude-3-5-sonnet',
73
+ 'anthropic/claude-3-opus',
74
+ 'anthropic/claude-3-haiku',
75
+ ],
76
+ apiKeyVar: 'ANTHROPIC_API_KEY',
77
+ importStatement:
78
+ "import { LlmAgent, FunctionTool, LiteLlm } from '@google/adk';",
79
+ modelConfig: (model) => `new LiteLlm({ model: '${model}' })`,
80
+ },
81
+ };
82
+
83
+ const renameFiles = {
84
+ _gitignore: '.gitignore',
85
+ };
86
+
87
+ function formatTargetDir(targetDir) {
88
+ return targetDir?.trim().replace(/\/+$/g, '');
89
+ }
90
+
91
+ function isValidPackageName(projectName) {
92
+ return /^(?:@[a-z0-9-*~][a-z0-9-*._~]*\/)?[a-z0-9-~][a-z0-9-._~]*$/.test(
93
+ projectName,
94
+ );
95
+ }
96
+
97
+ function toValidPackageName(projectName) {
98
+ return projectName
99
+ .trim()
100
+ .toLowerCase()
101
+ .replace(/\s+/g, '-')
102
+ .replace(/^[._]/, '')
103
+ .replace(/[^a-z0-9-~]+/g, '-');
104
+ }
105
+
106
+ function copy(src, dest) {
107
+ const stat = fs.statSync(src);
108
+ if (stat.isDirectory()) {
109
+ copyDir(src, dest);
110
+ } else {
111
+ fs.copyFileSync(src, dest);
112
+ }
113
+ }
114
+
115
+ function copyDir(srcDir, destDir) {
116
+ fs.mkdirSync(destDir, { recursive: true });
117
+ for (const file of fs.readdirSync(srcDir)) {
118
+ const srcFile = path.resolve(srcDir, file);
119
+ const destFile = path.resolve(destDir, file);
120
+ copy(srcFile, destFile);
121
+ }
122
+ }
123
+
124
+ function isEmpty(pathToCheck) {
125
+ const files = fs.readdirSync(pathToCheck);
126
+ return files.length === 0 || (files.length === 1 && files[0] === '.git');
127
+ }
128
+
129
+ function emptyDir(dir) {
130
+ if (!fs.existsSync(dir)) {
131
+ return;
132
+ }
133
+ for (const file of fs.readdirSync(dir)) {
134
+ fs.rmSync(path.resolve(dir, file), { recursive: true, force: true });
135
+ }
136
+ }
137
+
138
+ function replacePlaceholders(root, files, config) {
139
+ for (const file of Array.isArray(files) ? files : [files]) {
140
+ const filePath = path.join(root, file);
141
+ if (!fs.existsSync(filePath)) continue;
142
+
143
+ const fileContent = fs.readFileSync(filePath, 'utf-8');
144
+ const newFileContent = Object.keys(config).reduce(
145
+ (content, placeholder) =>
146
+ content.replace(new RegExp(placeholder, 'g'), config[placeholder]),
147
+ fileContent,
148
+ );
149
+ fs.writeFileSync(filePath, newFileContent);
150
+ }
151
+ }
152
+
153
+ async function init() {
154
+ let targetDir = formatTargetDir(argv._[0]);
155
+ let template = argv.template || argv.t;
156
+ let modelProvider = argv.modelProvider || argv.mp;
157
+ let model = argv.model || argv.m;
158
+ let description = argv.description || argv.d;
159
+
160
+ const defaultTargetDir = 'my-adk-agent';
161
+ const getProjectName = () =>
162
+ targetDir === '.' ? path.basename(path.resolve()) : targetDir;
163
+
164
+ let result = {};
165
+
166
+ try {
167
+ result = await prompts(
168
+ [
169
+ {
170
+ type: targetDir ? null : 'text',
171
+ name: 'projectName',
172
+ message: reset('Project name:'),
173
+ initial: defaultTargetDir,
174
+ onState: (state) => {
175
+ targetDir = formatTargetDir(state.value) || defaultTargetDir;
176
+ },
177
+ },
178
+ {
179
+ type: () =>
180
+ !fs.existsSync(targetDir) || isEmpty(targetDir) ? null : 'confirm',
181
+ name: 'overwrite',
182
+ message: () =>
183
+ (targetDir === '.'
184
+ ? 'Current directory'
185
+ : `Target directory "${targetDir}"`) +
186
+ ` is not empty. Remove existing files and continue?`,
187
+ },
188
+ {
189
+ type: (_, { overwrite } = {}) => {
190
+ if (overwrite === false) {
191
+ throw new Error(red('✖') + ' Operation cancelled');
192
+ }
193
+ return null;
194
+ },
195
+ name: 'overwriteChecker',
196
+ },
197
+ {
198
+ type: () => (isValidPackageName(getProjectName()) ? null : 'text'),
199
+ name: 'packageName',
200
+ message: reset('Package name:'),
201
+ initial: () => toValidPackageName(getProjectName()),
202
+ validate: (dir) =>
203
+ isValidPackageName(dir) || 'Invalid package.json name',
204
+ },
205
+ {
206
+ type: description ? null : 'text',
207
+ name: 'description',
208
+ message: reset('Project description:'),
209
+ initial: 'My ADK Agent',
210
+ },
211
+ {
212
+ type: template ? null : 'select',
213
+ name: 'template',
214
+ message: reset('Select a template:'),
215
+ choices: TEMPLATES.map((t) => ({
216
+ title: `${t.color(t.display)} - ${t.description}`,
217
+ value: t.name,
218
+ })),
219
+ },
220
+ {
221
+ type: modelProvider ? null : 'select',
222
+ name: 'modelProvider',
223
+ message: reset('Select LLM provider:'),
224
+ choices: Object.entries(MODEL_PROVIDERS).map(([key, value]) => ({
225
+ title: value.name,
226
+ value: key,
227
+ })),
228
+ },
229
+ {
230
+ type: (prev, values) => {
231
+ const provider = modelProvider || values.modelProvider;
232
+ return model || !provider ? null : 'select';
233
+ },
234
+ name: 'model',
235
+ message: reset('Select model:'),
236
+ choices: (prev, values) => {
237
+ const provider = modelProvider || values.modelProvider;
238
+ return MODEL_PROVIDERS[provider].models.map((m) => ({
239
+ title: m,
240
+ value: m,
241
+ }));
242
+ },
243
+ },
244
+ ],
245
+ {
246
+ onCancel: () => {
247
+ throw new Error(red('✖') + ' Operation cancelled');
248
+ },
249
+ },
250
+ );
251
+ } catch (cancelled) {
252
+ console.log(cancelled.message);
253
+ return;
254
+ }
255
+
256
+ // Extract values from prompts
257
+ const {
258
+ overwrite,
259
+ packageName,
260
+ description: promptDescription,
261
+ template: promptTemplate,
262
+ modelProvider: promptModelProvider,
263
+ model: promptModel,
264
+ } = result;
265
+
266
+ // Use CLI args or prompt values
267
+ template = template || promptTemplate || 'basic';
268
+ modelProvider = modelProvider || promptModelProvider || 'gemini';
269
+ model = model || promptModel || MODEL_PROVIDERS[modelProvider].models[0];
270
+ description = description || promptDescription || 'My ADK Agent';
271
+
272
+ const root = path.join(cwd, targetDir);
273
+
274
+ if (overwrite) {
275
+ emptyDir(root);
276
+ } else if (!fs.existsSync(root)) {
277
+ fs.mkdirSync(root, { recursive: true });
278
+ }
279
+
280
+ console.log(`\n${green('✓')} Scaffolding project in ${root}...\n`);
281
+
282
+ const templateDir = path.resolve(
283
+ fileURLToPath(import.meta.url),
284
+ '..',
285
+ '..',
286
+ 'templates',
287
+ template,
288
+ );
289
+
290
+ if (!fs.existsSync(templateDir)) {
291
+ console.error(red(`✖ Error: Template "${template}" not found!`));
292
+ process.exit(1);
293
+ }
294
+
295
+ const write = (file, content) => {
296
+ const targetPath = renameFiles[file]
297
+ ? path.join(root, renameFiles[file])
298
+ : path.join(root, file);
299
+
300
+ if (content) {
301
+ fs.writeFileSync(targetPath, content);
302
+ } else {
303
+ copy(path.join(templateDir, file), targetPath);
304
+ }
305
+ };
306
+
307
+ // Copy all template files
308
+ const files = fs.readdirSync(templateDir);
309
+ for (const file of files.filter((f) => f !== 'package.json')) {
310
+ write(file);
311
+ }
312
+
313
+ // Handle package.json separately
314
+ const pkg = JSON.parse(
315
+ fs.readFileSync(path.join(templateDir, 'package.json'), 'utf-8'),
316
+ );
317
+ pkg.name = packageName || getProjectName();
318
+ pkg.description = description;
319
+
320
+ write('package.json', JSON.stringify(pkg, null, 2));
321
+
322
+ // Replace placeholders
323
+ const providerConfig = MODEL_PROVIDERS[modelProvider];
324
+ const modelConfig = providerConfig.modelConfig(model);
325
+
326
+ replacePlaceholders(root, 'README.md', {
327
+ __PROJECT_NAME__: pkg.name,
328
+ __DESCRIPTION__: description,
329
+ });
330
+
331
+ replacePlaceholders(root, 'src/index.ts', {
332
+ __MODEL_CONFIG__: modelConfig,
333
+ });
334
+
335
+ // Update imports if needed (for LiteLLM)
336
+ if (modelProvider !== 'gemini') {
337
+ replacePlaceholders(root, 'src/index.ts', {
338
+ "import { LlmAgent, FunctionTool } from '@google/adk';":
339
+ providerConfig.importStatement,
340
+ });
341
+ }
342
+
343
+ console.log(`${green('✓')} Project created successfully!\n`);
344
+ console.log(`${cyan('Next steps:')}\n`);
345
+
346
+ if (root !== cwd) {
347
+ console.log(` cd ${path.relative(cwd, root)}`);
348
+ }
349
+
350
+ console.log(` npm install`);
351
+ console.log(` cp .env.example .env`);
352
+ console.log(` ${yellow('# Edit .env and add your API key')}`);
353
+ console.log(` npm run dev\n`);
354
+
355
+ console.log(`${cyan('Configuration:')}`);
356
+ console.log(` Template: ${green(template)}`);
357
+ console.log(` Provider: ${green(providerConfig.name)}`);
358
+ console.log(` Model: ${green(model)}`);
359
+ console.log(` API Key: ${yellow(providerConfig.apiKeyVar)}\n`);
38
360
  }
361
+
362
+ init().catch((e) => {
363
+ console.error(e);
364
+ });
@@ -0,0 +1,16 @@
1
+ # Google Gemini API Key
2
+ # Get your key from: https://aistudio.google.com/apikey
3
+ GEMINI_API_KEY=your_google_api_key_here
4
+
5
+ # OR use Vertex AI (for Google Cloud)
6
+ # GOOGLE_GENAI_USE_VERTEXAI=true
7
+ # GOOGLE_CLOUD_PROJECT=your-project-id
8
+ # GOOGLE_CLOUD_LOCATION=us-central1
9
+
10
+ # OpenAI API Key (if using OpenAI models)
11
+ # Get your key from: https://platform.openai.com/api-keys
12
+ # OPENAI_API_KEY=your_openai_api_key_here
13
+
14
+ # Anthropic API Key (if using Claude models)
15
+ # Get your key from: https://console.anthropic.com/settings/keys
16
+ # ANTHROPIC_API_KEY=your_anthropic_api_key_here
@@ -0,0 +1,20 @@
1
+ {
2
+ "parser": "@typescript-eslint/parser",
3
+ "extends": [
4
+ "eslint:recommended",
5
+ "plugin:@typescript-eslint/recommended"
6
+ ],
7
+ "plugins": ["@typescript-eslint"],
8
+ "env": {
9
+ "node": true,
10
+ "es2022": true
11
+ },
12
+ "parserOptions": {
13
+ "ecmaVersion": 2022,
14
+ "sourceType": "module"
15
+ },
16
+ "rules": {
17
+ "@typescript-eslint/no-unused-vars": ["warn", { "argsIgnorePattern": "^_" }],
18
+ "@typescript-eslint/no-explicit-any": "warn"
19
+ }
20
+ }
@@ -0,0 +1,27 @@
1
+ # Environment files - NEVER commit these!
2
+ .env
3
+ .env.local
4
+ .env*.local
5
+
6
+ # Dependencies
7
+ node_modules/
8
+
9
+ # Build output
10
+ dist/
11
+
12
+ # IDE
13
+ .vscode/
14
+ .idea/
15
+
16
+ # OS
17
+ .DS_Store
18
+ Thumbs.db
19
+
20
+ # Test coverage
21
+ coverage/
22
+
23
+ # Logs
24
+ *.log
25
+ npm-debug.log*
26
+ yarn-debug.log*
27
+ yarn-error.log*
@@ -0,0 +1,7 @@
1
+ {
2
+ "semi": true,
3
+ "trailingComma": "es5",
4
+ "singleQuote": true,
5
+ "printWidth": 80,
6
+ "tabWidth": 2
7
+ }