@inkeep/create-agents 0.1.10 → 0.2.1
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/README.md +3 -3
- package/dist/__tests__/utils.test.d.ts +1 -0
- package/dist/__tests__/utils.test.js +237 -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 +132 -781
- package/package.json +3 -1
package/README.md
CHANGED
|
@@ -96,10 +96,10 @@ my-agent-directory/
|
|
|
96
96
|
inkeep dev
|
|
97
97
|
```
|
|
98
98
|
|
|
99
|
-
4. **Deploy your
|
|
99
|
+
4. **Deploy your project:**
|
|
100
100
|
```bash
|
|
101
101
|
cd src/<project-id>/
|
|
102
|
-
pnpm inkeep push
|
|
102
|
+
pnpm inkeep push
|
|
103
103
|
```
|
|
104
104
|
|
|
105
105
|
5. **Test your agents:**
|
|
@@ -120,7 +120,7 @@ After setup, you'll have access to:
|
|
|
120
120
|
- `pnpm dev` - Start both API services with hot reload
|
|
121
121
|
- `pnpm db:push` - Apply database schema changes
|
|
122
122
|
- `inkeep dev` - Start the Manage UI
|
|
123
|
-
- `inkeep push
|
|
123
|
+
- `inkeep push` - Deploy project configurations
|
|
124
124
|
- `inkeep chat` - Interactive chat with your agents
|
|
125
125
|
|
|
126
126
|
## Environment Variables
|
|
@@ -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('--
|
|
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>;
|