@inkeep/create-agents 0.20.1 → 0.21.1

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.
@@ -49,7 +49,7 @@ describe('createAgents - Template and Project ID Logic', () => {
49
49
  vi.mocked(fs.remove).mockResolvedValue(undefined);
50
50
  // Mock templates
51
51
  vi.mocked(getAvailableTemplates).mockResolvedValue([
52
- 'weather-project',
52
+ 'event-planner',
53
53
  'chatbot',
54
54
  'data-analysis',
55
55
  ]);
@@ -72,7 +72,7 @@ describe('createAgents - Template and Project ID Logic', () => {
72
72
  processChdirSpy.mockRestore();
73
73
  });
74
74
  describe('Default behavior (no template or customProjectId)', () => {
75
- it('should use weather-project as default template and project ID', async () => {
75
+ it('should use event-planner as default template and project ID', async () => {
76
76
  await createAgents({
77
77
  dirName: 'test-dir',
78
78
  openAiKey: 'test-openai-key',
@@ -81,18 +81,18 @@ describe('createAgents - Template and Project ID Logic', () => {
81
81
  // Should clone base template and weather-project template
82
82
  expect(cloneTemplate).toHaveBeenCalledTimes(2);
83
83
  expect(cloneTemplate).toHaveBeenCalledWith('https://github.com/inkeep/create-agents-template', expect.any(String));
84
- expect(cloneTemplate).toHaveBeenCalledWith('https://github.com/inkeep/agents-cookbook/template-projects/weather-project', 'src/weather-project', expect.arrayContaining([
84
+ expect(cloneTemplate).toHaveBeenCalledWith('https://github.com/inkeep/agents-cookbook/template-projects/event-planner', 'src/projects/event-planner', expect.arrayContaining([
85
85
  expect.objectContaining({
86
86
  filePath: 'index.ts',
87
87
  replacements: expect.objectContaining({
88
- models: expect.any(Object)
89
- })
90
- })
88
+ models: expect.any(Object),
89
+ }),
90
+ }),
91
91
  ]));
92
92
  // Should not call getAvailableTemplates since no template validation needed
93
93
  expect(getAvailableTemplates).not.toHaveBeenCalled();
94
94
  });
95
- it('should create project with weather-project as project ID', async () => {
95
+ it('should create project with event-planner as project ID', async () => {
96
96
  await createAgents({
97
97
  dirName: 'test-dir',
98
98
  openAiKey: 'test-openai-key',
@@ -117,13 +117,13 @@ describe('createAgents - Template and Project ID Logic', () => {
117
117
  // Should clone base template and the specified template
118
118
  expect(cloneTemplate).toHaveBeenCalledTimes(2);
119
119
  expect(cloneTemplate).toHaveBeenCalledWith('https://github.com/inkeep/create-agents-template', expect.any(String));
120
- expect(cloneTemplate).toHaveBeenCalledWith('https://github.com/inkeep/agents-cookbook/template-projects/chatbot', 'src/chatbot', expect.arrayContaining([
120
+ expect(cloneTemplate).toHaveBeenCalledWith('https://github.com/inkeep/agents-cookbook/template-projects/chatbot', 'src/projects/chatbot', expect.arrayContaining([
121
121
  expect.objectContaining({
122
122
  filePath: 'index.ts',
123
123
  replacements: expect.objectContaining({
124
- models: expect.any(Object)
125
- })
126
- })
124
+ models: expect.any(Object),
125
+ }),
126
+ }),
127
127
  ]));
128
128
  // Check that .env file is created
129
129
  expect(fs.writeFile).toHaveBeenCalledWith('.env', expect.stringContaining('ENVIRONMENT=development'));
@@ -131,7 +131,7 @@ describe('createAgents - Template and Project ID Logic', () => {
131
131
  expect(fs.writeFile).toHaveBeenCalledWith('src/inkeep.config.ts', expect.stringContaining('tenantId: "default"'));
132
132
  });
133
133
  it('should exit with error when template does not exist', async () => {
134
- vi.mocked(getAvailableTemplates).mockResolvedValue(['weather-project', 'chatbot']);
134
+ vi.mocked(getAvailableTemplates).mockResolvedValue(['event-planner', 'chatbot']);
135
135
  await expect(createAgents({
136
136
  dirName: 'test-dir',
137
137
  template: 'non-existent-template',
@@ -142,7 +142,7 @@ describe('createAgents - Template and Project ID Logic', () => {
142
142
  });
143
143
  it('should show available templates when invalid template is provided', async () => {
144
144
  vi.mocked(getAvailableTemplates).mockResolvedValue([
145
- 'weather-project',
145
+ 'event-planner',
146
146
  'chatbot',
147
147
  'data-analysis',
148
148
  ]);
@@ -152,7 +152,7 @@ describe('createAgents - Template and Project ID Logic', () => {
152
152
  openAiKey: 'test-openai-key',
153
153
  })).rejects.toThrow('process.exit called');
154
154
  const cancelCall = vi.mocked(p.cancel).mock.calls[0][0];
155
- expect(cancelCall).toContain('weather-project');
155
+ expect(cancelCall).toContain('event-planner');
156
156
  expect(cancelCall).toContain('chatbot');
157
157
  expect(cancelCall).toContain('data-analysis');
158
158
  });
@@ -171,13 +171,13 @@ describe('createAgents - Template and Project ID Logic', () => {
171
171
  // Should NOT validate templates
172
172
  expect(getAvailableTemplates).not.toHaveBeenCalled();
173
173
  // Should create empty project directory
174
- expect(fs.ensureDir).toHaveBeenCalledWith('src/my-custom-project');
174
+ expect(fs.ensureDir).toHaveBeenCalledWith('src/projects/my-custom-project');
175
175
  // Check that .env file is created
176
176
  expect(fs.writeFile).toHaveBeenCalledWith('.env', expect.stringContaining('ENVIRONMENT=development'));
177
177
  // Check that inkeep.config.ts is created in src directory
178
178
  expect(fs.writeFile).toHaveBeenCalledWith('src/inkeep.config.ts', expect.stringContaining('tenantId: "default"'));
179
179
  // Check that custom project index.ts is created
180
- expect(fs.writeFile).toHaveBeenCalledWith('src/my-custom-project/index.ts', expect.stringContaining('id: "my-custom-project"'));
180
+ expect(fs.writeFile).toHaveBeenCalledWith('src/projects/my-custom-project/index.ts', expect.stringContaining('id: "my-custom-project"'));
181
181
  });
182
182
  it('should prioritize custom project ID over template if both are provided', async () => {
183
183
  await createAgents({
@@ -191,13 +191,13 @@ describe('createAgents - Template and Project ID Logic', () => {
191
191
  expect(cloneTemplate).toHaveBeenCalledTimes(1);
192
192
  expect(cloneTemplate).toHaveBeenCalledWith('https://github.com/inkeep/create-agents-template', expect.any(String));
193
193
  expect(getAvailableTemplates).not.toHaveBeenCalled();
194
- expect(fs.ensureDir).toHaveBeenCalledWith('src/my-custom-project');
194
+ expect(fs.ensureDir).toHaveBeenCalledWith('src/projects/my-custom-project');
195
195
  // Check that .env file is created
196
196
  expect(fs.writeFile).toHaveBeenCalledWith('.env', expect.stringContaining('ENVIRONMENT=development'));
197
197
  // Check that inkeep.config.ts is created in src directory
198
198
  expect(fs.writeFile).toHaveBeenCalledWith('src/inkeep.config.ts', expect.stringContaining('tenantId: "default"'));
199
199
  // Check that custom project index.ts is created
200
- expect(fs.writeFile).toHaveBeenCalledWith('src/my-custom-project/index.ts', expect.stringContaining('id: "my-custom-project"'));
200
+ expect(fs.writeFile).toHaveBeenCalledWith('src/projects/my-custom-project/index.ts', expect.stringContaining('id: "my-custom-project"'));
201
201
  });
202
202
  });
203
203
  describe('Edge cases and validation', () => {
@@ -213,13 +213,13 @@ describe('createAgents - Template and Project ID Logic', () => {
213
213
  anthropicKey: 'test-key',
214
214
  });
215
215
  expect(cloneTemplate).toHaveBeenCalledTimes(2);
216
- expect(cloneTemplate).toHaveBeenCalledWith('https://github.com/inkeep/agents-cookbook/template-projects/my-complex-template', 'src/my-complex-template', expect.arrayContaining([
216
+ expect(cloneTemplate).toHaveBeenCalledWith('https://github.com/inkeep/agents-cookbook/template-projects/my-complex-template', 'src/projects/my-complex-template', expect.arrayContaining([
217
217
  expect.objectContaining({
218
218
  filePath: 'index.ts',
219
219
  replacements: expect.objectContaining({
220
- models: expect.any(Object)
221
- })
222
- })
220
+ models: expect.any(Object),
221
+ }),
222
+ }),
223
223
  ]));
224
224
  });
225
225
  it('should handle custom project IDs with special characters', async () => {
@@ -229,13 +229,13 @@ describe('createAgents - Template and Project ID Logic', () => {
229
229
  openAiKey: 'test-key',
230
230
  anthropicKey: 'test-key',
231
231
  });
232
- expect(fs.ensureDir).toHaveBeenCalledWith('src/my_project-123');
232
+ expect(fs.ensureDir).toHaveBeenCalledWith('src/projects/my_project-123');
233
233
  // Check that .env file is created
234
234
  expect(fs.writeFile).toHaveBeenCalledWith('.env', expect.stringContaining('ENVIRONMENT=development'));
235
235
  // Check that inkeep.config.ts is created in src directory
236
236
  expect(fs.writeFile).toHaveBeenCalledWith('src/inkeep.config.ts', expect.stringContaining('tenantId: "default"'));
237
237
  // Check that custom project index.ts is created
238
- expect(fs.writeFile).toHaveBeenCalledWith('src/my_project-123/index.ts', expect.stringContaining('id: "my_project-123"'));
238
+ expect(fs.writeFile).toHaveBeenCalledWith('src/projects/my_project-123/index.ts', expect.stringContaining('id: "my_project-123"'));
239
239
  });
240
240
  it('should create correct folder structure for all scenarios', async () => {
241
241
  // Test default
@@ -267,7 +267,7 @@ describe('createAgents - Template and Project ID Logic', () => {
267
267
  anthropicKey: 'key',
268
268
  });
269
269
  expect(fs.ensureDir).toHaveBeenCalledWith('src');
270
- expect(fs.ensureDir).toHaveBeenCalledWith('src/custom');
270
+ expect(fs.ensureDir).toHaveBeenCalledWith('src/projects/custom');
271
271
  });
272
272
  });
273
273
  });
@@ -277,10 +277,6 @@ function setupDefaultMocks() {
277
277
  vi.mocked(fs.pathExists).mockResolvedValue(false);
278
278
  vi.mocked(fs.ensureDir).mockResolvedValue(undefined);
279
279
  vi.mocked(fs.writeFile).mockResolvedValue(undefined);
280
- vi.mocked(getAvailableTemplates).mockResolvedValue([
281
- 'weather-project',
282
- 'chatbot',
283
- 'data-analysis',
284
- ]);
280
+ vi.mocked(getAvailableTemplates).mockResolvedValue(['event-planner', 'chatbot', 'data-analysis']);
285
281
  vi.mocked(cloneTemplate).mockResolvedValue(undefined);
286
282
  }
@@ -5,6 +5,7 @@ export interface ContentReplacement {
5
5
  replacements: Record<string, any>;
6
6
  }
7
7
  export declare function cloneTemplate(templatePath: string, targetPath: string, replacements?: ContentReplacement[]): Promise<void>;
8
+ export declare function cloneTemplateLocal(templatePath: string, targetPath: string, replacements: ContentReplacement[]): Promise<void>;
8
9
  /**
9
10
  * Replace content in cloned template files
10
11
  */
package/dist/templates.js CHANGED
@@ -17,6 +17,22 @@ export async function cloneTemplate(templatePath, targetPath, replacements) {
17
17
  process.exit(1);
18
18
  }
19
19
  }
20
+ export async function cloneTemplateLocal(templatePath, targetPath, replacements) {
21
+ await fs.mkdir(targetPath, { recursive: true });
22
+ try {
23
+ await fs.copy(templatePath, targetPath, {
24
+ overwrite: true,
25
+ errorOnExist: false,
26
+ });
27
+ if (replacements && replacements.length > 0) {
28
+ await replaceContentInFiles(targetPath, replacements);
29
+ }
30
+ }
31
+ catch (error) {
32
+ console.error(`Failed to clone template from ${templatePath}:`, error);
33
+ process.exit(1);
34
+ }
35
+ }
20
36
  /**
21
37
  * Replace content in cloned template files
22
38
  */
package/dist/utils.js CHANGED
@@ -2,9 +2,9 @@ import { exec } from 'node:child_process';
2
2
  import path from 'node:path';
3
3
  import { promisify } from 'node:util';
4
4
  import * as p from '@clack/prompts';
5
+ import { ANTHROPIC_MODELS, GOOGLE_MODELS, OPENAI_MODELS } from '@inkeep/agents-core';
5
6
  import fs from 'fs-extra';
6
7
  import color from 'picocolors';
7
- import { ANTHROPIC_MODELS, OPENAI_MODELS, GOOGLE_MODELS } from '@inkeep/agents-core';
8
8
  import { cloneTemplate, getAvailableTemplates } from './templates.js';
9
9
  const execAsync = promisify(exec);
10
10
  export const defaultGoogleModelConfigurations = {
@@ -47,14 +47,11 @@ export const createAgents = async (args = {}) => {
47
47
  const runApiPort = '3003';
48
48
  let projectId;
49
49
  let templateName;
50
- // Determine project ID and template based on user input
51
50
  if (customProjectId) {
52
- // User provided custom project ID - use it as-is, no template needed
53
51
  projectId = customProjectId;
54
- templateName = ''; // No template will be cloned
52
+ templateName = '';
55
53
  }
56
54
  else if (template) {
57
- // User provided template - validate it exists and use template name as project ID
58
55
  const availableTemplates = await getAvailableTemplates();
59
56
  if (!availableTemplates.includes(template)) {
60
57
  p.cancel(`${color.red('✗')} Template "${template}" not found\n\n` +
@@ -66,12 +63,10 @@ export const createAgents = async (args = {}) => {
66
63
  templateName = template;
67
64
  }
68
65
  else {
69
- // No template or custom project ID provided - use defaults
70
- projectId = 'weather-project';
71
- templateName = 'weather-project';
66
+ projectId = 'event-planner';
67
+ templateName = 'event-planner';
72
68
  }
73
69
  p.intro(color.inverse(' Create Agents Directory '));
74
- // Prompt for directory name if not provided
75
70
  if (!dirName) {
76
71
  const dirResponse = await p.text({
77
72
  message: 'What do you want to name your agents directory?',
@@ -90,8 +85,6 @@ export const createAgents = async (args = {}) => {
90
85
  }
91
86
  dirName = dirResponse;
92
87
  }
93
- // Project ID is already determined above based on template/customProjectId logic
94
- // If keys aren't provided via CLI args, prompt for provider selection and keys
95
88
  if (!anthropicKey && !openAiKey && !googleKey) {
96
89
  const providerChoice = await p.select({
97
90
  message: 'Which AI provider would you like to use?',
@@ -105,7 +98,6 @@ export const createAgents = async (args = {}) => {
105
98
  p.cancel('Operation cancelled');
106
99
  process.exit(0);
107
100
  }
108
- // Prompt for keys based on selection
109
101
  if (providerChoice === 'anthropic') {
110
102
  const anthropicKeyResponse = await p.text({
111
103
  message: 'Enter your Anthropic API key:',
@@ -168,7 +160,6 @@ export const createAgents = async (args = {}) => {
168
160
  else if (googleKey) {
169
161
  defaultModelSettings = defaultGoogleModelConfigurations;
170
162
  }
171
- // Ensure models are always configured - fail if none were set
172
163
  if (Object.keys(defaultModelSettings).length === 0) {
173
164
  p.cancel('Cannot continue without a model configuration for project. Please provide an API key for at least one AI provider.');
174
165
  process.exit(1);
@@ -181,7 +172,6 @@ export const createAgents = async (args = {}) => {
181
172
  ? `https://github.com/inkeep/agents-cookbook/template-projects/${templateName}`
182
173
  : null;
183
174
  const directoryPath = path.resolve(process.cwd(), dirName);
184
- // Check if directory already exists
185
175
  if (await fs.pathExists(directoryPath)) {
186
176
  s.stop();
187
177
  const overwrite = await p.confirm({
@@ -194,10 +184,8 @@ export const createAgents = async (args = {}) => {
194
184
  s.start('Cleaning existing directory...');
195
185
  await fs.emptyDir(directoryPath);
196
186
  }
197
- // Clone the template repository
198
187
  s.message('Building template...');
199
188
  await cloneTemplate(agentsTemplateRepo, directoryPath);
200
- // Change to the project directory
201
189
  process.chdir(directoryPath);
202
190
  const config = {
203
191
  dirName,
@@ -211,17 +199,13 @@ export const createAgents = async (args = {}) => {
211
199
  modelSettings: defaultModelSettings,
212
200
  customProject: !!customProjectId,
213
201
  };
214
- // Create workspace structure for project-specific files
215
202
  s.message('Setting up project structure...');
216
203
  await createWorkspaceStructure();
217
- // Create environment files
218
204
  s.message('Setting up environment files...');
219
205
  await createEnvironmentFiles(config);
220
- // Create project template folder (only if template is specified)
221
206
  if (projectTemplateRepo) {
222
207
  s.message('Creating project template folder...');
223
- const templateTargetPath = `src/${projectId}`;
224
- // Prepare content replacements for model settings
208
+ const templateTargetPath = `src/projects/${projectId}`;
225
209
  const contentReplacements = [
226
210
  {
227
211
  filePath: 'index.ts',
@@ -234,23 +218,18 @@ export const createAgents = async (args = {}) => {
234
218
  }
235
219
  else {
236
220
  s.message('Creating empty project folder...');
237
- await fs.ensureDir(`src/${projectId}`);
221
+ await fs.ensureDir(`src/projects/${projectId}`);
238
222
  }
239
- // create or overwrite inkeep.config.ts
240
223
  s.message('Creating inkeep.config.ts...');
241
224
  await createInkeepConfig(config);
242
- // Install dependencies
243
225
  s.message('Installing dependencies (this may take a while)...');
244
226
  await installDependencies();
245
- // Setup database
246
227
  s.message('Setting up database...');
247
228
  await setupDatabase();
248
- // Setup project in database
249
229
  s.message('Pushing project...');
250
230
  await setupProjectInDatabase(config);
251
231
  s.message('Project setup complete!');
252
232
  s.stop();
253
- // Success message with next steps
254
233
  p.note(`${color.green('✓')} Project created at: ${color.cyan(directoryPath)}\n\n` +
255
234
  `${color.yellow('Ready to go!')}\n\n` +
256
235
  `${color.green('✓')} Project created in file system\n` +
@@ -265,7 +244,7 @@ export const createAgents = async (args = {}) => {
265
244
  ` • Manage UI: Available with management API\n` +
266
245
  `\n${color.yellow('Configuration:')}\n` +
267
246
  ` • Edit .env for environment variables\n` +
268
- ` • Edit files in src/${projectId}/ for agent definitions\n` +
247
+ ` • Edit files in src/projects/${projectId}/ for agent definitions\n` +
269
248
  ` • Use 'inkeep push' to deploy agents to the platform\n` +
270
249
  ` • Use 'inkeep chat' to test your agents locally\n`, 'Ready to go!');
271
250
  }
@@ -276,11 +255,9 @@ export const createAgents = async (args = {}) => {
276
255
  }
277
256
  };
278
257
  async function createWorkspaceStructure() {
279
- // Create the workspace directory structure
280
258
  await fs.ensureDir(`src`);
281
259
  }
282
260
  async function createEnvironmentFiles(config) {
283
- // Root .env file
284
261
  const envContent = `# Environment
285
262
  ENVIRONMENT=development
286
263
 
@@ -330,48 +307,36 @@ export const myProject = project({
330
307
  agent: () => [],
331
308
  models: ${JSON.stringify(config.modelSettings, null, 2)},
332
309
  });`;
333
- await fs.writeFile(`src/${config.projectId}/index.ts`, customIndexContent);
310
+ await fs.writeFile(`src/projects/${config.projectId}/index.ts`, customIndexContent);
334
311
  }
335
312
  }
336
313
  async function installDependencies() {
337
314
  await execAsync('pnpm install');
338
315
  }
339
316
  async function setupProjectInDatabase(config) {
340
- // Start development servers in background
341
317
  const { spawn } = await import('node:child_process');
342
318
  const devProcess = spawn('pnpm', ['dev:apis'], {
343
319
  stdio: ['pipe', 'pipe', 'pipe'],
344
- detached: true, // Detach so we can kill the process group
320
+ detached: true,
345
321
  cwd: process.cwd(),
346
322
  });
347
- // Give servers time to start
348
323
  await new Promise((resolve) => setTimeout(resolve, 5000));
349
- // Run inkeep push
350
324
  try {
351
- // Suppress all output
352
- await execAsync(`pnpm inkeep push --project src/${config.projectId} --config src/inkeep.config.ts`);
325
+ await execAsync(`pnpm inkeep push --project src/projects/${config.projectId} --config src/inkeep.config.ts`);
353
326
  }
354
327
  catch (_error) {
355
- //Continue despite error - user can setup project manually
356
328
  }
357
329
  finally {
358
- // Kill the dev servers and their child processes
359
330
  if (devProcess.pid) {
360
331
  try {
361
- // Kill the entire process group
362
332
  process.kill(-devProcess.pid, 'SIGTERM');
363
- // Wait a moment for graceful shutdown
364
333
  await new Promise((resolve) => setTimeout(resolve, 1000));
365
- // Force kill if still running
366
334
  try {
367
335
  process.kill(-devProcess.pid, 'SIGKILL');
368
336
  }
369
- catch {
370
- // Process already terminated
371
- }
337
+ catch { }
372
338
  }
373
339
  catch (_error) {
374
- // Process might already be dead, that's fine
375
340
  console.log('Note: Dev servers may still be running in background');
376
341
  }
377
342
  }
@@ -379,7 +344,6 @@ async function setupProjectInDatabase(config) {
379
344
  }
380
345
  async function setupDatabase() {
381
346
  try {
382
- // Run drizzle-kit migrate to apply migrations to database
383
347
  await execAsync('pnpm db:migrate');
384
348
  await new Promise((resolve) => setTimeout(resolve, 1000));
385
349
  }
@@ -387,7 +351,6 @@ async function setupDatabase() {
387
351
  throw new Error(`Failed to setup database: ${error instanceof Error ? error.message : 'Unknown error'}`);
388
352
  }
389
353
  }
390
- // Export the command function for the CLI
391
354
  export async function createCommand(dirName, options) {
392
355
  await createAgents({
393
356
  dirName,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@inkeep/create-agents",
3
- "version": "0.20.1",
3
+ "version": "0.21.1",
4
4
  "description": "Create an Inkeep Agent Framework project",
5
5
  "type": "module",
6
6
  "bin": {
@@ -34,7 +34,7 @@
34
34
  "fs-extra": "^11.0.0",
35
35
  "picocolors": "^1.0.0",
36
36
  "drizzle-kit": "^0.31.5",
37
- "@inkeep/agents-core": "0.20.1"
37
+ "@inkeep/agents-core": "0.21.1"
38
38
  },
39
39
  "devDependencies": {
40
40
  "@types/degit": "^2.8.6",