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