@inkeep/create-agents 0.2.1 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/__tests__/utils.test.js +43 -34
- package/dist/templates.js +3 -5
- package/dist/utils.js +21 -45
- package/package.json +1 -1
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import
|
|
1
|
+
import * as p from '@clack/prompts';
|
|
2
2
|
import fs from 'fs-extra';
|
|
3
|
+
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
|
4
|
+
import { cloneTemplate, getAvailableTemplates } from '../templates';
|
|
3
5
|
import { createAgents } from '../utils';
|
|
4
|
-
import { getAvailableTemplates, cloneTemplate } from '../templates';
|
|
5
|
-
import * as p from '@clack/prompts';
|
|
6
6
|
// Mock all dependencies
|
|
7
7
|
vi.mock('fs-extra');
|
|
8
8
|
vi.mock('../templates');
|
|
@@ -48,7 +48,11 @@ describe('createAgents - Template and Project ID Logic', () => {
|
|
|
48
48
|
vi.mocked(fs.mkdir).mockResolvedValue(undefined);
|
|
49
49
|
vi.mocked(fs.remove).mockResolvedValue(undefined);
|
|
50
50
|
// Mock templates
|
|
51
|
-
vi.mocked(getAvailableTemplates).mockResolvedValue([
|
|
51
|
+
vi.mocked(getAvailableTemplates).mockResolvedValue([
|
|
52
|
+
'weather-project',
|
|
53
|
+
'chatbot',
|
|
54
|
+
'data-analysis',
|
|
55
|
+
]);
|
|
52
56
|
vi.mocked(cloneTemplate).mockResolvedValue(undefined);
|
|
53
57
|
// Mock util.promisify to return a mock exec function
|
|
54
58
|
const mockExecAsync = vi.fn().mockResolvedValue({ stdout: '', stderr: '' });
|
|
@@ -68,29 +72,27 @@ describe('createAgents - Template and Project ID Logic', () => {
|
|
|
68
72
|
processChdirSpy.mockRestore();
|
|
69
73
|
});
|
|
70
74
|
describe('Default behavior (no template or customProjectId)', () => {
|
|
71
|
-
it('should use weather-
|
|
75
|
+
it('should use weather-project as default template and project ID', async () => {
|
|
72
76
|
await createAgents({
|
|
73
77
|
dirName: 'test-dir',
|
|
74
78
|
openAiKey: 'test-openai-key',
|
|
75
|
-
anthropicKey: 'test-anthropic-key'
|
|
79
|
+
anthropicKey: 'test-anthropic-key',
|
|
76
80
|
});
|
|
77
|
-
// Should clone base template and weather-
|
|
81
|
+
// Should clone base template and weather-project template
|
|
78
82
|
expect(cloneTemplate).toHaveBeenCalledTimes(2);
|
|
79
83
|
expect(cloneTemplate).toHaveBeenCalledWith('https://github.com/inkeep/create-agents-template', expect.any(String));
|
|
80
|
-
expect(cloneTemplate).toHaveBeenCalledWith('https://github.com/inkeep/agents-cookbook/
|
|
84
|
+
expect(cloneTemplate).toHaveBeenCalledWith('https://github.com/inkeep/agents-cookbook/template-projects/weather-project', 'src/weather-project');
|
|
81
85
|
// Should not call getAvailableTemplates since no template validation needed
|
|
82
86
|
expect(getAvailableTemplates).not.toHaveBeenCalled();
|
|
83
87
|
});
|
|
84
|
-
it('should create project with weather-
|
|
88
|
+
it('should create project with weather-project as project ID', async () => {
|
|
85
89
|
await createAgents({
|
|
86
90
|
dirName: 'test-dir',
|
|
87
91
|
openAiKey: 'test-openai-key',
|
|
88
|
-
anthropicKey: 'test-anthropic-key'
|
|
92
|
+
anthropicKey: 'test-anthropic-key',
|
|
89
93
|
});
|
|
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
94
|
// Check that inkeep.config.ts is created with correct project ID
|
|
93
|
-
expect(fs.writeFile).toHaveBeenCalledWith('src/weather-
|
|
95
|
+
expect(fs.writeFile).toHaveBeenCalledWith('src/weather-project/inkeep.config.ts', expect.stringContaining('projectId: "weather-project"'));
|
|
94
96
|
});
|
|
95
97
|
});
|
|
96
98
|
describe('Template provided', () => {
|
|
@@ -99,37 +101,39 @@ describe('createAgents - Template and Project ID Logic', () => {
|
|
|
99
101
|
dirName: 'test-dir',
|
|
100
102
|
template: 'chatbot',
|
|
101
103
|
openAiKey: 'test-openai-key',
|
|
102
|
-
anthropicKey: 'test-anthropic-key'
|
|
104
|
+
anthropicKey: 'test-anthropic-key',
|
|
103
105
|
});
|
|
104
106
|
// Should validate template exists
|
|
105
107
|
expect(getAvailableTemplates).toHaveBeenCalled();
|
|
106
108
|
// Should clone base template and the specified template
|
|
107
109
|
expect(cloneTemplate).toHaveBeenCalledTimes(2);
|
|
108
110
|
expect(cloneTemplate).toHaveBeenCalledWith('https://github.com/inkeep/create-agents-template', expect.any(String));
|
|
109
|
-
expect(cloneTemplate).toHaveBeenCalledWith('https://github.com/inkeep/agents-cookbook/
|
|
110
|
-
// Should create config files with template name as project ID
|
|
111
|
-
expect(fs.writeFile).toHaveBeenCalledWith('src/chatbot/.env', expect.any(String));
|
|
111
|
+
expect(cloneTemplate).toHaveBeenCalledWith('https://github.com/inkeep/agents-cookbook/template-projects/chatbot', 'src/chatbot');
|
|
112
112
|
expect(fs.writeFile).toHaveBeenCalledWith('src/chatbot/inkeep.config.ts', expect.stringContaining('projectId: "chatbot"'));
|
|
113
113
|
});
|
|
114
114
|
it('should exit with error when template does not exist', async () => {
|
|
115
|
-
vi.mocked(getAvailableTemplates).mockResolvedValue(['weather-
|
|
115
|
+
vi.mocked(getAvailableTemplates).mockResolvedValue(['weather-project', 'chatbot']);
|
|
116
116
|
await expect(createAgents({
|
|
117
117
|
dirName: 'test-dir',
|
|
118
118
|
template: 'non-existent-template',
|
|
119
|
-
openAiKey: 'test-openai-key'
|
|
119
|
+
openAiKey: 'test-openai-key',
|
|
120
120
|
})).rejects.toThrow('process.exit called');
|
|
121
121
|
expect(p.cancel).toHaveBeenCalledWith(expect.stringContaining('Template "non-existent-template" not found'));
|
|
122
122
|
expect(processExitSpy).toHaveBeenCalledWith(0);
|
|
123
123
|
});
|
|
124
124
|
it('should show available templates when invalid template is provided', async () => {
|
|
125
|
-
vi.mocked(getAvailableTemplates).mockResolvedValue([
|
|
125
|
+
vi.mocked(getAvailableTemplates).mockResolvedValue([
|
|
126
|
+
'weather-project',
|
|
127
|
+
'chatbot',
|
|
128
|
+
'data-analysis',
|
|
129
|
+
]);
|
|
126
130
|
await expect(createAgents({
|
|
127
131
|
dirName: 'test-dir',
|
|
128
132
|
template: 'invalid',
|
|
129
|
-
openAiKey: 'test-openai-key'
|
|
133
|
+
openAiKey: 'test-openai-key',
|
|
130
134
|
})).rejects.toThrow('process.exit called');
|
|
131
135
|
const cancelCall = vi.mocked(p.cancel).mock.calls[0][0];
|
|
132
|
-
expect(cancelCall).toContain('weather-
|
|
136
|
+
expect(cancelCall).toContain('weather-project');
|
|
133
137
|
expect(cancelCall).toContain('chatbot');
|
|
134
138
|
expect(cancelCall).toContain('data-analysis');
|
|
135
139
|
});
|
|
@@ -140,7 +144,7 @@ describe('createAgents - Template and Project ID Logic', () => {
|
|
|
140
144
|
dirName: 'test-dir',
|
|
141
145
|
customProjectId: 'my-custom-project',
|
|
142
146
|
openAiKey: 'test-openai-key',
|
|
143
|
-
anthropicKey: 'test-anthropic-key'
|
|
147
|
+
anthropicKey: 'test-anthropic-key',
|
|
144
148
|
});
|
|
145
149
|
// Should clone base template but NOT project template
|
|
146
150
|
expect(cloneTemplate).toHaveBeenCalledTimes(1);
|
|
@@ -149,8 +153,6 @@ describe('createAgents - Template and Project ID Logic', () => {
|
|
|
149
153
|
expect(getAvailableTemplates).not.toHaveBeenCalled();
|
|
150
154
|
// Should create empty project directory
|
|
151
155
|
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
156
|
expect(fs.writeFile).toHaveBeenCalledWith('src/my-custom-project/inkeep.config.ts', expect.stringContaining('projectId: "my-custom-project"'));
|
|
155
157
|
});
|
|
156
158
|
it('should prioritize custom project ID over template if both are provided', async () => {
|
|
@@ -159,7 +161,7 @@ describe('createAgents - Template and Project ID Logic', () => {
|
|
|
159
161
|
template: 'chatbot',
|
|
160
162
|
customProjectId: 'my-custom-project',
|
|
161
163
|
openAiKey: 'test-openai-key',
|
|
162
|
-
anthropicKey: 'test-anthropic-key'
|
|
164
|
+
anthropicKey: 'test-anthropic-key',
|
|
163
165
|
});
|
|
164
166
|
// Should only clone base template, not project template
|
|
165
167
|
expect(cloneTemplate).toHaveBeenCalledTimes(1);
|
|
@@ -172,22 +174,25 @@ describe('createAgents - Template and Project ID Logic', () => {
|
|
|
172
174
|
});
|
|
173
175
|
describe('Edge cases and validation', () => {
|
|
174
176
|
it('should handle template names with hyphens correctly', async () => {
|
|
175
|
-
vi.mocked(getAvailableTemplates).mockResolvedValue([
|
|
177
|
+
vi.mocked(getAvailableTemplates).mockResolvedValue([
|
|
178
|
+
'my-complex-template',
|
|
179
|
+
'another-template',
|
|
180
|
+
]);
|
|
176
181
|
await createAgents({
|
|
177
182
|
dirName: 'test-dir',
|
|
178
183
|
template: 'my-complex-template',
|
|
179
184
|
openAiKey: 'test-key',
|
|
180
|
-
anthropicKey: 'test-key'
|
|
185
|
+
anthropicKey: 'test-key',
|
|
181
186
|
});
|
|
182
187
|
expect(cloneTemplate).toHaveBeenCalledTimes(2);
|
|
183
|
-
expect(cloneTemplate).toHaveBeenCalledWith('https://github.com/inkeep/agents-cookbook/
|
|
188
|
+
expect(cloneTemplate).toHaveBeenCalledWith('https://github.com/inkeep/agents-cookbook/template-projects/my-complex-template', 'src/my-complex-template');
|
|
184
189
|
});
|
|
185
190
|
it('should handle custom project IDs with special characters', async () => {
|
|
186
191
|
await createAgents({
|
|
187
192
|
dirName: 'test-dir',
|
|
188
193
|
customProjectId: 'my_project-123',
|
|
189
194
|
openAiKey: 'test-key',
|
|
190
|
-
anthropicKey: 'test-key'
|
|
195
|
+
anthropicKey: 'test-key',
|
|
191
196
|
});
|
|
192
197
|
expect(fs.ensureDir).toHaveBeenCalledWith('src/my_project-123');
|
|
193
198
|
expect(fs.writeFile).toHaveBeenCalledWith('src/my_project-123/inkeep.config.ts', expect.stringContaining('projectId: "my_project-123"'));
|
|
@@ -197,7 +202,7 @@ describe('createAgents - Template and Project ID Logic', () => {
|
|
|
197
202
|
await createAgents({
|
|
198
203
|
dirName: 'dir1',
|
|
199
204
|
openAiKey: 'key',
|
|
200
|
-
anthropicKey: 'key'
|
|
205
|
+
anthropicKey: 'key',
|
|
201
206
|
});
|
|
202
207
|
expect(fs.ensureDir).toHaveBeenCalledWith('src');
|
|
203
208
|
// Reset mocks
|
|
@@ -208,7 +213,7 @@ describe('createAgents - Template and Project ID Logic', () => {
|
|
|
208
213
|
dirName: 'dir2',
|
|
209
214
|
template: 'chatbot',
|
|
210
215
|
openAiKey: 'key',
|
|
211
|
-
anthropicKey: 'key'
|
|
216
|
+
anthropicKey: 'key',
|
|
212
217
|
});
|
|
213
218
|
expect(fs.ensureDir).toHaveBeenCalledWith('src');
|
|
214
219
|
// Reset mocks
|
|
@@ -219,7 +224,7 @@ describe('createAgents - Template and Project ID Logic', () => {
|
|
|
219
224
|
dirName: 'dir3',
|
|
220
225
|
customProjectId: 'custom',
|
|
221
226
|
openAiKey: 'key',
|
|
222
|
-
anthropicKey: 'key'
|
|
227
|
+
anthropicKey: 'key',
|
|
223
228
|
});
|
|
224
229
|
expect(fs.ensureDir).toHaveBeenCalledWith('src');
|
|
225
230
|
expect(fs.ensureDir).toHaveBeenCalledWith('src/custom');
|
|
@@ -232,6 +237,10 @@ function setupDefaultMocks() {
|
|
|
232
237
|
vi.mocked(fs.pathExists).mockResolvedValue(false);
|
|
233
238
|
vi.mocked(fs.ensureDir).mockResolvedValue(undefined);
|
|
234
239
|
vi.mocked(fs.writeFile).mockResolvedValue(undefined);
|
|
235
|
-
vi.mocked(getAvailableTemplates).mockResolvedValue([
|
|
240
|
+
vi.mocked(getAvailableTemplates).mockResolvedValue([
|
|
241
|
+
'weather-project',
|
|
242
|
+
'chatbot',
|
|
243
|
+
'data-analysis',
|
|
244
|
+
]);
|
|
236
245
|
vi.mocked(cloneTemplate).mockResolvedValue(undefined);
|
|
237
246
|
}
|
package/dist/templates.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import fs from 'fs-extra';
|
|
2
1
|
import degit from 'degit';
|
|
2
|
+
import fs from 'fs-extra';
|
|
3
3
|
//Duplicating function here so we dont have to add a dependency on the agents-cli package
|
|
4
4
|
export async function cloneTemplate(templatePath, targetPath) {
|
|
5
5
|
await fs.mkdir(targetPath, { recursive: true });
|
|
@@ -14,9 +14,7 @@ export async function cloneTemplate(templatePath, targetPath) {
|
|
|
14
14
|
}
|
|
15
15
|
export async function getAvailableTemplates() {
|
|
16
16
|
// Fetch the list of templates from your repo
|
|
17
|
-
const response = await fetch('https://api.github.com/repos/inkeep/agents-cookbook/contents/
|
|
17
|
+
const response = await fetch('https://api.github.com/repos/inkeep/agents-cookbook/contents/template-projects');
|
|
18
18
|
const contents = await response.json();
|
|
19
|
-
return contents
|
|
20
|
-
.filter((item) => item.type === 'dir')
|
|
21
|
-
.map((item) => item.name);
|
|
19
|
+
return contents.filter((item) => item.type === 'dir').map((item) => item.name);
|
|
22
20
|
}
|
package/dist/utils.js
CHANGED
|
@@ -66,8 +66,8 @@ export const createAgents = async (args = {}) => {
|
|
|
66
66
|
}
|
|
67
67
|
else {
|
|
68
68
|
// No template or custom project ID provided - use defaults
|
|
69
|
-
projectId = 'weather-
|
|
70
|
-
templateName = 'weather-
|
|
69
|
+
projectId = 'weather-project';
|
|
70
|
+
templateName = 'weather-project';
|
|
71
71
|
}
|
|
72
72
|
p.intro(color.inverse(' Create Agents Directory '));
|
|
73
73
|
// Prompt for directory name if not provided
|
|
@@ -93,11 +93,11 @@ export const createAgents = async (args = {}) => {
|
|
|
93
93
|
// If keys aren't provided via CLI args, prompt for provider selection and keys
|
|
94
94
|
if (!anthropicKey && !openAiKey) {
|
|
95
95
|
const providerChoice = await p.select({
|
|
96
|
-
message: 'Which AI provider
|
|
96
|
+
message: 'Which AI provider would you like to use?',
|
|
97
97
|
options: [
|
|
98
|
-
{ value: 'anthropic', label: 'Anthropic
|
|
99
|
-
{ value: 'openai', label: 'OpenAI
|
|
100
|
-
{ value: 'google', label: 'Google
|
|
98
|
+
{ value: 'anthropic', label: 'Anthropic' },
|
|
99
|
+
{ value: 'openai', label: 'OpenAI' },
|
|
100
|
+
{ value: 'google', label: 'Google' },
|
|
101
101
|
],
|
|
102
102
|
});
|
|
103
103
|
if (p.isCancel(providerChoice)) {
|
|
@@ -172,7 +172,7 @@ export const createAgents = async (args = {}) => {
|
|
|
172
172
|
try {
|
|
173
173
|
const agentsTemplateRepo = 'https://github.com/inkeep/create-agents-template';
|
|
174
174
|
const projectTemplateRepo = templateName
|
|
175
|
-
? `https://github.com/inkeep/agents-cookbook/
|
|
175
|
+
? `https://github.com/inkeep/agents-cookbook/template-projects/${templateName}`
|
|
176
176
|
: null;
|
|
177
177
|
const directoryPath = path.resolve(process.cwd(), dirName);
|
|
178
178
|
// Check if directory already exists
|
|
@@ -199,6 +199,7 @@ export const createAgents = async (args = {}) => {
|
|
|
199
199
|
projectId,
|
|
200
200
|
openAiKey,
|
|
201
201
|
anthropicKey,
|
|
202
|
+
googleKey,
|
|
202
203
|
manageApiPort: manageApiPort || '3002',
|
|
203
204
|
runApiPort: runApiPort || '3003',
|
|
204
205
|
modelSettings: defaultModelSettings,
|
|
@@ -223,9 +224,6 @@ export const createAgents = async (args = {}) => {
|
|
|
223
224
|
// create or overwrite inkeep.config.ts
|
|
224
225
|
s.message('Creating inkeep.config.ts...');
|
|
225
226
|
await createInkeepConfig(config);
|
|
226
|
-
// Create service files
|
|
227
|
-
s.message('Creating service files...');
|
|
228
|
-
await createServiceFiles(config);
|
|
229
227
|
// Install dependencies
|
|
230
228
|
s.message('Installing dependencies (this may take a while)...');
|
|
231
229
|
await installDependencies();
|
|
@@ -272,51 +270,29 @@ async function createEnvironmentFiles(config) {
|
|
|
272
270
|
ENVIRONMENT=development
|
|
273
271
|
|
|
274
272
|
# Database
|
|
275
|
-
DB_FILE_NAME=file
|
|
273
|
+
DB_FILE_NAME=file:${process.cwd()}/local.db
|
|
276
274
|
|
|
277
275
|
# AI Provider Keys
|
|
278
276
|
ANTHROPIC_API_KEY=${config.anthropicKey || 'your-anthropic-key-here'}
|
|
279
277
|
OPENAI_API_KEY=${config.openAiKey || 'your-openai-key-here'}
|
|
280
278
|
GOOGLE_GENERATIVE_AI_API_KEY=${config.googleKey || 'your-google-key-here'}
|
|
281
|
-
`;
|
|
282
|
-
await fs.writeFile('.env', envContent);
|
|
283
|
-
// Create .env.example
|
|
284
|
-
const envExample = envContent.replace(/=.+$/gm, '=');
|
|
285
|
-
await fs.writeFile('.env.example', envExample);
|
|
286
|
-
// Create .env files for each API service
|
|
287
|
-
const runApiEnvContent = `# Environment
|
|
288
|
-
ENVIRONMENT=development
|
|
289
279
|
|
|
290
|
-
#
|
|
291
|
-
|
|
280
|
+
# Inkeep API URLs
|
|
281
|
+
INKEEP_AGENTS_MANAGE_API_URL="http://localhost:3002"
|
|
282
|
+
INKEEP_AGENTS_RUN_API_URL="http://localhost:3003"
|
|
292
283
|
|
|
293
|
-
#
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
GOOGLE_GENERATIVE_AI_API_KEY=${config.googleKey || 'your-google-key-here'}
|
|
284
|
+
# SigNoz Configuration
|
|
285
|
+
SIGNOZ_URL=your-signoz-url-here
|
|
286
|
+
SIGNOZ_API_KEY=your-signoz-api-key-here
|
|
297
287
|
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
ENVIRONMENT=development
|
|
302
|
-
|
|
303
|
-
# Database (relative path from API directory)
|
|
304
|
-
DB_FILE_NAME=file:../../local.db
|
|
288
|
+
# OTEL Configuration
|
|
289
|
+
OTEL_EXPORTER_OTLP_TRACES_ENDPOINT=https://ingest.us.signoz.cloud:443/v1/traces
|
|
290
|
+
OTEL_EXPORTER_OTLP_TRACES_HEADERS="signoz-ingestion-key=<your-ingestion-key>"
|
|
305
291
|
|
|
306
|
-
|
|
292
|
+
# Nango Configuration
|
|
293
|
+
NANGO_SECRET_KEY=
|
|
307
294
|
`;
|
|
308
|
-
await fs.writeFile('
|
|
309
|
-
await fs.writeFile('apps/run-api/.env', runApiEnvContent);
|
|
310
|
-
}
|
|
311
|
-
async function createServiceFiles(config) {
|
|
312
|
-
// Create .env file for the project directory (for inkeep CLI commands)
|
|
313
|
-
const projectEnvContent = `# Environment
|
|
314
|
-
ENVIRONMENT=development
|
|
315
|
-
|
|
316
|
-
# Database (relative path from project directory)
|
|
317
|
-
DB_FILE_NAME=file:../../local.db
|
|
318
|
-
`;
|
|
319
|
-
await fs.writeFile(`src/${config.projectId}/.env`, projectEnvContent);
|
|
295
|
+
await fs.writeFile('.env', envContent);
|
|
320
296
|
}
|
|
321
297
|
async function createInkeepConfig(config) {
|
|
322
298
|
const inkeepConfig = `import { defineConfig } from '@inkeep/agents-cli/config';
|