@inkeep/create-agents 0.0.0-dev-20250916015245 → 0.0.0-dev-20250916190005

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.
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,237 @@
1
+ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
2
+ import fs from 'fs-extra';
3
+ import { createAgents } from '../utils';
4
+ import { getAvailableTemplates, cloneTemplate } from '../templates';
5
+ import * as p from '@clack/prompts';
6
+ // Mock all dependencies
7
+ vi.mock('fs-extra');
8
+ vi.mock('../templates');
9
+ vi.mock('@clack/prompts');
10
+ vi.mock('child_process');
11
+ vi.mock('util');
12
+ // Setup default mocks
13
+ const mockSpinner = {
14
+ start: vi.fn().mockReturnThis(),
15
+ stop: vi.fn().mockReturnThis(),
16
+ message: vi.fn().mockReturnThis(),
17
+ };
18
+ describe('createAgents - Template and Project ID Logic', () => {
19
+ let processExitSpy;
20
+ let processChdirSpy;
21
+ beforeEach(() => {
22
+ vi.clearAllMocks();
23
+ // Mock process methods
24
+ processExitSpy = vi.spyOn(process, 'exit').mockImplementation((code) => {
25
+ // Only throw for exit(0) which is expected behavior in some tests
26
+ // Let exit(1) pass so we can see the actual error
27
+ if (code === 0) {
28
+ throw new Error('process.exit called');
29
+ }
30
+ // Don't actually exit for exit(1) in tests
31
+ return undefined;
32
+ });
33
+ processChdirSpy = vi.spyOn(process, 'chdir').mockImplementation(() => { });
34
+ // Setup default mocks for @clack/prompts
35
+ vi.mocked(p.intro).mockImplementation(() => { });
36
+ vi.mocked(p.outro).mockImplementation(() => { });
37
+ vi.mocked(p.cancel).mockImplementation(() => { });
38
+ vi.mocked(p.note).mockImplementation(() => { });
39
+ vi.mocked(p.text).mockResolvedValue('test-dir');
40
+ vi.mocked(p.select).mockResolvedValue('dual');
41
+ vi.mocked(p.confirm).mockResolvedValue(false);
42
+ vi.mocked(p.spinner).mockReturnValue(mockSpinner);
43
+ vi.mocked(p.isCancel).mockReturnValue(false);
44
+ // Mock fs-extra
45
+ vi.mocked(fs.pathExists).mockResolvedValue(false);
46
+ vi.mocked(fs.ensureDir).mockResolvedValue(undefined);
47
+ vi.mocked(fs.writeFile).mockResolvedValue(undefined);
48
+ vi.mocked(fs.mkdir).mockResolvedValue(undefined);
49
+ vi.mocked(fs.remove).mockResolvedValue(undefined);
50
+ // Mock templates
51
+ vi.mocked(getAvailableTemplates).mockResolvedValue(['weather-graph', 'chatbot', 'data-analysis']);
52
+ vi.mocked(cloneTemplate).mockResolvedValue(undefined);
53
+ // Mock util.promisify to return a mock exec function
54
+ const mockExecAsync = vi.fn().mockResolvedValue({ stdout: '', stderr: '' });
55
+ const util = require('util');
56
+ util.promisify = vi.fn(() => mockExecAsync);
57
+ // Mock child_process.spawn
58
+ const childProcess = require('child_process');
59
+ childProcess.spawn = vi.fn(() => ({
60
+ pid: 12345,
61
+ stdio: ['pipe', 'pipe', 'pipe'],
62
+ on: vi.fn(),
63
+ kill: vi.fn(),
64
+ }));
65
+ });
66
+ afterEach(() => {
67
+ processExitSpy.mockRestore();
68
+ processChdirSpy.mockRestore();
69
+ });
70
+ describe('Default behavior (no template or customProjectId)', () => {
71
+ it('should use weather-graph as default template and project ID', async () => {
72
+ await createAgents({
73
+ dirName: 'test-dir',
74
+ openAiKey: 'test-openai-key',
75
+ anthropicKey: 'test-anthropic-key'
76
+ });
77
+ // Should clone base template and weather-graph template
78
+ expect(cloneTemplate).toHaveBeenCalledTimes(2);
79
+ expect(cloneTemplate).toHaveBeenCalledWith('https://github.com/inkeep/create-agents-template', expect.any(String));
80
+ expect(cloneTemplate).toHaveBeenCalledWith('https://github.com/inkeep/agents-cookbook/templates/weather-graph', 'src/weather-graph');
81
+ // Should not call getAvailableTemplates since no template validation needed
82
+ expect(getAvailableTemplates).not.toHaveBeenCalled();
83
+ });
84
+ it('should create project with weather-graph as project ID', async () => {
85
+ await createAgents({
86
+ dirName: 'test-dir',
87
+ openAiKey: 'test-openai-key',
88
+ anthropicKey: 'test-anthropic-key'
89
+ });
90
+ // Check that .env file is created with correct project ID path
91
+ expect(fs.writeFile).toHaveBeenCalledWith('src/weather-graph/.env', expect.any(String));
92
+ // Check that inkeep.config.ts is created with correct project ID
93
+ expect(fs.writeFile).toHaveBeenCalledWith('src/weather-graph/inkeep.config.ts', expect.stringContaining('projectId: "weather-graph"'));
94
+ });
95
+ });
96
+ describe('Template provided', () => {
97
+ it('should use template name as project ID when template is provided', async () => {
98
+ await createAgents({
99
+ dirName: 'test-dir',
100
+ template: 'chatbot',
101
+ openAiKey: 'test-openai-key',
102
+ anthropicKey: 'test-anthropic-key'
103
+ });
104
+ // Should validate template exists
105
+ expect(getAvailableTemplates).toHaveBeenCalled();
106
+ // Should clone base template and the specified template
107
+ expect(cloneTemplate).toHaveBeenCalledTimes(2);
108
+ expect(cloneTemplate).toHaveBeenCalledWith('https://github.com/inkeep/create-agents-template', expect.any(String));
109
+ expect(cloneTemplate).toHaveBeenCalledWith('https://github.com/inkeep/agents-cookbook/templates/chatbot', 'src/chatbot');
110
+ // Should create config files with template name as project ID
111
+ expect(fs.writeFile).toHaveBeenCalledWith('src/chatbot/.env', expect.any(String));
112
+ expect(fs.writeFile).toHaveBeenCalledWith('src/chatbot/inkeep.config.ts', expect.stringContaining('projectId: "chatbot"'));
113
+ });
114
+ it('should exit with error when template does not exist', async () => {
115
+ vi.mocked(getAvailableTemplates).mockResolvedValue(['weather-graph', 'chatbot']);
116
+ await expect(createAgents({
117
+ dirName: 'test-dir',
118
+ template: 'non-existent-template',
119
+ openAiKey: 'test-openai-key'
120
+ })).rejects.toThrow('process.exit called');
121
+ expect(p.cancel).toHaveBeenCalledWith(expect.stringContaining('Template "non-existent-template" not found'));
122
+ expect(processExitSpy).toHaveBeenCalledWith(0);
123
+ });
124
+ it('should show available templates when invalid template is provided', async () => {
125
+ vi.mocked(getAvailableTemplates).mockResolvedValue(['weather-graph', 'chatbot', 'data-analysis']);
126
+ await expect(createAgents({
127
+ dirName: 'test-dir',
128
+ template: 'invalid',
129
+ openAiKey: 'test-openai-key'
130
+ })).rejects.toThrow('process.exit called');
131
+ const cancelCall = vi.mocked(p.cancel).mock.calls[0][0];
132
+ expect(cancelCall).toContain('weather-graph');
133
+ expect(cancelCall).toContain('chatbot');
134
+ expect(cancelCall).toContain('data-analysis');
135
+ });
136
+ });
137
+ describe('Custom Project ID provided', () => {
138
+ it('should use custom project ID and not clone any template', async () => {
139
+ await createAgents({
140
+ dirName: 'test-dir',
141
+ customProjectId: 'my-custom-project',
142
+ openAiKey: 'test-openai-key',
143
+ anthropicKey: 'test-anthropic-key'
144
+ });
145
+ // Should clone base template but NOT project template
146
+ expect(cloneTemplate).toHaveBeenCalledTimes(1);
147
+ expect(cloneTemplate).toHaveBeenCalledWith('https://github.com/inkeep/create-agents-template', expect.any(String));
148
+ // Should NOT validate templates
149
+ expect(getAvailableTemplates).not.toHaveBeenCalled();
150
+ // Should create empty project directory
151
+ expect(fs.ensureDir).toHaveBeenCalledWith('src/my-custom-project');
152
+ // Should create config files with custom project ID
153
+ expect(fs.writeFile).toHaveBeenCalledWith('src/my-custom-project/.env', expect.any(String));
154
+ expect(fs.writeFile).toHaveBeenCalledWith('src/my-custom-project/inkeep.config.ts', expect.stringContaining('projectId: "my-custom-project"'));
155
+ });
156
+ it('should prioritize custom project ID over template if both are provided', async () => {
157
+ await createAgents({
158
+ dirName: 'test-dir',
159
+ template: 'chatbot',
160
+ customProjectId: 'my-custom-project',
161
+ openAiKey: 'test-openai-key',
162
+ anthropicKey: 'test-anthropic-key'
163
+ });
164
+ // Should only clone base template, not project template
165
+ expect(cloneTemplate).toHaveBeenCalledTimes(1);
166
+ expect(cloneTemplate).toHaveBeenCalledWith('https://github.com/inkeep/create-agents-template', expect.any(String));
167
+ expect(getAvailableTemplates).not.toHaveBeenCalled();
168
+ expect(fs.ensureDir).toHaveBeenCalledWith('src/my-custom-project');
169
+ // Config should use custom project ID
170
+ expect(fs.writeFile).toHaveBeenCalledWith('src/my-custom-project/inkeep.config.ts', expect.stringContaining('projectId: "my-custom-project"'));
171
+ });
172
+ });
173
+ describe('Edge cases and validation', () => {
174
+ it('should handle template names with hyphens correctly', async () => {
175
+ vi.mocked(getAvailableTemplates).mockResolvedValue(['my-complex-template', 'another-template']);
176
+ await createAgents({
177
+ dirName: 'test-dir',
178
+ template: 'my-complex-template',
179
+ openAiKey: 'test-key',
180
+ anthropicKey: 'test-key'
181
+ });
182
+ expect(cloneTemplate).toHaveBeenCalledTimes(2);
183
+ expect(cloneTemplate).toHaveBeenCalledWith('https://github.com/inkeep/agents-cookbook/templates/my-complex-template', 'src/my-complex-template');
184
+ });
185
+ it('should handle custom project IDs with special characters', async () => {
186
+ await createAgents({
187
+ dirName: 'test-dir',
188
+ customProjectId: 'my_project-123',
189
+ openAiKey: 'test-key',
190
+ anthropicKey: 'test-key'
191
+ });
192
+ expect(fs.ensureDir).toHaveBeenCalledWith('src/my_project-123');
193
+ expect(fs.writeFile).toHaveBeenCalledWith('src/my_project-123/inkeep.config.ts', expect.stringContaining('projectId: "my_project-123"'));
194
+ });
195
+ it('should create correct folder structure for all scenarios', async () => {
196
+ // Test default
197
+ await createAgents({
198
+ dirName: 'dir1',
199
+ openAiKey: 'key',
200
+ anthropicKey: 'key'
201
+ });
202
+ expect(fs.ensureDir).toHaveBeenCalledWith('src');
203
+ // Reset mocks
204
+ vi.clearAllMocks();
205
+ setupDefaultMocks();
206
+ // Test with template
207
+ await createAgents({
208
+ dirName: 'dir2',
209
+ template: 'chatbot',
210
+ openAiKey: 'key',
211
+ anthropicKey: 'key'
212
+ });
213
+ expect(fs.ensureDir).toHaveBeenCalledWith('src');
214
+ // Reset mocks
215
+ vi.clearAllMocks();
216
+ setupDefaultMocks();
217
+ // Test with custom ID
218
+ await createAgents({
219
+ dirName: 'dir3',
220
+ customProjectId: 'custom',
221
+ openAiKey: 'key',
222
+ anthropicKey: 'key'
223
+ });
224
+ expect(fs.ensureDir).toHaveBeenCalledWith('src');
225
+ expect(fs.ensureDir).toHaveBeenCalledWith('src/custom');
226
+ });
227
+ });
228
+ });
229
+ // Helper to setup default mocks
230
+ function setupDefaultMocks() {
231
+ vi.mocked(p.spinner).mockReturnValue(mockSpinner);
232
+ vi.mocked(fs.pathExists).mockResolvedValue(false);
233
+ vi.mocked(fs.ensureDir).mockResolvedValue(undefined);
234
+ vi.mocked(fs.writeFile).mockResolvedValue(undefined);
235
+ vi.mocked(getAvailableTemplates).mockResolvedValue(['weather-graph', 'chatbot', 'data-analysis']);
236
+ vi.mocked(cloneTemplate).mockResolvedValue(undefined);
237
+ }
package/dist/index.js CHANGED
@@ -6,9 +6,10 @@ program
6
6
  .description('Create an Inkeep Agent Framework directory')
7
7
  .version('0.1.0')
8
8
  .argument('[directory-name]', 'Name of the directory')
9
- .option('--project-id <project-id>', 'Project ID')
9
+ .option('--template <template>', 'Template to use')
10
10
  .option('--openai-key <openai-key>', 'OpenAI API key')
11
11
  .option('--anthropic-key <anthropic-key>', 'Anthropic API key')
12
+ .option('--custom-project-id <custom-project-id>', 'Custom project id for experienced users who want an empty project directory')
12
13
  .parse();
13
14
  async function main() {
14
15
  const options = program.opts();
@@ -18,7 +19,8 @@ async function main() {
18
19
  dirName: directoryName,
19
20
  openAiKey: options.openaiKey,
20
21
  anthropicKey: options.anthropicKey,
21
- projectId: options.projectId,
22
+ customProjectId: options.customProjectId,
23
+ template: options.template,
22
24
  });
23
25
  }
24
26
  catch (error) {
@@ -0,0 +1,2 @@
1
+ export declare function cloneTemplate(templatePath: string, targetPath: string): Promise<void>;
2
+ export declare function getAvailableTemplates(): Promise<string[]>;
@@ -0,0 +1,22 @@
1
+ import fs from 'fs-extra';
2
+ import degit from 'degit';
3
+ //Duplicating function here so we dont have to add a dependency on the agents-cli package
4
+ export async function cloneTemplate(templatePath, targetPath) {
5
+ await fs.mkdir(targetPath, { recursive: true });
6
+ const templatePathSuffix = templatePath.replace('https://github.com/', '');
7
+ const emitter = degit(templatePathSuffix);
8
+ try {
9
+ await emitter.clone(targetPath);
10
+ }
11
+ catch (error) {
12
+ process.exit(1);
13
+ }
14
+ }
15
+ export async function getAvailableTemplates() {
16
+ // Fetch the list of templates from your repo
17
+ const response = await fetch('https://api.github.com/repos/inkeep/agents-cookbook/contents/templates');
18
+ const contents = await response.json();
19
+ return contents
20
+ .filter((item) => item.type === 'dir')
21
+ .map((item) => item.name);
22
+ }
package/dist/utils.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- export declare const defaultDualModelConfigurations: {
1
+ export declare const defaultGoogleModelConfigurations: {
2
2
  base: {
3
3
  model: string;
4
4
  };
@@ -32,9 +32,12 @@ export declare const defaultAnthropicModelConfigurations: {
32
32
  };
33
33
  };
34
34
  export declare const createAgents: (args?: {
35
- projectId?: string;
36
35
  dirName?: string;
36
+ templateName?: string;
37
37
  openAiKey?: string;
38
38
  anthropicKey?: string;
39
+ googleKey?: string;
40
+ template?: string;
41
+ customProjectId?: string;
39
42
  }) => Promise<void>;
40
43
  export declare function createCommand(dirName?: string, options?: any): Promise<void>;
package/dist/utils.js CHANGED
@@ -1,19 +1,20 @@
1
- import color from 'picocolors';
2
1
  import * as p from '@clack/prompts';
3
- import fs from 'fs-extra';
4
2
  import { exec } from 'child_process';
5
- import { promisify } from 'util';
3
+ import fs from 'fs-extra';
6
4
  import path from 'path';
5
+ import color from 'picocolors';
6
+ import { promisify } from 'util';
7
+ import { cloneTemplate, getAvailableTemplates } from './templates.js';
7
8
  const execAsync = promisify(exec);
8
- export const defaultDualModelConfigurations = {
9
+ export const defaultGoogleModelConfigurations = {
9
10
  base: {
10
- model: 'anthropic/claude-sonnet-4-20250514',
11
+ model: 'google/gemini-2.5-flash',
11
12
  },
12
13
  structuredOutput: {
13
- model: 'openai/gpt-4.1-mini-2025-04-14',
14
+ model: 'google/gemini-2.5-flash-lite',
14
15
  },
15
16
  summarizer: {
16
- model: 'openai/gpt-4.1-nano-2025-04-14',
17
+ model: 'google/gemini-2.5-flash-lite',
17
18
  },
18
19
  };
19
20
  export const defaultOpenaiModelConfigurations = {
@@ -32,17 +33,42 @@ export const defaultAnthropicModelConfigurations = {
32
33
  model: 'anthropic/claude-sonnet-4-20250514',
33
34
  },
34
35
  structuredOutput: {
35
- model: 'anthropic/claude-sonnet-4-20250514',
36
+ model: 'anthropic/claude-3-5-haiku-20241022',
36
37
  },
37
38
  summarizer: {
38
- model: 'anthropic/claude-sonnet-4-20250514',
39
+ model: 'anthropic/claude-3-5-haiku-20241022',
39
40
  },
40
41
  };
41
42
  export const createAgents = async (args = {}) => {
42
- let { projectId, dirName, openAiKey, anthropicKey } = args;
43
+ let { dirName, openAiKey, anthropicKey, googleKey, template, customProjectId } = args;
43
44
  const tenantId = 'default';
44
45
  const manageApiPort = '3002';
45
46
  const runApiPort = '3003';
47
+ let projectId;
48
+ let templateName;
49
+ // Determine project ID and template based on user input
50
+ if (customProjectId) {
51
+ // User provided custom project ID - use it as-is, no template needed
52
+ projectId = customProjectId;
53
+ templateName = ''; // No template will be cloned
54
+ }
55
+ else if (template) {
56
+ // User provided template - validate it exists and use template name as project ID
57
+ const availableTemplates = await getAvailableTemplates();
58
+ if (!availableTemplates.includes(template)) {
59
+ p.cancel(`${color.red('✗')} Template "${template}" not found\n\n` +
60
+ `${color.yellow('Available templates:')}\n` +
61
+ ` • ${availableTemplates.join('\n • ')}\n`);
62
+ process.exit(0);
63
+ }
64
+ projectId = template;
65
+ templateName = template;
66
+ }
67
+ else {
68
+ // No template or custom project ID provided - use defaults
69
+ projectId = 'weather-graph';
70
+ templateName = 'weather-graph';
71
+ }
46
72
  p.intro(color.inverse(' Create Agents Directory '));
47
73
  // Prompt for directory name if not provided
48
74
  if (!dirName) {
@@ -63,27 +89,15 @@ export const createAgents = async (args = {}) => {
63
89
  }
64
90
  dirName = dirResponse;
65
91
  }
66
- // Prompt for project ID
67
- if (!projectId) {
68
- const projectIdResponse = await p.text({
69
- message: 'Enter your project ID:',
70
- placeholder: '(default)',
71
- defaultValue: 'default',
72
- });
73
- if (p.isCancel(projectIdResponse)) {
74
- p.cancel('Operation cancelled');
75
- process.exit(0);
76
- }
77
- projectId = projectIdResponse;
78
- }
92
+ // Project ID is already determined above based on template/customProjectId logic
79
93
  // If keys aren't provided via CLI args, prompt for provider selection and keys
80
94
  if (!anthropicKey && !openAiKey) {
81
95
  const providerChoice = await p.select({
82
96
  message: 'Which AI provider(s) would you like to use?',
83
97
  options: [
84
- { value: 'both', label: 'Both Anthropic and OpenAI (recommended)' },
85
98
  { value: 'anthropic', label: 'Anthropic only' },
86
99
  { value: 'openai', label: 'OpenAI only' },
100
+ { value: 'google', label: 'Google only' },
87
101
  ],
88
102
  });
89
103
  if (p.isCancel(providerChoice)) {
@@ -91,7 +105,7 @@ export const createAgents = async (args = {}) => {
91
105
  process.exit(0);
92
106
  }
93
107
  // Prompt for keys based on selection
94
- if (providerChoice === 'anthropic' || providerChoice === 'both') {
108
+ if (providerChoice === 'anthropic') {
95
109
  const anthropicKeyResponse = await p.text({
96
110
  message: 'Enter your Anthropic API key:',
97
111
  placeholder: 'sk-ant-...',
@@ -108,7 +122,7 @@ export const createAgents = async (args = {}) => {
108
122
  }
109
123
  anthropicKey = anthropicKeyResponse;
110
124
  }
111
- if (providerChoice === 'openai' || providerChoice === 'both') {
125
+ else if (providerChoice === 'openai') {
112
126
  const openAiKeyResponse = await p.text({
113
127
  message: 'Enter your OpenAI API key:',
114
128
  placeholder: 'sk-...',
@@ -125,47 +139,41 @@ export const createAgents = async (args = {}) => {
125
139
  }
126
140
  openAiKey = openAiKeyResponse;
127
141
  }
128
- }
129
- else {
130
- // If some keys are provided via CLI args, prompt for missing ones
131
- if (!anthropicKey) {
132
- const anthropicKeyResponse = await p.text({
133
- message: 'Enter your Anthropic API key (optional):',
134
- placeholder: 'sk-ant-...',
135
- defaultValue: '',
136
- });
137
- if (p.isCancel(anthropicKeyResponse)) {
138
- p.cancel('Operation cancelled');
139
- process.exit(0);
140
- }
141
- anthropicKey = anthropicKeyResponse || undefined;
142
- }
143
- if (!openAiKey) {
144
- const openAiKeyResponse = await p.text({
145
- message: 'Enter your OpenAI API key (optional):',
146
- placeholder: 'sk-...',
147
- defaultValue: '',
142
+ else if (providerChoice === 'google') {
143
+ const googleKeyResponse = await p.text({
144
+ message: 'Enter your Google API key:',
145
+ placeholder: 'AIzaSy...',
146
+ validate: (value) => {
147
+ if (!value || value.trim() === '') {
148
+ return 'Google API key is required';
149
+ }
150
+ return undefined;
151
+ },
148
152
  });
149
- if (p.isCancel(openAiKeyResponse)) {
153
+ if (p.isCancel(googleKeyResponse)) {
150
154
  p.cancel('Operation cancelled');
151
155
  process.exit(0);
152
156
  }
153
- openAiKey = openAiKeyResponse || undefined;
157
+ googleKey = googleKeyResponse;
154
158
  }
155
159
  }
156
160
  let defaultModelSettings = {};
157
- if (anthropicKey && openAiKey) {
158
- defaultModelSettings = defaultDualModelConfigurations;
159
- }
160
- else if (anthropicKey) {
161
+ if (anthropicKey) {
161
162
  defaultModelSettings = defaultAnthropicModelConfigurations;
162
163
  }
163
164
  else if (openAiKey) {
164
165
  defaultModelSettings = defaultOpenaiModelConfigurations;
165
166
  }
167
+ else if (googleKey) {
168
+ defaultModelSettings = defaultGoogleModelConfigurations;
169
+ }
166
170
  const s = p.spinner();
167
171
  s.start('Creating directory structure...');
168
172
  try {
173
+ const agentsTemplateRepo = 'https://github.com/inkeep/create-agents-template';
174
+ const projectTemplateRepo = templateName
175
+ ? `https://github.com/inkeep/agents-cookbook/templates/${templateName}`
176
+ : null;
169
177
  const directoryPath = path.resolve(process.cwd(), dirName);
170
178
  // Check if directory already exists
171
179
  if (await fs.pathExists(directoryPath)) {
@@ -180,8 +188,10 @@ export const createAgents = async (args = {}) => {
180
188
  s.start('Cleaning existing directory...');
181
189
  await fs.emptyDir(directoryPath);
182
190
  }
183
- // Create the project directory
184
- await fs.ensureDir(directoryPath);
191
+ // Clone the template repository
192
+ s.message('Building template...');
193
+ await cloneTemplate(agentsTemplateRepo, directoryPath);
194
+ // Change to the project directory
185
195
  process.chdir(directoryPath);
186
196
  const config = {
187
197
  dirName,
@@ -192,25 +202,30 @@ export const createAgents = async (args = {}) => {
192
202
  manageApiPort: manageApiPort || '3002',
193
203
  runApiPort: runApiPort || '3003',
194
204
  modelSettings: defaultModelSettings,
205
+ customProject: customProjectId ? true : false,
195
206
  };
196
- // Create workspace structure
197
- s.message('Setting up workspace structure...');
198
- await createWorkspaceStructure(projectId);
199
- // Setup package configurations
200
- s.message('Creating package configurations...');
201
- await setupPackageConfigurations(dirName);
207
+ // Create workspace structure for project-specific files
208
+ s.message('Setting up project structure...');
209
+ await createWorkspaceStructure();
202
210
  // Create environment files
203
211
  s.message('Setting up environment files...');
204
212
  await createEnvironmentFiles(config);
213
+ // Create project template folder (only if template is specified)
214
+ if (projectTemplateRepo) {
215
+ s.message('Creating project template folder...');
216
+ const templateTargetPath = `src/${projectId}`;
217
+ await cloneTemplate(projectTemplateRepo, templateTargetPath);
218
+ }
219
+ else {
220
+ s.message('Creating empty project folder...');
221
+ await fs.ensureDir(`src/${projectId}`);
222
+ }
223
+ // create or overwrite inkeep.config.ts
224
+ s.message('Creating inkeep.config.ts...');
225
+ await createInkeepConfig(config);
205
226
  // Create service files
206
227
  s.message('Creating service files...');
207
228
  await createServiceFiles(config);
208
- // Create documentation
209
- s.message('Creating documentation...');
210
- await createDocumentation(config);
211
- // Create turbo config
212
- s.message('Setting up Turbo...');
213
- await createTurboConfig();
214
229
  // Install dependencies
215
230
  s.message('Installing dependencies (this may take a while)...');
216
231
  await installDependencies();
@@ -218,8 +233,9 @@ export const createAgents = async (args = {}) => {
218
233
  s.message('Setting up database...');
219
234
  await setupDatabase();
220
235
  // Setup project in database
221
- s.message('Setting up project in database...');
222
- await setupProjectInDatabase();
236
+ s.message('Pushing project...');
237
+ await setupProjectInDatabase(config);
238
+ s.message('Project setup complete!');
223
239
  s.stop();
224
240
  // Success message with next steps
225
241
  p.note(`${color.green('✓')} Project created at: ${color.cyan(directoryPath)}\n\n` +
@@ -236,7 +252,7 @@ export const createAgents = async (args = {}) => {
236
252
  ` • Manage UI: Available with management API\n` +
237
253
  `\n${color.yellow('Configuration:')}\n` +
238
254
  ` • Edit .env for environment variables\n` +
239
- ` • Edit src/${projectId}/weather.graph.ts for agent definitions\n` +
255
+ ` • Edit files in src/${projectId}/ for agent definitions\n` +
240
256
  ` • Use 'inkeep push' to deploy agents to the platform\n` +
241
257
  ` • Use 'inkeep chat' to test your agents locally\n`, 'Ready to go!');
242
258
  }
@@ -246,136 +262,9 @@ export const createAgents = async (args = {}) => {
246
262
  process.exit(1);
247
263
  }
248
264
  };
249
- async function createWorkspaceStructure(projectId) {
265
+ async function createWorkspaceStructure() {
250
266
  // Create the workspace directory structure
251
- await fs.ensureDir(`src/${projectId}`);
252
- await fs.ensureDir('apps/manage-api/src');
253
- await fs.ensureDir('apps/run-api/src');
254
- await fs.ensureDir('apps/shared');
255
- await fs.ensureDir('scripts');
256
- }
257
- async function setupPackageConfigurations(dirName) {
258
- // Root package.json (workspace root)
259
- const rootPackageJson = {
260
- name: dirName,
261
- version: '0.1.0',
262
- description: 'An Inkeep Agent Framework directory',
263
- private: true,
264
- type: 'module',
265
- scripts: {
266
- dev: 'turbo dev',
267
- 'db:push': 'drizzle-kit push',
268
- setup: 'node scripts/setup.js',
269
- 'dev:setup': 'node scripts/dev-setup.js',
270
- start: 'pnpm dev:setup',
271
- },
272
- dependencies: {},
273
- devDependencies: {
274
- '@biomejs/biome': '^1.8.0',
275
- '@inkeep/agents-cli': '^0.1.1',
276
- 'drizzle-kit': '^0.31.4',
277
- tsx: '^4.19.0',
278
- turbo: '^2.5.5',
279
- concurrently: '^8.2.0',
280
- 'wait-on': '^8.0.0',
281
- },
282
- engines: {
283
- node: '>=22.x',
284
- },
285
- packageManager: 'pnpm@10.10.0',
286
- pnpm: {
287
- onlyBuiltDependencies: ['keytar'],
288
- },
289
- };
290
- await fs.writeJson('package.json', rootPackageJson, { spaces: 2 });
291
- // Create pnpm-workspace.yaml for pnpm workspaces
292
- const pnpmWorkspace = `packages:
293
- - "apps/*"
294
- `;
295
- await fs.writeFile('pnpm-workspace.yaml', pnpmWorkspace);
296
- // Add shared dependencies to root package.json
297
- rootPackageJson.dependencies = {
298
- '@inkeep/agents-core': '^0.1.0',
299
- '@inkeep/agents-sdk': '^0.1.0',
300
- dotenv: '^16.0.0',
301
- zod: '^4.1.5',
302
- };
303
- await fs.writeJson('package.json', rootPackageJson, { spaces: 2 });
304
- // Manage API package
305
- const manageApiPackageJson = {
306
- name: `@${dirName}/manage-api`,
307
- version: '0.1.0',
308
- description: 'Manage API for agents',
309
- type: 'module',
310
- scripts: {
311
- build: 'tsc',
312
- dev: 'tsx watch src/index.ts',
313
- start: 'node dist/index.js',
314
- },
315
- dependencies: {
316
- '@inkeep/agents-manage-api': '^0.1.1',
317
- '@inkeep/agents-core': '^0.1.0',
318
- '@hono/node-server': '^1.14.3',
319
- },
320
- devDependencies: {
321
- '@types/node': '^20.12.0',
322
- tsx: '^4.19.0',
323
- typescript: '^5.4.0',
324
- },
325
- engines: {
326
- node: '>=22.x',
327
- },
328
- };
329
- await fs.writeJson('apps/manage-api/package.json', manageApiPackageJson, { spaces: 2 });
330
- // Run API package
331
- const runApiPackageJson = {
332
- name: `@${dirName}/run-api`,
333
- version: '0.1.0',
334
- description: 'Run API for agents',
335
- type: 'module',
336
- scripts: {
337
- dev: 'tsx watch src/index.ts',
338
- start: 'node dist/index.js',
339
- },
340
- dependencies: {
341
- '@inkeep/agents-run-api': '^0.1.1',
342
- '@inkeep/agents-core': '^0.1.0',
343
- '@hono/node-server': '^1.14.3',
344
- },
345
- devDependencies: {
346
- '@types/node': '^20.12.0',
347
- tsx: '^4.19.0',
348
- typescript: '^5.4.0',
349
- },
350
- engines: {
351
- node: '>=22.x',
352
- },
353
- };
354
- await fs.writeJson('apps/run-api/package.json', runApiPackageJson, { spaces: 2 });
355
- // TypeScript configs for API services
356
- const apiTsConfig = {
357
- compilerOptions: {
358
- target: 'ES2022',
359
- module: 'ESNext',
360
- moduleResolution: 'bundler',
361
- strict: true,
362
- esModuleInterop: true,
363
- skipLibCheck: true,
364
- forceConsistentCasingInFileNames: true,
365
- declaration: true,
366
- outDir: './dist',
367
- rootDir: '..',
368
- allowImportingTsExtensions: false,
369
- resolveJsonModule: true,
370
- isolatedModules: true,
371
- noEmit: false,
372
- },
373
- include: ['src/**/*', '../shared/**/*'],
374
- exclude: ['node_modules', 'dist', '**/*.test.ts'],
375
- };
376
- await fs.writeJson('apps/manage-api/tsconfig.json', apiTsConfig, { spaces: 2 });
377
- await fs.writeJson('apps/run-api/tsconfig.json', apiTsConfig, { spaces: 2 });
378
- // No tsconfig needed for UI since we're using the packaged version
267
+ await fs.ensureDir(`src`);
379
268
  }
380
269
  async function createEnvironmentFiles(config) {
381
270
  // Root .env file
@@ -388,6 +277,7 @@ DB_FILE_NAME=file:./local.db
388
277
  # AI Provider Keys
389
278
  ANTHROPIC_API_KEY=${config.anthropicKey || 'your-anthropic-key-here'}
390
279
  OPENAI_API_KEY=${config.openAiKey || 'your-openai-key-here'}
280
+ GOOGLE_GENERATIVE_AI_API_KEY=${config.googleKey || 'your-google-key-here'}
391
281
 
392
282
  # Logging
393
283
  LOG_LEVEL=debug
@@ -403,10 +293,6 @@ RUN_API_PORT=${config.runApiPort}
403
293
  // Create .env.example
404
294
  const envExample = envContent.replace(/=.+$/gm, '=');
405
295
  await fs.writeFile('.env.example', envExample);
406
- // Create setup script
407
- await createSetupScript(config);
408
- // Create dev-setup script
409
- await createDevSetupScript(config);
410
296
  // Create .env files for each API service
411
297
  const runApiEnvContent = `# Environment
412
298
  ENVIRONMENT=development
@@ -417,6 +303,7 @@ DB_FILE_NAME=file:../../local.db
417
303
  # AI Provider Keys
418
304
  ANTHROPIC_API_KEY=${config.anthropicKey || 'your-anthropic-key-here'}
419
305
  OPENAI_API_KEY=${config.openAiKey || 'your-openai-key-here'}
306
+ GOOGLE_GENERATIVE_AI_API_KEY=${config.googleKey || 'your-google-key-here'}
420
307
 
421
308
  AGENTS_RUN_API_URL=http://localhost:${config.runApiPort}
422
309
  `;
@@ -430,287 +317,8 @@ AGENTS_MANAGE_API_URL=http://localhost:${config.manageApiPort}
430
317
  `;
431
318
  await fs.writeFile('apps/manage-api/.env', manageApiEnvContent);
432
319
  await fs.writeFile('apps/run-api/.env', runApiEnvContent);
433
- // Create .gitignore
434
- const gitignore = `# Dependencies
435
- node_modules/
436
- .pnpm-store/
437
-
438
- # Environment variables
439
- .env
440
- .env.local
441
- .env.development.local
442
- .env.test.local
443
- .env.production.local
444
-
445
- # Build outputs
446
- dist/
447
- build/
448
- .next/
449
- .turbo/
450
-
451
- # Logs
452
- *.log
453
- logs/
454
-
455
- # Database
456
- *.db
457
- *.sqlite
458
- *.sqlite3
459
-
460
- # IDE
461
- .vscode/
462
- .idea/
463
- *.swp
464
- *.swo
465
-
466
- # OS
467
- .DS_Store
468
- Thumbs.db
469
-
470
- # Coverage
471
- coverage/
472
- .nyc_output/
473
-
474
- # Temporary files
475
- *.tmp
476
- *.temp
477
- .cache/
478
-
479
- # Runtime data
480
- pids/
481
- *.pid
482
- *.seed
483
- *.pid.lock
484
- `;
485
- await fs.writeFile('.gitignore', gitignore);
486
- // Create biome.json
487
- const biomeConfig = {
488
- linter: {
489
- enabled: true,
490
- rules: {
491
- recommended: true,
492
- },
493
- },
494
- formatter: {
495
- enabled: true,
496
- indentStyle: 'space',
497
- indentWidth: 2,
498
- },
499
- organizeImports: {
500
- enabled: true,
501
- },
502
- javascript: {
503
- formatter: {
504
- semicolons: 'always',
505
- quoteStyle: 'single',
506
- },
507
- },
508
- };
509
- await fs.writeJson('biome.json', biomeConfig, { spaces: 2 });
510
- }
511
- async function createSetupScript(config) {
512
- const setupScriptContent = `#!/usr/bin/env node
513
-
514
- import { createDatabaseClient, createProject, getProject } from '@inkeep/agents-core';
515
- import dotenv from 'dotenv';
516
-
517
- // Load environment variables
518
- dotenv.config();
519
-
520
- const dbUrl = process.env.DB_FILE_NAME || 'file:local.db';
521
- const tenantId = '${config.tenantId}';
522
- const projectId = '${config.projectId}';
523
- const projectName = '${config.projectId}';
524
- const projectDescription = 'Generated Inkeep Agents project';
525
-
526
- async function setupProject() {
527
- console.log('🚀 Setting up your Inkeep Agents project...');
528
-
529
- try {
530
- const dbClient = createDatabaseClient({ url: dbUrl });
531
-
532
- // Check if project already exists
533
- console.log('📋 Checking if project already exists...');
534
- try {
535
- const existingProject = await getProject(dbClient)({
536
- id: projectId,
537
- tenantId: tenantId
538
- });
539
-
540
- if (existingProject) {
541
- console.log('✅ Project already exists in database:', existingProject.name);
542
- console.log('🎯 Project ID:', projectId);
543
- console.log('🏢 Tenant ID:', tenantId);
544
- return;
545
- }
546
- } catch (error) {
547
- // Project doesn't exist, continue with creation
548
- }
549
-
550
- // Create the project in the database
551
- console.log('📦 Creating project in database...');
552
- await createProject(dbClient)({
553
- id: projectId,
554
- tenantId: tenantId,
555
- name: projectName,
556
- description: projectDescription,
557
- models: ${JSON.stringify(config.modelSettings, null, 2)},
558
- });
559
-
560
- console.log('✅ Project created successfully!');
561
- console.log('🎯 Project ID:', projectId);
562
- console.log('🏢 Tenant ID:', tenantId);
563
- console.log('');
564
- console.log('🎉 Setup complete! Your development servers are running.');
565
- console.log('');
566
- console.log('📋 Available URLs:');
567
- console.log(' - Management UI: http://localhost:${config.manageApiPort}');
568
- console.log(' - Runtime API: http://localhost:${config.runApiPort}');
569
- console.log('');
570
- console.log('🚀 Ready to build agents!');
571
-
572
- } catch (error) {
573
- console.error('❌ Failed to setup project:', error);
574
- process.exit(1);
575
- }
576
- }
577
-
578
- setupProject();
579
- `;
580
- await fs.writeFile('scripts/setup.js', setupScriptContent);
581
- // Make the script executable
582
- await fs.chmod('scripts/setup.js', 0o755);
583
- }
584
- async function createDevSetupScript(config) {
585
- const devSetupScriptContent = `#!/usr/bin/env node
586
-
587
- import { spawn } from 'child_process';
588
- import { promisify } from 'util';
589
- import { exec } from 'child_process';
590
-
591
- const execAsync = promisify(exec);
592
-
593
- async function devSetup() {
594
- console.log('🚀 Starting Inkeep Agents development environment...');
595
- console.log('');
596
-
597
- try {
598
- // Start development servers in background
599
- console.log('📡 Starting development servers...');
600
- const devProcess = spawn('pnpm', ['dev'], {
601
- stdio: ['pipe', 'pipe', 'pipe'],
602
- detached: false
603
- });
604
-
605
- // Give servers time to start
606
- console.log('⏳ Waiting for servers to start...');
607
- await new Promise(resolve => setTimeout(resolve, 5000));
608
-
609
- console.log('');
610
- console.log('📦 Servers are ready! Setting up project in database...');
611
-
612
- // Run the setup script
613
- await execAsync('pnpm setup');
614
-
615
- console.log('');
616
- console.log('🎉 Development environment is ready!');
617
- console.log('');
618
- console.log('📋 Available URLs:');
619
- console.log(\` - Management UI: http://localhost:${config.manageApiPort}\`);
620
- console.log(\` - Runtime API: http://localhost:${config.runApiPort}\`);
621
- console.log('');
622
- console.log('✨ The servers will continue running. Press Ctrl+C to stop.');
623
-
624
- // Keep the script running so servers don't terminate
625
- process.on('SIGINT', () => {
626
- console.log('\\n👋 Shutting down development servers...');
627
- devProcess.kill();
628
- process.exit(0);
629
- });
630
-
631
- // Wait for the dev process to finish or be killed
632
- devProcess.on('close', (code) => {
633
- console.log(\`Development servers stopped with code \${code}\`);
634
- process.exit(code);
635
- });
636
-
637
- } catch (error) {
638
- console.error('❌ Failed to start development environment:', error.message);
639
- process.exit(1);
640
- }
641
- }
642
-
643
- devSetup();
644
- `;
645
- await fs.writeFile('scripts/dev-setup.js', devSetupScriptContent);
646
- await fs.chmod('scripts/dev-setup.js', 0o755);
647
320
  }
648
321
  async function createServiceFiles(config) {
649
- const agentsGraph = `import { agent, agentGraph, mcpTool } from '@inkeep/agents-sdk';
650
-
651
- // MCP Tools
652
- const forecastWeatherTool = mcpTool({
653
- id: 'fUI2riwrBVJ6MepT8rjx0',
654
- name: 'Forecast weather',
655
- serverUrl: 'https://weather-forecast-mcp.vercel.app/mcp',
656
- });
657
-
658
- const geocodeAddressTool = mcpTool({
659
- id: 'fdxgfv9HL7SXlfynPx8hf',
660
- name: 'Geocode address',
661
- serverUrl: 'https://geocoder-mcp.vercel.app/mcp',
662
- });
663
-
664
- // Agents
665
- const weatherAssistant = agent({
666
- id: 'weather-assistant',
667
- name: 'Weather assistant',
668
- description: 'Responsible for routing between the geocoder agent and weather forecast agent',
669
- prompt:
670
- 'You are a helpful assistant. When the user asks about the weather in a given location, first ask the geocoder agent for the coordinates, and then pass those coordinates to the weather forecast agent to get the weather forecast',
671
- canDelegateTo: () => [weatherForecaster, geocoderAgent],
672
- });
673
-
674
- const weatherForecaster = agent({
675
- id: 'weather-forecaster',
676
- name: 'Weather forecaster',
677
- description:
678
- 'This agent is responsible for taking in coordinates and returning the forecast for the weather at that location',
679
- prompt:
680
- 'You are a helpful assistant responsible for taking in coordinates and returning the forecast for that location using your forecasting tool',
681
- canUse: () => [forecastWeatherTool],
682
- });
683
-
684
- const geocoderAgent = agent({
685
- id: 'geocoder-agent',
686
- name: 'Geocoder agent',
687
- description: 'Responsible for converting location or address into coordinates',
688
- prompt:
689
- 'You are a helpful assistant responsible for converting location or address into coordinates using your geocode tool',
690
- canUse: () => [geocodeAddressTool],
691
- });
692
-
693
- // Agent Graph
694
- export const weatherGraph = agentGraph({
695
- id: 'weather-graph',
696
- name: 'Weather graph',
697
- defaultAgent: weatherAssistant,
698
- agents: () => [weatherAssistant, weatherForecaster, geocoderAgent],
699
- });`;
700
- await fs.writeFile(`src/${config.projectId}/weather.graph.ts`, agentsGraph);
701
- // Inkeep config (if using CLI)
702
- const inkeepConfig = `import { defineConfig } from '@inkeep/agents-cli/config';
703
-
704
- const config = defineConfig({
705
- tenantId: "${config.tenantId}",
706
- projectId: "${config.projectId}",
707
- agentsManageApiUrl: \`http://localhost:\${process.env.MANAGE_API_PORT || '3002'}\`,
708
- agentsRunApiUrl: \`http://localhost:\${process.env.RUN_API_PORT || '3003'}\`,
709
- modelSettings: ${JSON.stringify(config.modelSettings, null, 2)},
710
- });
711
-
712
- export default config;`;
713
- await fs.writeFile(`src/${config.projectId}/inkeep.config.ts`, inkeepConfig);
714
322
  // Create .env file for the project directory (for inkeep CLI commands)
715
323
  const projectEnvContent = `# Environment
716
324
  ENVIRONMENT=development
@@ -719,110 +327,31 @@ ENVIRONMENT=development
719
327
  DB_FILE_NAME=file:../../local.db
720
328
  `;
721
329
  await fs.writeFile(`src/${config.projectId}/.env`, projectEnvContent);
722
- // Shared credential stores
723
- const credentialStoresFile = `import {
724
- InMemoryCredentialStore,
725
- createNangoCredentialStore,
726
- createKeyChainStore,
727
- } from '@inkeep/agents-core';
728
-
729
- // Shared credential stores configuration for all services
730
- export const credentialStores = [
731
- new InMemoryCredentialStore('memory-default'),
732
- ...(process.env.NANGO_SECRET_KEY
733
- ? [
734
- createNangoCredentialStore('nango-default', {
735
- apiUrl: process.env.NANGO_HOST || 'https://api.nango.dev',
736
- secretKey: process.env.NANGO_SECRET_KEY,
737
- }),
738
- ]
739
- : []),
740
- createKeyChainStore('keychain-default'),
741
- ];
742
- `;
743
- await fs.writeFile('apps/shared/credential-stores.ts', credentialStoresFile);
744
- // Manage API
745
- const manageApiIndex = `import { serve } from '@hono/node-server';
746
- import { createManagementApp } from '@inkeep/agents-manage-api';
747
- import { getLogger } from '@inkeep/agents-core';
748
- import { credentialStores } from '../../shared/credential-stores.js';
749
-
750
- const logger = getLogger('management-api');
751
-
752
- // Create the Hono app
753
- const app = createManagementApp({
754
- serverConfig: {
755
- port: Number(process.env.MANAGE_API_PORT) || 3002,
756
- serverOptions: {
757
- requestTimeout: 60000,
758
- keepAliveTimeout: 60000,
759
- keepAlive: true,
760
- },
761
- },
762
- credentialStores,
763
- });
764
-
765
- const port = Number(process.env.MANAGE_API_PORT) || 3002;
766
-
767
- // Start the server using @hono/node-server
768
- serve(
769
- {
770
- fetch: app.fetch,
771
- port,
772
- },
773
- (info) => {
774
- logger.info({}, \`📝 Management API running on http://localhost:\${info.port}\`);
775
- logger.info({}, \`📝 OpenAPI documentation available at http://localhost:\${info.port}/openapi.json\`);
776
- }
777
- );`;
778
- await fs.writeFile('apps/manage-api/src/index.ts', manageApiIndex);
779
- // Run API
780
- const runApiIndex = `import { serve } from '@hono/node-server';
781
- import { createExecutionApp } from '@inkeep/agents-run-api';
782
- import { credentialStores } from '../../shared/credential-stores.js';
783
- import { getLogger } from '@inkeep/agents-core';
784
-
785
- const logger = getLogger('execution-api');
786
-
787
-
788
- // Create the Hono app
789
- const app = createExecutionApp({
790
- serverConfig: {
791
- port: Number(process.env.RUN_API_PORT) || 3003,
792
- serverOptions: {
793
- requestTimeout: 120000,
794
- keepAliveTimeout: 60000,
795
- keepAlive: true,
796
- },
797
- },
798
- credentialStores,
799
- });
800
-
801
- const port = Number(process.env.RUN_API_PORT) || 3003;
802
-
803
- // Start the server using @hono/node-server
804
- serve(
805
- {
806
- fetch: app.fetch,
807
- port,
808
- },
809
- (info) => {
810
- logger.info({}, \`📝 Run API running on http://localhost:\${info.port}\`);
811
- logger.info({}, \`📝 OpenAPI documentation available at http://localhost:\${info.port}/openapi.json\`);
812
- }
813
- );`;
814
- await fs.writeFile('apps/run-api/src/index.ts', runApiIndex);
815
- // Database configuration
816
- const drizzleConfig = `import { defineConfig } from 'drizzle-kit';
330
+ }
331
+ async function createInkeepConfig(config) {
332
+ const inkeepConfig = `import { defineConfig } from '@inkeep/agents-cli/config';
817
333
 
818
- export default defineConfig({
819
- schema: 'node_modules/@inkeep/agents-core/dist/db/schema.js',
820
- dialect: 'sqlite',
821
- dbCredentials: {
822
- url: process.env.DB_FILE_NAME || 'file:./local.db'
823
- },
334
+ const config = defineConfig({
335
+ tenantId: "${config.tenantId}",
336
+ projectId: "${config.projectId}",
337
+ agentsManageApiUrl: 'http://localhost:3002',
338
+ agentsRunApiUrl: 'http://localhost:3003',
339
+ modelSettings: ${JSON.stringify(config.modelSettings, null, 2)},
340
+ });
341
+
342
+ export default config;`;
343
+ await fs.writeFile(`src/${config.projectId}/inkeep.config.ts`, inkeepConfig);
344
+ if (config.customProject) {
345
+ const customIndexContent = `import { project } from '@inkeep/agents-sdk';
346
+
347
+ export const myProject = project({
348
+ id: "${config.projectId}",
349
+ name: "${config.projectId}",
350
+ description: "",
351
+ graphs: () => [],
824
352
  });`;
825
- await fs.writeFile('drizzle.config.ts', drizzleConfig);
353
+ await fs.writeFile(`src/${config.projectId}/index.ts`, customIndexContent);
354
+ }
826
355
  }
827
356
  async function createTurboConfig() {
828
357
  const turboConfig = {
@@ -903,7 +432,7 @@ This project follows a workspace structure with the following services:
903
432
  inkeep push weather.graph.ts
904
433
  \`\`\`
905
434
  - Follow the prompts to create the project and graph
906
- - Click on the \"View graph in UI:\" link to see the graph in the management dashboard
435
+ - Click on the "View graph in UI:" link to see the graph in the management dashboard
907
436
 
908
437
  ## Project Structure
909
438
 
@@ -996,22 +525,25 @@ Once services are running, view the OpenAPI documentation:
996
525
  async function installDependencies() {
997
526
  await execAsync('pnpm install');
998
527
  }
999
- async function setupProjectInDatabase() {
1000
- const s = p.spinner();
1001
- s.start('🚀 Starting development servers and setting up database...');
528
+ async function setupProjectInDatabase(config) {
529
+ // Start development servers in background
530
+ const { spawn } = await import('child_process');
531
+ const devProcess = spawn('pnpm', ['dev:apis'], {
532
+ stdio: ['pipe', 'pipe', 'pipe'],
533
+ detached: true, // Detach so we can kill the process group
534
+ cwd: process.cwd(),
535
+ });
536
+ // Give servers time to start
537
+ await new Promise((resolve) => setTimeout(resolve, 5000));
538
+ // Run inkeep push
1002
539
  try {
1003
- // Start development servers in background
1004
- const { spawn } = await import('child_process');
1005
- const devProcess = spawn('pnpm', ['dev'], {
1006
- stdio: ['pipe', 'pipe', 'pipe'],
1007
- detached: true, // Detach so we can kill the process group
1008
- cwd: process.cwd(),
1009
- });
1010
- // Give servers time to start
1011
- await new Promise((resolve) => setTimeout(resolve, 5000));
1012
- s.message('📦 Servers ready! Creating project in database...');
1013
- // Run the database setup
1014
- await execAsync('node scripts/setup.js');
540
+ // Suppress all output
541
+ const { stdout, stderr } = await execAsync(`pnpm inkeep push --project src/${config.projectId}`);
542
+ }
543
+ catch (error) {
544
+ //Continue despite error - user can setup project manually
545
+ }
546
+ finally {
1015
547
  // Kill the dev servers and their child processes
1016
548
  if (devProcess.pid) {
1017
549
  try {
@@ -1032,18 +564,13 @@ async function setupProjectInDatabase() {
1032
564
  console.log('Note: Dev servers may still be running in background');
1033
565
  }
1034
566
  }
1035
- s.stop('✅ Project successfully created and configured in database!');
1036
- }
1037
- catch (error) {
1038
- s.stop('❌ Failed to setup project in database');
1039
- console.error('Setup error:', error);
1040
- // Continue anyway - user can run setup manually
1041
567
  }
1042
568
  }
1043
569
  async function setupDatabase() {
1044
570
  try {
1045
571
  // Run drizzle-kit push to create database file and apply schema
1046
572
  await execAsync('pnpm db:push');
573
+ await new Promise((resolve) => setTimeout(resolve, 1000));
1047
574
  }
1048
575
  catch (error) {
1049
576
  throw new Error(`Failed to setup database: ${error instanceof Error ? error.message : 'Unknown error'}`);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@inkeep/create-agents",
3
- "version": "0.0.0-dev-20250916015245",
3
+ "version": "0.0.0-dev-20250916190005",
4
4
  "description": "Create an Inkeep Agent Framework project",
5
5
  "type": "module",
6
6
  "bin": {
@@ -30,10 +30,12 @@
30
30
  "dependencies": {
31
31
  "@clack/prompts": "^0.11.0",
32
32
  "commander": "^12.0.0",
33
+ "degit": "^2.8.4",
33
34
  "fs-extra": "^11.0.0",
34
35
  "picocolors": "^1.0.0"
35
36
  },
36
37
  "devDependencies": {
38
+ "@types/degit": "^2.8.6",
37
39
  "@types/fs-extra": "^11.0.0",
38
40
  "@types/node": "^20.12.0",
39
41
  "tsx": "^4.7.0",