@inkeep/create-agents 0.0.0-dev-20250915202938 → 0.0.0-dev-20250916184039

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
@@ -32,9 +32,11 @@ 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
+ template?: string;
40
+ customProjectId?: string;
39
41
  }) => Promise<void>;
40
42
  export declare function createCommand(dirName?: string, options?: any): Promise<void>;
package/dist/utils.js CHANGED
@@ -4,6 +4,7 @@ import fs from 'fs-extra';
4
4
  import { exec } from 'child_process';
5
5
  import { promisify } from 'util';
6
6
  import path from 'path';
7
+ import { cloneTemplate, getAvailableTemplates } from './templates.js';
7
8
  const execAsync = promisify(exec);
8
9
  export const defaultDualModelConfigurations = {
9
10
  base: {
@@ -39,10 +40,35 @@ export const defaultAnthropicModelConfigurations = {
39
40
  },
40
41
  };
41
42
  export const createAgents = async (args = {}) => {
42
- let { projectId, dirName, openAiKey, anthropicKey } = args;
43
+ let { dirName, openAiKey, anthropicKey, 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,25 +89,12 @@ 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' },
87
100
  ],
@@ -91,7 +104,7 @@ export const createAgents = async (args = {}) => {
91
104
  process.exit(0);
92
105
  }
93
106
  // Prompt for keys based on selection
94
- if (providerChoice === 'anthropic' || providerChoice === 'both') {
107
+ if (providerChoice === 'anthropic') {
95
108
  const anthropicKeyResponse = await p.text({
96
109
  message: 'Enter your Anthropic API key:',
97
110
  placeholder: 'sk-ant-...',
@@ -108,7 +121,7 @@ export const createAgents = async (args = {}) => {
108
121
  }
109
122
  anthropicKey = anthropicKeyResponse;
110
123
  }
111
- if (providerChoice === 'openai' || providerChoice === 'both') {
124
+ if (providerChoice === 'openai') {
112
125
  const openAiKeyResponse = await p.text({
113
126
  message: 'Enter your OpenAI API key:',
114
127
  placeholder: 'sk-...',
@@ -166,6 +179,8 @@ export const createAgents = async (args = {}) => {
166
179
  const s = p.spinner();
167
180
  s.start('Creating directory structure...');
168
181
  try {
182
+ const agentsTemplateRepo = 'https://github.com/inkeep/create-agents-template';
183
+ const projectTemplateRepo = templateName ? `https://github.com/inkeep/agents-cookbook/templates/${templateName}` : null;
169
184
  const directoryPath = path.resolve(process.cwd(), dirName);
170
185
  // Check if directory already exists
171
186
  if (await fs.pathExists(directoryPath)) {
@@ -180,8 +195,10 @@ export const createAgents = async (args = {}) => {
180
195
  s.start('Cleaning existing directory...');
181
196
  await fs.emptyDir(directoryPath);
182
197
  }
183
- // Create the project directory
184
- await fs.ensureDir(directoryPath);
198
+ // Clone the template repository
199
+ s.message('Building template...');
200
+ await cloneTemplate(agentsTemplateRepo, directoryPath);
201
+ // Change to the project directory
185
202
  process.chdir(directoryPath);
186
203
  const config = {
187
204
  dirName,
@@ -192,25 +209,30 @@ export const createAgents = async (args = {}) => {
192
209
  manageApiPort: manageApiPort || '3002',
193
210
  runApiPort: runApiPort || '3003',
194
211
  modelSettings: defaultModelSettings,
212
+ customProject: customProjectId ? true : false,
195
213
  };
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);
214
+ // Create workspace structure for project-specific files
215
+ s.message('Setting up project structure...');
216
+ await createWorkspaceStructure();
202
217
  // Create environment files
203
218
  s.message('Setting up environment files...');
204
219
  await createEnvironmentFiles(config);
220
+ // Create project template folder (only if template is specified)
221
+ if (projectTemplateRepo) {
222
+ s.message('Creating project template folder...');
223
+ const templateTargetPath = `src/${projectId}`;
224
+ await cloneTemplate(projectTemplateRepo, templateTargetPath);
225
+ }
226
+ else {
227
+ s.message('Creating empty project folder...');
228
+ await fs.ensureDir(`src/${projectId}`);
229
+ }
230
+ // create or overwrite inkeep.config.ts
231
+ s.message('Creating inkeep.config.ts...');
232
+ await createInkeepConfig(config);
205
233
  // Create service files
206
234
  s.message('Creating service files...');
207
235
  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
236
  // Install dependencies
215
237
  s.message('Installing dependencies (this may take a while)...');
216
238
  await installDependencies();
@@ -218,8 +240,9 @@ export const createAgents = async (args = {}) => {
218
240
  s.message('Setting up database...');
219
241
  await setupDatabase();
220
242
  // Setup project in database
221
- s.message('Setting up project in database...');
222
- await setupProjectInDatabase();
243
+ s.message('Pushing project...');
244
+ await setupProjectInDatabase(config);
245
+ s.message('Project setup complete!');
223
246
  s.stop();
224
247
  // Success message with next steps
225
248
  p.note(`${color.green('✓')} Project created at: ${color.cyan(directoryPath)}\n\n` +
@@ -236,7 +259,7 @@ export const createAgents = async (args = {}) => {
236
259
  ` • Manage UI: Available with management API\n` +
237
260
  `\n${color.yellow('Configuration:')}\n` +
238
261
  ` • Edit .env for environment variables\n` +
239
- ` • Edit src/${projectId}/weather.graph.ts for agent definitions\n` +
262
+ ` • Edit files in src/${projectId}/ for agent definitions\n` +
240
263
  ` • Use 'inkeep push' to deploy agents to the platform\n` +
241
264
  ` • Use 'inkeep chat' to test your agents locally\n`, 'Ready to go!');
242
265
  }
@@ -246,138 +269,9 @@ export const createAgents = async (args = {}) => {
246
269
  process.exit(1);
247
270
  }
248
271
  };
249
- async function createWorkspaceStructure(projectId) {
272
+ async function createWorkspaceStructure() {
250
273
  // 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: [
288
- 'keytar'
289
- ]
290
- },
291
- };
292
- await fs.writeJson('package.json', rootPackageJson, { spaces: 2 });
293
- // Create pnpm-workspace.yaml for pnpm workspaces
294
- const pnpmWorkspace = `packages:
295
- - "apps/*"
296
- `;
297
- await fs.writeFile('pnpm-workspace.yaml', pnpmWorkspace);
298
- // Add shared dependencies to root package.json
299
- rootPackageJson.dependencies = {
300
- '@inkeep/agents-core': '^0.1.0',
301
- '@inkeep/agents-sdk': '^0.1.0',
302
- dotenv: '^16.0.0',
303
- zod: '^4.1.5',
304
- };
305
- await fs.writeJson('package.json', rootPackageJson, { spaces: 2 });
306
- // Manage API package
307
- const manageApiPackageJson = {
308
- name: `@${dirName}/manage-api`,
309
- version: '0.1.0',
310
- description: 'Manage API for agents',
311
- type: 'module',
312
- scripts: {
313
- build: 'tsc',
314
- dev: 'tsx watch src/index.ts',
315
- start: 'node dist/index.js',
316
- },
317
- dependencies: {
318
- '@inkeep/agents-manage-api': '^0.1.1',
319
- '@inkeep/agents-core': '^0.1.0',
320
- '@hono/node-server': '^1.14.3',
321
- },
322
- devDependencies: {
323
- '@types/node': '^20.12.0',
324
- tsx: '^4.19.0',
325
- typescript: '^5.4.0',
326
- },
327
- engines: {
328
- node: '>=22.x',
329
- },
330
- };
331
- await fs.writeJson('apps/manage-api/package.json', manageApiPackageJson, { spaces: 2 });
332
- // Run API package
333
- const runApiPackageJson = {
334
- name: `@${dirName}/run-api`,
335
- version: '0.1.0',
336
- description: 'Run API for agents',
337
- type: 'module',
338
- scripts: {
339
- dev: 'tsx watch src/index.ts',
340
- start: 'node dist/index.js',
341
- },
342
- dependencies: {
343
- '@inkeep/agents-run-api': '^0.1.1',
344
- '@inkeep/agents-core': '^0.1.0',
345
- '@hono/node-server': '^1.14.3',
346
- },
347
- devDependencies: {
348
- '@types/node': '^20.12.0',
349
- tsx: '^4.19.0',
350
- typescript: '^5.4.0',
351
- },
352
- engines: {
353
- node: '>=22.x',
354
- },
355
- };
356
- await fs.writeJson('apps/run-api/package.json', runApiPackageJson, { spaces: 2 });
357
- // TypeScript configs for API services
358
- const apiTsConfig = {
359
- compilerOptions: {
360
- target: 'ES2022',
361
- module: 'ESNext',
362
- moduleResolution: 'bundler',
363
- strict: true,
364
- esModuleInterop: true,
365
- skipLibCheck: true,
366
- forceConsistentCasingInFileNames: true,
367
- declaration: true,
368
- outDir: './dist',
369
- rootDir: '..',
370
- allowImportingTsExtensions: false,
371
- resolveJsonModule: true,
372
- isolatedModules: true,
373
- noEmit: false,
374
- },
375
- include: ['src/**/*', '../shared/**/*'],
376
- exclude: ['node_modules', 'dist', '**/*.test.ts'],
377
- };
378
- await fs.writeJson('apps/manage-api/tsconfig.json', apiTsConfig, { spaces: 2 });
379
- await fs.writeJson('apps/run-api/tsconfig.json', apiTsConfig, { spaces: 2 });
380
- // No tsconfig needed for UI since we're using the packaged version
274
+ await fs.ensureDir(`src`);
381
275
  }
382
276
  async function createEnvironmentFiles(config) {
383
277
  // Root .env file
@@ -390,25 +284,11 @@ DB_FILE_NAME=file:./local.db
390
284
  # AI Provider Keys
391
285
  ANTHROPIC_API_KEY=${config.anthropicKey || 'your-anthropic-key-here'}
392
286
  OPENAI_API_KEY=${config.openAiKey || 'your-openai-key-here'}
393
-
394
- # Logging
395
- LOG_LEVEL=debug
396
-
397
- # Service Ports
398
- MANAGE_API_PORT=${config.manageApiPort}
399
- RUN_API_PORT=${config.runApiPort}
400
-
401
- # UI Configuration (for dashboard)
402
-
403
287
  `;
404
288
  await fs.writeFile('.env', envContent);
405
289
  // Create .env.example
406
290
  const envExample = envContent.replace(/=.+$/gm, '=');
407
291
  await fs.writeFile('.env.example', envExample);
408
- // Create setup script
409
- await createSetupScript(config);
410
- // Create dev-setup script
411
- await createDevSetupScript(config);
412
292
  // Create .env files for each API service
413
293
  const runApiEnvContent = `# Environment
414
294
  ENVIRONMENT=development
@@ -432,287 +312,8 @@ AGENTS_MANAGE_API_URL=http://localhost:${config.manageApiPort}
432
312
  `;
433
313
  await fs.writeFile('apps/manage-api/.env', manageApiEnvContent);
434
314
  await fs.writeFile('apps/run-api/.env', runApiEnvContent);
435
- // Create .gitignore
436
- const gitignore = `# Dependencies
437
- node_modules/
438
- .pnpm-store/
439
-
440
- # Environment variables
441
- .env
442
- .env.local
443
- .env.development.local
444
- .env.test.local
445
- .env.production.local
446
-
447
- # Build outputs
448
- dist/
449
- build/
450
- .next/
451
- .turbo/
452
-
453
- # Logs
454
- *.log
455
- logs/
456
-
457
- # Database
458
- *.db
459
- *.sqlite
460
- *.sqlite3
461
-
462
- # IDE
463
- .vscode/
464
- .idea/
465
- *.swp
466
- *.swo
467
-
468
- # OS
469
- .DS_Store
470
- Thumbs.db
471
-
472
- # Coverage
473
- coverage/
474
- .nyc_output/
475
-
476
- # Temporary files
477
- *.tmp
478
- *.temp
479
- .cache/
480
-
481
- # Runtime data
482
- pids/
483
- *.pid
484
- *.seed
485
- *.pid.lock
486
- `;
487
- await fs.writeFile('.gitignore', gitignore);
488
- // Create biome.json
489
- const biomeConfig = {
490
- linter: {
491
- enabled: true,
492
- rules: {
493
- recommended: true,
494
- },
495
- },
496
- formatter: {
497
- enabled: true,
498
- indentStyle: 'space',
499
- indentWidth: 2,
500
- },
501
- organizeImports: {
502
- enabled: true,
503
- },
504
- javascript: {
505
- formatter: {
506
- semicolons: 'always',
507
- quoteStyle: 'single',
508
- },
509
- },
510
- };
511
- await fs.writeJson('biome.json', biomeConfig, { spaces: 2 });
512
- }
513
- async function createSetupScript(config) {
514
- const setupScriptContent = `#!/usr/bin/env node
515
-
516
- import { createDatabaseClient, createProject, getProject } from '@inkeep/agents-core';
517
- import dotenv from 'dotenv';
518
-
519
- // Load environment variables
520
- dotenv.config();
521
-
522
- const dbUrl = process.env.DB_FILE_NAME || 'file:local.db';
523
- const tenantId = '${config.tenantId}';
524
- const projectId = '${config.projectId}';
525
- const projectName = '${config.projectId}';
526
- const projectDescription = 'Generated Inkeep Agents project';
527
-
528
- async function setupProject() {
529
- console.log('🚀 Setting up your Inkeep Agents project...');
530
-
531
- try {
532
- const dbClient = createDatabaseClient({ url: dbUrl });
533
-
534
- // Check if project already exists
535
- console.log('📋 Checking if project already exists...');
536
- try {
537
- const existingProject = await getProject(dbClient)({
538
- id: projectId,
539
- tenantId: tenantId
540
- });
541
-
542
- if (existingProject) {
543
- console.log('✅ Project already exists in database:', existingProject.name);
544
- console.log('🎯 Project ID:', projectId);
545
- console.log('🏢 Tenant ID:', tenantId);
546
- return;
547
- }
548
- } catch (error) {
549
- // Project doesn't exist, continue with creation
550
- }
551
-
552
- // Create the project in the database
553
- console.log('📦 Creating project in database...');
554
- await createProject(dbClient)({
555
- id: projectId,
556
- tenantId: tenantId,
557
- name: projectName,
558
- description: projectDescription,
559
- models: ${JSON.stringify(config.modelSettings, null, 2)},
560
- });
561
-
562
- console.log('✅ Project created successfully!');
563
- console.log('🎯 Project ID:', projectId);
564
- console.log('🏢 Tenant ID:', tenantId);
565
- console.log('');
566
- console.log('🎉 Setup complete! Your development servers are running.');
567
- console.log('');
568
- console.log('📋 Available URLs:');
569
- console.log(' - Management UI: http://localhost:${config.manageApiPort}');
570
- console.log(' - Runtime API: http://localhost:${config.runApiPort}');
571
- console.log('');
572
- console.log('🚀 Ready to build agents!');
573
-
574
- } catch (error) {
575
- console.error('❌ Failed to setup project:', error);
576
- process.exit(1);
577
- }
578
- }
579
-
580
- setupProject();
581
- `;
582
- await fs.writeFile('scripts/setup.js', setupScriptContent);
583
- // Make the script executable
584
- await fs.chmod('scripts/setup.js', 0o755);
585
- }
586
- async function createDevSetupScript(config) {
587
- const devSetupScriptContent = `#!/usr/bin/env node
588
-
589
- import { spawn } from 'child_process';
590
- import { promisify } from 'util';
591
- import { exec } from 'child_process';
592
-
593
- const execAsync = promisify(exec);
594
-
595
- async function devSetup() {
596
- console.log('🚀 Starting Inkeep Agents development environment...');
597
- console.log('');
598
-
599
- try {
600
- // Start development servers in background
601
- console.log('📡 Starting development servers...');
602
- const devProcess = spawn('pnpm', ['dev'], {
603
- stdio: ['pipe', 'pipe', 'pipe'],
604
- detached: false
605
- });
606
-
607
- // Give servers time to start
608
- console.log('⏳ Waiting for servers to start...');
609
- await new Promise(resolve => setTimeout(resolve, 5000));
610
-
611
- console.log('');
612
- console.log('📦 Servers are ready! Setting up project in database...');
613
-
614
- // Run the setup script
615
- await execAsync('pnpm setup');
616
-
617
- console.log('');
618
- console.log('🎉 Development environment is ready!');
619
- console.log('');
620
- console.log('📋 Available URLs:');
621
- console.log(\` - Management UI: http://localhost:${config.manageApiPort}\`);
622
- console.log(\` - Runtime API: http://localhost:${config.runApiPort}\`);
623
- console.log('');
624
- console.log('✨ The servers will continue running. Press Ctrl+C to stop.');
625
-
626
- // Keep the script running so servers don't terminate
627
- process.on('SIGINT', () => {
628
- console.log('\\n👋 Shutting down development servers...');
629
- devProcess.kill();
630
- process.exit(0);
631
- });
632
-
633
- // Wait for the dev process to finish or be killed
634
- devProcess.on('close', (code) => {
635
- console.log(\`Development servers stopped with code \${code}\`);
636
- process.exit(code);
637
- });
638
-
639
- } catch (error) {
640
- console.error('❌ Failed to start development environment:', error.message);
641
- process.exit(1);
642
- }
643
- }
644
-
645
- devSetup();
646
- `;
647
- await fs.writeFile('scripts/dev-setup.js', devSetupScriptContent);
648
- await fs.chmod('scripts/dev-setup.js', 0o755);
649
315
  }
650
316
  async function createServiceFiles(config) {
651
- const agentsGraph = `import { agent, agentGraph, mcpTool } from '@inkeep/agents-sdk';
652
-
653
- // MCP Tools
654
- const forecastWeatherTool = mcpTool({
655
- id: 'fUI2riwrBVJ6MepT8rjx0',
656
- name: 'Forecast weather',
657
- serverUrl: 'https://weather-forecast-mcp.vercel.app/mcp',
658
- });
659
-
660
- const geocodeAddressTool = mcpTool({
661
- id: 'fdxgfv9HL7SXlfynPx8hf',
662
- name: 'Geocode address',
663
- serverUrl: 'https://geocoder-mcp.vercel.app/mcp',
664
- });
665
-
666
- // Agents
667
- const weatherAssistant = agent({
668
- id: 'weather-assistant',
669
- name: 'Weather assistant',
670
- description: 'Responsible for routing between the geocoder agent and weather forecast agent',
671
- prompt:
672
- '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',
673
- canDelegateTo: () => [weatherForecaster, geocoderAgent],
674
- });
675
-
676
- const weatherForecaster = agent({
677
- id: 'weather-forecaster',
678
- name: 'Weather forecaster',
679
- description:
680
- 'This agent is responsible for taking in coordinates and returning the forecast for the weather at that location',
681
- prompt:
682
- 'You are a helpful assistant responsible for taking in coordinates and returning the forecast for that location using your forecasting tool',
683
- canUse: () => [forecastWeatherTool],
684
- });
685
-
686
- const geocoderAgent = agent({
687
- id: 'geocoder-agent',
688
- name: 'Geocoder agent',
689
- description: 'Responsible for converting location or address into coordinates',
690
- prompt:
691
- 'You are a helpful assistant responsible for converting location or address into coordinates using your geocode tool',
692
- canUse: () => [geocodeAddressTool],
693
- });
694
-
695
- // Agent Graph
696
- export const weatherGraph = agentGraph({
697
- id: 'weather-graph',
698
- name: 'Weather graph',
699
- defaultAgent: weatherAssistant,
700
- agents: () => [weatherAssistant, weatherForecaster, geocoderAgent],
701
- });`;
702
- await fs.writeFile(`src/${config.projectId}/weather.graph.ts`, agentsGraph);
703
- // Inkeep config (if using CLI)
704
- const inkeepConfig = `import { defineConfig } from '@inkeep/agents-cli/config';
705
-
706
- const config = defineConfig({
707
- tenantId: "${config.tenantId}",
708
- projectId: "${config.projectId}",
709
- agentsManageApiUrl: \`http://localhost:\${process.env.MANAGE_API_PORT || '3002'}\`,
710
- agentsRunApiUrl: \`http://localhost:\${process.env.RUN_API_PORT || '3003'}\`,
711
- modelSettings: ${JSON.stringify(config.modelSettings, null, 2)},
712
- });
713
-
714
- export default config;`;
715
- await fs.writeFile(`src/${config.projectId}/inkeep.config.ts`, inkeepConfig);
716
317
  // Create .env file for the project directory (for inkeep CLI commands)
717
318
  const projectEnvContent = `# Environment
718
319
  ENVIRONMENT=development
@@ -721,306 +322,61 @@ ENVIRONMENT=development
721
322
  DB_FILE_NAME=file:../../local.db
722
323
  `;
723
324
  await fs.writeFile(`src/${config.projectId}/.env`, projectEnvContent);
724
- // Shared credential stores
725
- const credentialStoresFile = `import {
726
- InMemoryCredentialStore,
727
- createNangoCredentialStore,
728
- createKeyChainStore,
729
- } from '@inkeep/agents-core';
730
-
731
- // Shared credential stores configuration for all services
732
- export const credentialStores = [
733
- new InMemoryCredentialStore('memory-default'),
734
- ...(process.env.NANGO_SECRET_KEY
735
- ? [
736
- createNangoCredentialStore('nango-default', {
737
- apiUrl: process.env.NANGO_HOST || 'https://api.nango.dev',
738
- secretKey: process.env.NANGO_SECRET_KEY,
739
- }),
740
- ]
741
- : []),
742
- createKeyChainStore('keychain-default'),
743
- ];
744
- `;
745
- await fs.writeFile('apps/shared/credential-stores.ts', credentialStoresFile);
746
- // Manage API
747
- const manageApiIndex = `import { serve } from '@hono/node-server';
748
- import { createManagementApp } from '@inkeep/agents-manage-api';
749
- import { getLogger } from '@inkeep/agents-core';
750
- import { credentialStores } from '../../shared/credential-stores.js';
751
-
752
- const logger = getLogger('management-api');
753
-
754
- // Create the Hono app
755
- const app = createManagementApp({
756
- serverConfig: {
757
- port: Number(process.env.MANAGE_API_PORT) || 3002,
758
- serverOptions: {
759
- requestTimeout: 60000,
760
- keepAliveTimeout: 60000,
761
- keepAlive: true,
762
- },
763
- },
764
- credentialStores,
765
- });
766
-
767
- const port = Number(process.env.MANAGE_API_PORT) || 3002;
768
-
769
- // Start the server using @hono/node-server
770
- serve(
771
- {
772
- fetch: app.fetch,
773
- port,
774
- },
775
- (info) => {
776
- logger.info({}, \`📝 Management API running on http://localhost:\${info.port}\`);
777
- logger.info({}, \`📝 OpenAPI documentation available at http://localhost:\${info.port}/openapi.json\`);
778
- }
779
- );`;
780
- await fs.writeFile('apps/manage-api/src/index.ts', manageApiIndex);
781
- // Run API
782
- const runApiIndex = `import { serve } from '@hono/node-server';
783
- import { createExecutionApp } from '@inkeep/agents-run-api';
784
- import { credentialStores } from '../../shared/credential-stores.js';
785
- import { getLogger } from '@inkeep/agents-core';
786
-
787
- const logger = getLogger('execution-api');
788
-
789
-
790
- // Create the Hono app
791
- const app = createExecutionApp({
792
- serverConfig: {
793
- port: Number(process.env.RUN_API_PORT) || 3003,
794
- serverOptions: {
795
- requestTimeout: 120000,
796
- keepAliveTimeout: 60000,
797
- keepAlive: true,
798
- },
799
- },
800
- credentialStores,
801
- });
802
-
803
- const port = Number(process.env.RUN_API_PORT) || 3003;
804
-
805
- // Start the server using @hono/node-server
806
- serve(
807
- {
808
- fetch: app.fetch,
809
- port,
810
- },
811
- (info) => {
812
- logger.info({}, \`📝 Run API running on http://localhost:\${info.port}\`);
813
- logger.info({}, \`📝 OpenAPI documentation available at http://localhost:\${info.port}/openapi.json\`);
814
- }
815
- );`;
816
- await fs.writeFile('apps/run-api/src/index.ts', runApiIndex);
817
- // Database configuration
818
- const drizzleConfig = `import { defineConfig } from 'drizzle-kit';
819
-
820
- export default defineConfig({
821
- schema: 'node_modules/@inkeep/agents-core/dist/db/schema.js',
822
- dialect: 'sqlite',
823
- dbCredentials: {
824
- url: process.env.DB_FILE_NAME || 'file:./local.db'
825
- },
826
- });`;
827
- await fs.writeFile('drizzle.config.ts', drizzleConfig);
828
- }
829
- async function createTurboConfig() {
830
- const turboConfig = {
831
- $schema: 'https://turbo.build/schema.json',
832
- ui: 'tui',
833
- globalDependencies: ['**/.env', '**/.env.local', '**/.env.*'],
834
- globalEnv: [
835
- 'NODE_ENV',
836
- 'CI',
837
- 'ANTHROPIC_API_KEY',
838
- 'OPENAI_API_KEY',
839
- 'ENVIRONMENT',
840
- 'DB_FILE_NAME',
841
- 'MANAGE_API_PORT',
842
- 'RUN_API_PORT',
843
- 'LOG_LEVEL',
844
- 'NANGO_SECRET_KEY',
845
- ],
846
- tasks: {
847
- build: {
848
- dependsOn: ['^build'],
849
- inputs: ['$TURBO_DEFAULT$', '.env*'],
850
- outputs: ['dist/**', 'build/**', '.next/**', '!.next/cache/**'],
851
- },
852
- dev: {
853
- cache: false,
854
- persistent: true,
855
- },
856
- start: {
857
- dependsOn: ['build'],
858
- cache: false,
859
- },
860
- 'db:push': {
861
- cache: false,
862
- inputs: ['drizzle.config.ts', 'src/data/db/schema.ts'],
863
- },
864
- },
865
- };
866
- await fs.writeJson('turbo.json', turboConfig, { spaces: 2 });
867
325
  }
868
- async function createDocumentation(config) {
869
- const readme = `# ${config.dirName}
870
-
871
- An Inkeep Agent Framework project with multi-service architecture.
872
-
873
- ## Architecture
874
-
875
- This project follows a workspace structure with the following services:
876
-
877
- - **Agents Manage API** (Port 3002): Agent configuration and managemen
878
- - Handles entity management and configuration endpoints.
879
- - **Agents Run API** (Port 3003): Agent execution and chat processing
880
- - Handles agent communication. You can interact with your agents either over MCP from an MCP client or through our React UI components library
881
- - **Agents Manage UI** (Port 3000): Web interface available via \`inkeep dev\`
882
- - The agent framework visual builder. From the builder you can create, manage and visualize all your graphs.
883
-
884
- ## Quick Start
885
- 1. **Install the Inkeep CLI:**
886
- \`\`\`bash
887
- pnpm install -g @inkeep/agents-cli
888
- \`\`\`
889
-
890
- 1. **Start services:**
891
- \`\`\`bash
892
- # Start Agents Manage API and Agents Run API
893
- pnpm dev
894
-
895
- # Start the Dashboard
896
- inkeep dev
897
- \`\`\`
898
-
899
- 3. **Deploy your first agent graph:**
900
- \`\`\`bash
901
- # Navigate to your project's graph directory
902
- cd src/${config.projectId}/
903
-
904
- # Push the weather graph to create it
905
- inkeep push weather.graph.ts
906
- \`\`\`
907
- - Follow the prompts to create the project and graph
908
- - Click on the \"View graph in UI:\" link to see the graph in the management dashboard
909
-
910
- ## Project Structure
911
-
912
- \`\`\`
913
- ${config.dirName}/
914
- ├── src/
915
- │ ├── /${config.projectId} # Agent configurations
916
- ├── apps/
917
- │ ├── manage-api/ # Agents Manage API service
918
- │ ├── run-api/ # Agents Run API service
919
- │ └── shared/ # Shared code between API services
920
- │ └── credential-stores.ts # Shared credential store configuration
921
- ├── turbo.json # Turbo configuration
922
- ├── pnpm-workspace.yaml # pnpm workspace configuration
923
- └── package.json # Root package configuration
924
- \`\`\`
925
-
926
- ## Configuration
927
-
928
- ### Environment Variables
929
-
930
- Environment variables are defined in the following places:
931
-
932
- - \`apps/manage-api/.env\`: Agents Manage API environment variables
933
- - \`apps/run-api/.env\`: Agents Run API environment variables
934
- - \`src/${config.projectId}/.env\`: Inkeep CLI environment variables
935
- - \`.env\`: Root environment variables
936
-
937
- To change the API keys used by your agents modify \`apps/run-api/.env\`. You are required to define at least one LLM provider key.
938
-
939
- \`\`\`bash
940
- # AI Provider Keys
941
- ANTHROPIC_API_KEY=your-anthropic-key-here
942
- OPENAI_API_KEY=your-openai-key-here
943
- \`\`\`
944
-
945
-
946
-
947
- ### Agent Configuration
948
-
949
- Your graphs are defined in \`src/${config.projectId}/weather.graph.ts\`. The default setup includes:
950
-
951
- - **Weather Graph**: A graph that can forecast the weather in a given location.
952
-
953
- Your inkeep configuration is defined in \`src/${config.projectId}/inkeep.config.ts\`. The inkeep configuration is used to configure defaults for the inkeep CLI. The configuration includes:
954
-
955
- - \`tenantId\`: The tenant ID
956
- - \`projectId\`: The project ID
957
- - \`agentsManageApiUrl\`: The Manage API URL
958
- - \`agentsRunApiUrl\`: The Run API URL
959
-
960
-
961
- ## Development
962
-
963
- ### Updating Your Agents
964
-
965
- 1. Edit \`src/${config.projectId}/weather.graph.ts\`
966
- 2. Push the graph to the platform to update: \`inkeep pus weather.graph.ts\`
967
-
968
- ### API Documentation
969
-
970
- Once services are running, view the OpenAPI documentation:
971
-
972
- - Manage API: http://localhost:${config.manageApiPort}/docs
973
- - Run API: http://localhost:${config.runApiPort}/docs
974
-
975
- ## Learn More
976
-
977
- - [Inkeep Documentation](https://docs.inkeep.com)
978
-
979
- ## Troubleshooting
980
-
981
- ## Inkeep CLI commands
982
-
983
- - Ensure you are runnning commands from \`cd src/${config.projectId}\`.
984
- - Validate the \`inkeep.config.ts\` file has the correct api urls.
985
- - Validate that the \`.env\` file in \`src/${config.projectId}\` has the correct \`DB_FILE_NAME\`.
986
-
987
- ### Services won't start
988
-
989
- 1. Ensure all dependencies are installed: \`pnpm install\`
990
- 2. Check that ports 3000-3003 are available
991
-
992
- ### Agents won't respond
326
+ async function createInkeepConfig(config) {
327
+ const inkeepConfig = `import { defineConfig } from '@inkeep/agents-cli/config';
993
328
 
994
- 1. Ensure that the Agents Run API is running and includes a valid Anthropic or OpenAI API key in its .env file
995
- `;
996
- await fs.writeFile('README.md', readme);
329
+ const config = defineConfig({
330
+ tenantId: "${config.tenantId}",
331
+ projectId: "${config.projectId}",
332
+ agentsManageApiUrl: 'http://localhost:3002',
333
+ agentsRunApiUrl: 'http://localhost:3003',
334
+ modelSettings: ${JSON.stringify(config.modelSettings, null, 2)},
335
+ });
336
+
337
+ export default config;`;
338
+ await fs.writeFile(`src/${config.projectId}/inkeep.config.ts`, inkeepConfig);
339
+ if (config.customProject) {
340
+ const customIndexContent = `import { project } from '@inkeep/agents-sdk';
341
+
342
+ export const myProject = project({
343
+ id: "${config.projectId}",
344
+ name: "${config.projectId}",
345
+ description: "",
346
+ graphs: () => [],
347
+ });`;
348
+ await fs.writeFile(`src/${config.projectId}/index.ts`, customIndexContent);
349
+ }
997
350
  }
998
351
  async function installDependencies() {
999
352
  await execAsync('pnpm install');
1000
353
  }
1001
- async function setupProjectInDatabase() {
1002
- const s = p.spinner();
1003
- s.start('🚀 Starting development servers and setting up database...');
354
+ async function setupProjectInDatabase(config) {
355
+ // Start development servers in background
356
+ const { spawn } = await import('child_process');
357
+ const devProcess = spawn('pnpm', ['dev:apis'], {
358
+ stdio: ['pipe', 'pipe', 'pipe'],
359
+ detached: true, // Detach so we can kill the process group
360
+ cwd: process.cwd(),
361
+ });
362
+ // Give servers time to start
363
+ await new Promise((resolve) => setTimeout(resolve, 5000));
364
+ // Run inkeep push
1004
365
  try {
1005
- // Start development servers in background
1006
- const { spawn } = await import('child_process');
1007
- const devProcess = spawn('pnpm', ['dev'], {
1008
- stdio: ['pipe', 'pipe', 'pipe'],
1009
- detached: true, // Detach so we can kill the process group
1010
- cwd: process.cwd()
1011
- });
1012
- // Give servers time to start
1013
- await new Promise(resolve => setTimeout(resolve, 5000));
1014
- s.message('📦 Servers ready! Creating project in database...');
1015
- // Run the database setup
1016
- await execAsync('node scripts/setup.js');
366
+ // Suppress all output
367
+ const { stdout, stderr } = await execAsync(`pnpm inkeep push --project src/${config.projectId}`);
368
+ }
369
+ catch (error) {
370
+ //Continue despite error - user can setup project manually
371
+ }
372
+ finally {
1017
373
  // Kill the dev servers and their child processes
1018
374
  if (devProcess.pid) {
1019
375
  try {
1020
376
  // Kill the entire process group
1021
377
  process.kill(-devProcess.pid, 'SIGTERM');
1022
378
  // Wait a moment for graceful shutdown
1023
- await new Promise(resolve => setTimeout(resolve, 1000));
379
+ await new Promise((resolve) => setTimeout(resolve, 1000));
1024
380
  // Force kill if still running
1025
381
  try {
1026
382
  process.kill(-devProcess.pid, 'SIGKILL');
@@ -1034,18 +390,13 @@ async function setupProjectInDatabase() {
1034
390
  console.log('Note: Dev servers may still be running in background');
1035
391
  }
1036
392
  }
1037
- s.stop('✅ Project successfully created and configured in database!');
1038
- }
1039
- catch (error) {
1040
- s.stop('❌ Failed to setup project in database');
1041
- console.error('Setup error:', error);
1042
- // Continue anyway - user can run setup manually
1043
393
  }
1044
394
  }
1045
395
  async function setupDatabase() {
1046
396
  try {
1047
397
  // Run drizzle-kit push to create database file and apply schema
1048
398
  await execAsync('pnpm db:push');
399
+ await new Promise((resolve) => setTimeout(resolve, 1000));
1049
400
  }
1050
401
  catch (error) {
1051
402
  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-20250915202938",
3
+ "version": "0.0.0-dev-20250916184039",
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",