@inkeep/create-agents 0.2.0 → 0.2.2
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.d.ts +1 -0
- package/dist/__tests__/utils.test.js +242 -0
- package/dist/index.js +4 -2
- package/dist/templates.d.ts +2 -0
- package/dist/templates.js +22 -0
- package/dist/utils.d.ts +5 -2
- package/dist/utils.js +142 -822
- package/package.json +3 -1
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,242 @@
|
|
|
1
|
+
import * as p from '@clack/prompts';
|
|
2
|
+
import fs from 'fs-extra';
|
|
3
|
+
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
|
4
|
+
import { cloneTemplate, getAvailableTemplates } from '../templates';
|
|
5
|
+
import { createAgents } from '../utils';
|
|
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([
|
|
52
|
+
'weather-graph',
|
|
53
|
+
'chatbot',
|
|
54
|
+
'data-analysis',
|
|
55
|
+
]);
|
|
56
|
+
vi.mocked(cloneTemplate).mockResolvedValue(undefined);
|
|
57
|
+
// Mock util.promisify to return a mock exec function
|
|
58
|
+
const mockExecAsync = vi.fn().mockResolvedValue({ stdout: '', stderr: '' });
|
|
59
|
+
const util = require('util');
|
|
60
|
+
util.promisify = vi.fn(() => mockExecAsync);
|
|
61
|
+
// Mock child_process.spawn
|
|
62
|
+
const childProcess = require('child_process');
|
|
63
|
+
childProcess.spawn = vi.fn(() => ({
|
|
64
|
+
pid: 12345,
|
|
65
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
66
|
+
on: vi.fn(),
|
|
67
|
+
kill: vi.fn(),
|
|
68
|
+
}));
|
|
69
|
+
});
|
|
70
|
+
afterEach(() => {
|
|
71
|
+
processExitSpy.mockRestore();
|
|
72
|
+
processChdirSpy.mockRestore();
|
|
73
|
+
});
|
|
74
|
+
describe('Default behavior (no template or customProjectId)', () => {
|
|
75
|
+
it('should use weather-graph as default template and project ID', async () => {
|
|
76
|
+
await createAgents({
|
|
77
|
+
dirName: 'test-dir',
|
|
78
|
+
openAiKey: 'test-openai-key',
|
|
79
|
+
anthropicKey: 'test-anthropic-key',
|
|
80
|
+
});
|
|
81
|
+
// Should clone base template and weather-graph template
|
|
82
|
+
expect(cloneTemplate).toHaveBeenCalledTimes(2);
|
|
83
|
+
expect(cloneTemplate).toHaveBeenCalledWith('https://github.com/inkeep/create-agents-template', expect.any(String));
|
|
84
|
+
expect(cloneTemplate).toHaveBeenCalledWith('https://github.com/inkeep/agents-cookbook/templates/weather-graph', 'src/weather-graph');
|
|
85
|
+
// Should not call getAvailableTemplates since no template validation needed
|
|
86
|
+
expect(getAvailableTemplates).not.toHaveBeenCalled();
|
|
87
|
+
});
|
|
88
|
+
it('should create project with weather-graph as project ID', async () => {
|
|
89
|
+
await createAgents({
|
|
90
|
+
dirName: 'test-dir',
|
|
91
|
+
openAiKey: 'test-openai-key',
|
|
92
|
+
anthropicKey: 'test-anthropic-key',
|
|
93
|
+
});
|
|
94
|
+
// Check that inkeep.config.ts is created with correct project ID
|
|
95
|
+
expect(fs.writeFile).toHaveBeenCalledWith('src/weather-graph/inkeep.config.ts', expect.stringContaining('projectId: "weather-graph"'));
|
|
96
|
+
});
|
|
97
|
+
});
|
|
98
|
+
describe('Template provided', () => {
|
|
99
|
+
it('should use template name as project ID when template is provided', async () => {
|
|
100
|
+
await createAgents({
|
|
101
|
+
dirName: 'test-dir',
|
|
102
|
+
template: 'chatbot',
|
|
103
|
+
openAiKey: 'test-openai-key',
|
|
104
|
+
anthropicKey: 'test-anthropic-key',
|
|
105
|
+
});
|
|
106
|
+
// Should validate template exists
|
|
107
|
+
expect(getAvailableTemplates).toHaveBeenCalled();
|
|
108
|
+
// Should clone base template and the specified template
|
|
109
|
+
expect(cloneTemplate).toHaveBeenCalledTimes(2);
|
|
110
|
+
expect(cloneTemplate).toHaveBeenCalledWith('https://github.com/inkeep/create-agents-template', expect.any(String));
|
|
111
|
+
expect(cloneTemplate).toHaveBeenCalledWith('https://github.com/inkeep/agents-cookbook/templates/chatbot', 'src/chatbot');
|
|
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([
|
|
126
|
+
'weather-graph',
|
|
127
|
+
'chatbot',
|
|
128
|
+
'data-analysis',
|
|
129
|
+
]);
|
|
130
|
+
await expect(createAgents({
|
|
131
|
+
dirName: 'test-dir',
|
|
132
|
+
template: 'invalid',
|
|
133
|
+
openAiKey: 'test-openai-key',
|
|
134
|
+
})).rejects.toThrow('process.exit called');
|
|
135
|
+
const cancelCall = vi.mocked(p.cancel).mock.calls[0][0];
|
|
136
|
+
expect(cancelCall).toContain('weather-graph');
|
|
137
|
+
expect(cancelCall).toContain('chatbot');
|
|
138
|
+
expect(cancelCall).toContain('data-analysis');
|
|
139
|
+
});
|
|
140
|
+
});
|
|
141
|
+
describe('Custom Project ID provided', () => {
|
|
142
|
+
it('should use custom project ID and not clone any template', async () => {
|
|
143
|
+
await createAgents({
|
|
144
|
+
dirName: 'test-dir',
|
|
145
|
+
customProjectId: 'my-custom-project',
|
|
146
|
+
openAiKey: 'test-openai-key',
|
|
147
|
+
anthropicKey: 'test-anthropic-key',
|
|
148
|
+
});
|
|
149
|
+
// Should clone base template but NOT project template
|
|
150
|
+
expect(cloneTemplate).toHaveBeenCalledTimes(1);
|
|
151
|
+
expect(cloneTemplate).toHaveBeenCalledWith('https://github.com/inkeep/create-agents-template', expect.any(String));
|
|
152
|
+
// Should NOT validate templates
|
|
153
|
+
expect(getAvailableTemplates).not.toHaveBeenCalled();
|
|
154
|
+
// Should create empty project directory
|
|
155
|
+
expect(fs.ensureDir).toHaveBeenCalledWith('src/my-custom-project');
|
|
156
|
+
expect(fs.writeFile).toHaveBeenCalledWith('src/my-custom-project/inkeep.config.ts', expect.stringContaining('projectId: "my-custom-project"'));
|
|
157
|
+
});
|
|
158
|
+
it('should prioritize custom project ID over template if both are provided', async () => {
|
|
159
|
+
await createAgents({
|
|
160
|
+
dirName: 'test-dir',
|
|
161
|
+
template: 'chatbot',
|
|
162
|
+
customProjectId: 'my-custom-project',
|
|
163
|
+
openAiKey: 'test-openai-key',
|
|
164
|
+
anthropicKey: 'test-anthropic-key',
|
|
165
|
+
});
|
|
166
|
+
// Should only clone base template, not project template
|
|
167
|
+
expect(cloneTemplate).toHaveBeenCalledTimes(1);
|
|
168
|
+
expect(cloneTemplate).toHaveBeenCalledWith('https://github.com/inkeep/create-agents-template', expect.any(String));
|
|
169
|
+
expect(getAvailableTemplates).not.toHaveBeenCalled();
|
|
170
|
+
expect(fs.ensureDir).toHaveBeenCalledWith('src/my-custom-project');
|
|
171
|
+
// Config should use custom project ID
|
|
172
|
+
expect(fs.writeFile).toHaveBeenCalledWith('src/my-custom-project/inkeep.config.ts', expect.stringContaining('projectId: "my-custom-project"'));
|
|
173
|
+
});
|
|
174
|
+
});
|
|
175
|
+
describe('Edge cases and validation', () => {
|
|
176
|
+
it('should handle template names with hyphens correctly', async () => {
|
|
177
|
+
vi.mocked(getAvailableTemplates).mockResolvedValue([
|
|
178
|
+
'my-complex-template',
|
|
179
|
+
'another-template',
|
|
180
|
+
]);
|
|
181
|
+
await createAgents({
|
|
182
|
+
dirName: 'test-dir',
|
|
183
|
+
template: 'my-complex-template',
|
|
184
|
+
openAiKey: 'test-key',
|
|
185
|
+
anthropicKey: 'test-key',
|
|
186
|
+
});
|
|
187
|
+
expect(cloneTemplate).toHaveBeenCalledTimes(2);
|
|
188
|
+
expect(cloneTemplate).toHaveBeenCalledWith('https://github.com/inkeep/agents-cookbook/templates/my-complex-template', 'src/my-complex-template');
|
|
189
|
+
});
|
|
190
|
+
it('should handle custom project IDs with special characters', async () => {
|
|
191
|
+
await createAgents({
|
|
192
|
+
dirName: 'test-dir',
|
|
193
|
+
customProjectId: 'my_project-123',
|
|
194
|
+
openAiKey: 'test-key',
|
|
195
|
+
anthropicKey: 'test-key',
|
|
196
|
+
});
|
|
197
|
+
expect(fs.ensureDir).toHaveBeenCalledWith('src/my_project-123');
|
|
198
|
+
expect(fs.writeFile).toHaveBeenCalledWith('src/my_project-123/inkeep.config.ts', expect.stringContaining('projectId: "my_project-123"'));
|
|
199
|
+
});
|
|
200
|
+
it('should create correct folder structure for all scenarios', async () => {
|
|
201
|
+
// Test default
|
|
202
|
+
await createAgents({
|
|
203
|
+
dirName: 'dir1',
|
|
204
|
+
openAiKey: 'key',
|
|
205
|
+
anthropicKey: 'key',
|
|
206
|
+
});
|
|
207
|
+
expect(fs.ensureDir).toHaveBeenCalledWith('src');
|
|
208
|
+
// Reset mocks
|
|
209
|
+
vi.clearAllMocks();
|
|
210
|
+
setupDefaultMocks();
|
|
211
|
+
// Test with template
|
|
212
|
+
await createAgents({
|
|
213
|
+
dirName: 'dir2',
|
|
214
|
+
template: 'chatbot',
|
|
215
|
+
openAiKey: 'key',
|
|
216
|
+
anthropicKey: 'key',
|
|
217
|
+
});
|
|
218
|
+
expect(fs.ensureDir).toHaveBeenCalledWith('src');
|
|
219
|
+
// Reset mocks
|
|
220
|
+
vi.clearAllMocks();
|
|
221
|
+
setupDefaultMocks();
|
|
222
|
+
// Test with custom ID
|
|
223
|
+
await createAgents({
|
|
224
|
+
dirName: 'dir3',
|
|
225
|
+
customProjectId: 'custom',
|
|
226
|
+
openAiKey: 'key',
|
|
227
|
+
anthropicKey: 'key',
|
|
228
|
+
});
|
|
229
|
+
expect(fs.ensureDir).toHaveBeenCalledWith('src');
|
|
230
|
+
expect(fs.ensureDir).toHaveBeenCalledWith('src/custom');
|
|
231
|
+
});
|
|
232
|
+
});
|
|
233
|
+
});
|
|
234
|
+
// Helper to setup default mocks
|
|
235
|
+
function setupDefaultMocks() {
|
|
236
|
+
vi.mocked(p.spinner).mockReturnValue(mockSpinner);
|
|
237
|
+
vi.mocked(fs.pathExists).mockResolvedValue(false);
|
|
238
|
+
vi.mocked(fs.ensureDir).mockResolvedValue(undefined);
|
|
239
|
+
vi.mocked(fs.writeFile).mockResolvedValue(undefined);
|
|
240
|
+
vi.mocked(getAvailableTemplates).mockResolvedValue(['weather-graph', 'chatbot', 'data-analysis']);
|
|
241
|
+
vi.mocked(cloneTemplate).mockResolvedValue(undefined);
|
|
242
|
+
}
|
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('--
|
|
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
|
-
|
|
22
|
+
customProjectId: options.customProjectId,
|
|
23
|
+
template: options.template,
|
|
22
24
|
});
|
|
23
25
|
}
|
|
24
26
|
catch (error) {
|
|
@@ -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
|
|
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>;
|