@inkeep/create-agents 0.29.11 → 0.30.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.
@@ -3,7 +3,6 @@ import { execa } from 'execa';
3
3
  import { afterEach, beforeEach, describe, expect, it } from 'vitest';
4
4
  import { cleanupDir, createTempDir, linkLocalPackages, runCommand, runCreateAgentsCLI, verifyDirectoryStructure, verifyFile, waitForServerReady, } from './utils';
5
5
  const manageApiUrl = 'http://localhost:3002';
6
- const runApiUrl = 'http://localhost:3003';
7
6
  describe('create-agents quickstart e2e', () => {
8
7
  let testDir;
9
8
  let projectDir;
@@ -18,6 +17,9 @@ describe('create-agents quickstart e2e', () => {
18
17
  await cleanupDir(testDir);
19
18
  });
20
19
  it('should work e2e', async () => {
20
+ const monorepoRoot = path.join(__dirname, '../../../../../');
21
+ const createAgentsPrefix = path.join(monorepoRoot, 'create-agents-template');
22
+ const projectTemplatesPrefix = path.join(monorepoRoot, 'agents-cookbook/template-projects');
21
23
  // Run the CLI with all options (non-interactive mode)
22
24
  console.log('Running CLI with options:');
23
25
  console.log(`Working directory: ${testDir}`);
@@ -26,6 +28,10 @@ describe('create-agents quickstart e2e', () => {
26
28
  '--openai-key',
27
29
  'test-openai-key',
28
30
  '--disable-git', // Skip git init for faster tests
31
+ '--local-agents-prefix',
32
+ createAgentsPrefix,
33
+ '--local-templates-prefix',
34
+ projectTemplatesPrefix,
29
35
  ], testDir);
30
36
  // Verify the CLI completed successfully
31
37
  expect(result.exitCode).toBe(0);
@@ -114,7 +120,6 @@ describe('create-agents quickstart e2e', () => {
114
120
  expect(data.data.tenantId).toBe('default');
115
121
  expect(data.data.id).toBe(projectId);
116
122
  // Link to local monorepo packages
117
- const monorepoRoot = path.join(__dirname, '../../../../../'); // Go up to repo root
118
123
  await linkLocalPackages(projectDir, monorepoRoot);
119
124
  const pushResultLocal = await runCommand('pnpm', [
120
125
  'inkeep',
@@ -1,7 +1,7 @@
1
1
  import * as p from '@clack/prompts';
2
2
  import fs from 'fs-extra';
3
3
  import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
4
- import { cloneTemplate, getAvailableTemplates } from '../templates';
4
+ import { cloneTemplate, cloneTemplateLocal, getAvailableTemplates } from '../templates';
5
5
  import { createAgents } from '../utils';
6
6
  // Mock all dependencies
7
7
  vi.mock('fs-extra');
@@ -55,6 +55,7 @@ describe('createAgents - Template and Project ID Logic', () => {
55
55
  'data-analysis',
56
56
  ]);
57
57
  vi.mocked(cloneTemplate).mockResolvedValue(undefined);
58
+ vi.mocked(cloneTemplateLocal).mockResolvedValue(undefined);
58
59
  // Mock util.promisify to return a mock exec function
59
60
  const mockExecAsync = vi.fn().mockResolvedValue({ stdout: '', stderr: '' });
60
61
  const util = require('node:util');
@@ -79,10 +80,10 @@ describe('createAgents - Template and Project ID Logic', () => {
79
80
  openAiKey: 'test-openai-key',
80
81
  anthropicKey: 'test-anthropic-key',
81
82
  });
82
- // Should clone base template and weather-project template
83
+ // Should clone base template and activities-planner template
83
84
  expect(cloneTemplate).toHaveBeenCalledTimes(2);
84
- expect(cloneTemplate).toHaveBeenCalledWith('https://github.com/inkeep/create-agents-template', expect.any(String));
85
- expect(cloneTemplate).toHaveBeenCalledWith('https://github.com/inkeep/agents-cookbook/template-projects/activities-planner', 'src/projects/activities-planner', expect.arrayContaining([
85
+ expect(cloneTemplate).toHaveBeenCalledWith('https://github.com/inkeep/agents/create-agents-template', expect.any(String), undefined);
86
+ expect(cloneTemplate).toHaveBeenCalledWith('https://github.com/inkeep/agents/agents-cookbook/template-projects/activities-planner', 'src/projects/activities-planner', expect.arrayContaining([
86
87
  expect.objectContaining({
87
88
  filePath: 'index.ts',
88
89
  replacements: expect.objectContaining({
@@ -117,8 +118,8 @@ describe('createAgents - Template and Project ID Logic', () => {
117
118
  expect(getAvailableTemplates).toHaveBeenCalled();
118
119
  // Should clone base template and the specified template
119
120
  expect(cloneTemplate).toHaveBeenCalledTimes(2);
120
- expect(cloneTemplate).toHaveBeenCalledWith('https://github.com/inkeep/create-agents-template', expect.any(String));
121
- expect(cloneTemplate).toHaveBeenCalledWith('https://github.com/inkeep/agents-cookbook/template-projects/chatbot', 'src/projects/chatbot', expect.arrayContaining([
121
+ expect(cloneTemplate).toHaveBeenCalledWith('https://github.com/inkeep/agents/create-agents-template', expect.any(String), undefined);
122
+ expect(cloneTemplate).toHaveBeenCalledWith('https://github.com/inkeep/agents/agents-cookbook/template-projects/chatbot', 'src/projects/chatbot', expect.arrayContaining([
122
123
  expect.objectContaining({
123
124
  filePath: 'index.ts',
124
125
  replacements: expect.objectContaining({
@@ -168,7 +169,7 @@ describe('createAgents - Template and Project ID Logic', () => {
168
169
  });
169
170
  // Should clone base template but NOT project template
170
171
  expect(cloneTemplate).toHaveBeenCalledTimes(1);
171
- expect(cloneTemplate).toHaveBeenCalledWith('https://github.com/inkeep/create-agents-template', expect.any(String));
172
+ expect(cloneTemplate).toHaveBeenCalledWith('https://github.com/inkeep/agents/create-agents-template', expect.any(String), undefined);
172
173
  // Should NOT validate templates
173
174
  expect(getAvailableTemplates).not.toHaveBeenCalled();
174
175
  // Should create empty project directory
@@ -190,7 +191,7 @@ describe('createAgents - Template and Project ID Logic', () => {
190
191
  });
191
192
  // Should only clone base template, not project template
192
193
  expect(cloneTemplate).toHaveBeenCalledTimes(1);
193
- expect(cloneTemplate).toHaveBeenCalledWith('https://github.com/inkeep/create-agents-template', expect.any(String));
194
+ expect(cloneTemplate).toHaveBeenCalledWith('https://github.com/inkeep/agents/create-agents-template', expect.any(String), undefined);
194
195
  expect(getAvailableTemplates).not.toHaveBeenCalled();
195
196
  expect(fs.ensureDir).toHaveBeenCalledWith('src/projects/my-custom-project');
196
197
  // Check that .env file is created
@@ -214,7 +215,7 @@ describe('createAgents - Template and Project ID Logic', () => {
214
215
  anthropicKey: 'test-key',
215
216
  });
216
217
  expect(cloneTemplate).toHaveBeenCalledTimes(2);
217
- expect(cloneTemplate).toHaveBeenCalledWith('https://github.com/inkeep/agents-cookbook/template-projects/my-complex-template', 'src/projects/my-complex-template', expect.arrayContaining([
218
+ expect(cloneTemplate).toHaveBeenCalledWith('https://github.com/inkeep/agents/agents-cookbook/template-projects/my-complex-template', 'src/projects/my-complex-template', expect.arrayContaining([
218
219
  expect.objectContaining({
219
220
  filePath: 'index.ts',
220
221
  replacements: expect.objectContaining({
@@ -328,4 +329,5 @@ function setupDefaultMocks() {
328
329
  vi.mocked(fs.writeFile).mockResolvedValue(undefined);
329
330
  vi.mocked(getAvailableTemplates).mockResolvedValue(['event-planner', 'chatbot', 'data-analysis']);
330
331
  vi.mocked(cloneTemplate).mockResolvedValue(undefined);
332
+ vi.mocked(cloneTemplateLocal).mockResolvedValue(undefined);
331
333
  }
package/dist/index.js CHANGED
@@ -11,6 +11,8 @@ program
11
11
  .option('--anthropic-key <anthropic-key>', 'Anthropic API key')
12
12
  .option('--custom-project-id <custom-project-id>', 'Custom project id for experienced users who want an empty project directory')
13
13
  .option('--disable-git', 'Disable git initialization')
14
+ .option('--local-agents-prefix <local-agents-prefix>', 'Local prefix for create-agents-template')
15
+ .option('--local-templates-prefix <local-templates-prefix>', 'Local prefix for project templates')
14
16
  .parse();
15
17
  async function main() {
16
18
  const options = program.opts();
@@ -23,6 +25,8 @@ async function main() {
23
25
  customProjectId: options.customProjectId,
24
26
  template: options.template,
25
27
  disableGit: options.disableGit,
28
+ localAgentsPrefix: options.localAgentsPrefix,
29
+ localTemplatesPrefix: options.localTemplatesPrefix,
26
30
  });
27
31
  }
28
32
  catch (error) {
@@ -5,7 +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
+ export declare function cloneTemplateLocal(templatePath: string, targetPath: string, replacements?: ContentReplacement[]): Promise<void>;
9
9
  /**
10
10
  * Replace content in cloned template files
11
11
  */
@@ -14,4 +14,4 @@ export declare function replaceContentInFiles(targetPath: string, replacements:
14
14
  * Replace object properties in TypeScript code content
15
15
  */
16
16
  export declare function replaceObjectProperties(content: string, replacements: Record<string, any>): Promise<string>;
17
- export declare function getAvailableTemplates(): Promise<string[]>;
17
+ export declare function getAvailableTemplates(localPrefix?: string): Promise<string[]>;
package/dist/templates.js CHANGED
@@ -253,9 +253,16 @@ function injectPropertyIntoObject(content, propertyPath, replacement) {
253
253
  console.warn(`Could not inject property "${propertyPath}" - no suitable object found in content`);
254
254
  return content;
255
255
  }
256
- export async function getAvailableTemplates() {
257
- // Fetch the list of templates from your repo
258
- const response = await fetch('https://api.github.com/repos/inkeep/agents-cookbook/contents/template-projects');
259
- const contents = await response.json();
260
- return contents.filter((item) => item.type === 'dir').map((item) => item.name);
256
+ export async function getAvailableTemplates(localPrefix) {
257
+ if (localPrefix && localPrefix.length > 0) {
258
+ const fullTemplatePath = path.join(localPrefix, 'template-projects');
259
+ const response = await fs.readdir(fullTemplatePath);
260
+ return response.filter((item) => fs.stat(path.join(fullTemplatePath, item)).then((stat) => stat.isDirectory()));
261
+ }
262
+ else {
263
+ // Fetch the list of templates from your repo
264
+ const response = await fetch(`https://api.github.com/repos/inkeep/agents/contents/agents-cookbook/template-projects`);
265
+ const contents = await response.json();
266
+ return contents.filter((item) => item.type === 'dir').map((item) => item.name);
267
+ }
261
268
  }
package/dist/utils.d.ts CHANGED
@@ -40,5 +40,7 @@ export declare const createAgents: (args?: {
40
40
  template?: string;
41
41
  customProjectId?: string;
42
42
  disableGit?: boolean;
43
+ localAgentsPrefix?: string;
44
+ localTemplatesPrefix?: string;
43
45
  }) => Promise<void>;
44
46
  export declare function createCommand(dirName?: string, options?: any): Promise<void>;
package/dist/utils.js CHANGED
@@ -6,7 +6,7 @@ import * as p from '@clack/prompts';
6
6
  import { ANTHROPIC_MODELS, GOOGLE_MODELS, OPENAI_MODELS } from '@inkeep/agents-core';
7
7
  import fs from 'fs-extra';
8
8
  import color from 'picocolors';
9
- import { cloneTemplate, getAvailableTemplates } from './templates.js';
9
+ import { cloneTemplate, cloneTemplateLocal, getAvailableTemplates, } from './templates.js';
10
10
  // Shared validation utility
11
11
  const DIRECTORY_VALIDATION = {
12
12
  pattern: /^[a-zA-Z0-9][a-zA-Z0-9_-]*$/,
@@ -28,6 +28,8 @@ const DIRECTORY_VALIDATION = {
28
28
  return undefined;
29
29
  },
30
30
  };
31
+ const agentsTemplateRepo = 'https://github.com/inkeep/agents/create-agents-template';
32
+ const projectTemplateRepo = 'https://github.com/inkeep/agents/agents-cookbook/template-projects';
31
33
  const execAsync = promisify(exec);
32
34
  const manageApiPort = '3002';
33
35
  const runApiPort = '3003';
@@ -65,7 +67,7 @@ export const defaultAnthropicModelConfigurations = {
65
67
  },
66
68
  };
67
69
  export const createAgents = async (args = {}) => {
68
- let { dirName, openAiKey, anthropicKey, googleKey, template, customProjectId, disableGit } = args;
70
+ let { dirName, openAiKey, anthropicKey, googleKey, template, customProjectId, disableGit, localAgentsPrefix, localTemplatesPrefix, } = args;
69
71
  const tenantId = 'default';
70
72
  let projectId;
71
73
  let templateName;
@@ -74,7 +76,7 @@ export const createAgents = async (args = {}) => {
74
76
  templateName = '';
75
77
  }
76
78
  else if (template) {
77
- const availableTemplates = await getAvailableTemplates();
79
+ const availableTemplates = await getAvailableTemplates(localTemplatesPrefix);
78
80
  if (!availableTemplates.includes(template)) {
79
81
  p.cancel(`${color.red('✗')} Template "${template}" not found\n\n` +
80
82
  `${color.yellow('Available templates:')}\n` +
@@ -188,10 +190,6 @@ export const createAgents = async (args = {}) => {
188
190
  const s = p.spinner();
189
191
  s.start('Creating directory structure...');
190
192
  try {
191
- const agentsTemplateRepo = 'https://github.com/inkeep/create-agents-template';
192
- const projectTemplateRepo = templateName
193
- ? `https://github.com/inkeep/agents-cookbook/template-projects/${templateName}`
194
- : null;
195
193
  const directoryPath = path.resolve(process.cwd(), dirName);
196
194
  if (await fs.pathExists(directoryPath)) {
197
195
  s.stop();
@@ -206,7 +204,10 @@ export const createAgents = async (args = {}) => {
206
204
  await fs.emptyDir(directoryPath);
207
205
  }
208
206
  s.message('Building template...');
209
- await cloneTemplate(agentsTemplateRepo, directoryPath);
207
+ await cloneTemplateHelper({
208
+ targetPath: directoryPath,
209
+ localPrefix: localAgentsPrefix,
210
+ });
210
211
  process.chdir(directoryPath);
211
212
  const config = {
212
213
  dirName,
@@ -223,7 +224,7 @@ export const createAgents = async (args = {}) => {
223
224
  await createWorkspaceStructure();
224
225
  s.message('Setting up environment files...');
225
226
  await createEnvironmentFiles(config);
226
- if (projectTemplateRepo) {
227
+ if (templateName && templateName.length > 0) {
227
228
  s.message('Creating project template folder...');
228
229
  const templateTargetPath = `src/projects/${projectId}`;
229
230
  const contentReplacements = [
@@ -234,7 +235,12 @@ export const createAgents = async (args = {}) => {
234
235
  },
235
236
  },
236
237
  ];
237
- await cloneTemplate(projectTemplateRepo, templateTargetPath, contentReplacements);
238
+ await cloneTemplateHelper({
239
+ templateName,
240
+ targetPath: templateTargetPath,
241
+ localPrefix: localTemplatesPrefix,
242
+ replacements: contentReplacements,
243
+ });
238
244
  }
239
245
  else {
240
246
  s.message('Creating empty project folder...');
@@ -505,6 +511,27 @@ async function setupDatabase() {
505
511
  throw new Error(`Failed to setup database: ${error instanceof Error ? error.message : 'Unknown error'}`);
506
512
  }
507
513
  }
514
+ async function cloneTemplateHelper(options) {
515
+ const { targetPath, templateName, localPrefix, replacements } = options;
516
+ // If local prefix is provided, use it to clone the template. This is useful for local development and testing.
517
+ if (localPrefix && localPrefix.length > 0) {
518
+ if (templateName) {
519
+ const fullTemplatePath = path.join(localPrefix, templateName);
520
+ await cloneTemplateLocal(fullTemplatePath, targetPath, replacements);
521
+ }
522
+ else {
523
+ await cloneTemplateLocal(localPrefix, targetPath, replacements);
524
+ }
525
+ }
526
+ else {
527
+ if (templateName) {
528
+ await cloneTemplate(`${projectTemplateRepo}/${templateName}`, targetPath, replacements);
529
+ }
530
+ else {
531
+ await cloneTemplate(agentsTemplateRepo, targetPath, replacements);
532
+ }
533
+ }
534
+ }
508
535
  export async function createCommand(dirName, options) {
509
536
  await createAgents({
510
537
  dirName,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@inkeep/create-agents",
3
- "version": "0.29.11",
3
+ "version": "0.30.0",
4
4
  "description": "Create an Inkeep Agent Framework project",
5
5
  "type": "module",
6
6
  "bin": {
@@ -34,7 +34,7 @@
34
34
  "drizzle-kit": "^0.31.5",
35
35
  "fs-extra": "^11.0.0",
36
36
  "picocolors": "^1.0.0",
37
- "@inkeep/agents-core": "0.29.11"
37
+ "@inkeep/agents-core": "0.30.0"
38
38
  },
39
39
  "devDependencies": {
40
40
  "@types/degit": "^2.8.6",