@inkeep/create-agents 0.29.10 → 0.30.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__/e2e/quickstart.test.d.ts +1 -0
- package/dist/__tests__/e2e/quickstart.test.js +175 -0
- package/dist/__tests__/e2e/utils.d.ts +41 -0
- package/dist/__tests__/e2e/utils.js +217 -0
- package/dist/__tests__/utils.test.js +11 -9
- package/dist/index.js +4 -0
- package/dist/templates.d.ts +2 -2
- package/dist/templates.js +12 -5
- package/dist/utils.d.ts +2 -0
- package/dist/utils.js +84 -26
- package/package.json +10 -5
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
import { execa } from 'execa';
|
|
3
|
+
import { afterEach, beforeEach, describe, expect, it } from 'vitest';
|
|
4
|
+
import { cleanupDir, createTempDir, linkLocalPackages, runCommand, runCreateAgentsCLI, verifyDirectoryStructure, verifyFile, waitForServerReady, } from './utils';
|
|
5
|
+
const manageApiUrl = 'http://localhost:3002';
|
|
6
|
+
describe('create-agents quickstart e2e', () => {
|
|
7
|
+
let testDir;
|
|
8
|
+
let projectDir;
|
|
9
|
+
const workspaceName = 'test-project';
|
|
10
|
+
const projectId = 'activities-planner';
|
|
11
|
+
beforeEach(async () => {
|
|
12
|
+
// Create a temporary directory for each test
|
|
13
|
+
testDir = await createTempDir();
|
|
14
|
+
projectDir = path.join(testDir, workspaceName);
|
|
15
|
+
});
|
|
16
|
+
afterEach(async () => {
|
|
17
|
+
await cleanupDir(testDir);
|
|
18
|
+
});
|
|
19
|
+
it('should work e2e', async () => {
|
|
20
|
+
const monorepoRoot = path.join(__dirname, '../../../../../');
|
|
21
|
+
const createAgentsPrefix = path.join(monorepoRoot, 'create-agents-template');
|
|
22
|
+
const projectTemplatesPrefix = path.join(monorepoRoot, 'agents-cookbook/template-projects');
|
|
23
|
+
// Run the CLI with all options (non-interactive mode)
|
|
24
|
+
console.log('Running CLI with options:');
|
|
25
|
+
console.log(`Working directory: ${testDir}`);
|
|
26
|
+
const result = await runCreateAgentsCLI([
|
|
27
|
+
workspaceName,
|
|
28
|
+
'--openai-key',
|
|
29
|
+
'test-openai-key',
|
|
30
|
+
'--disable-git', // Skip git init for faster tests
|
|
31
|
+
'--local-agents-prefix',
|
|
32
|
+
createAgentsPrefix,
|
|
33
|
+
'--local-templates-prefix',
|
|
34
|
+
projectTemplatesPrefix,
|
|
35
|
+
], testDir);
|
|
36
|
+
// Verify the CLI completed successfully
|
|
37
|
+
expect(result.exitCode).toBe(0);
|
|
38
|
+
console.log('CLI completed successfully');
|
|
39
|
+
// Verify the core directory structure
|
|
40
|
+
console.log('Verifying directory structure...');
|
|
41
|
+
await verifyDirectoryStructure(projectDir, [
|
|
42
|
+
'src',
|
|
43
|
+
'src/inkeep.config.ts',
|
|
44
|
+
`src/projects/${projectId}`,
|
|
45
|
+
'apps/manage-api',
|
|
46
|
+
'apps/run-api',
|
|
47
|
+
'apps/mcp',
|
|
48
|
+
'apps/manage-ui',
|
|
49
|
+
'.env',
|
|
50
|
+
'package.json',
|
|
51
|
+
'drizzle.config.ts',
|
|
52
|
+
]);
|
|
53
|
+
console.log('Directory structure verified');
|
|
54
|
+
// Verify .env file has required variables
|
|
55
|
+
console.log('Verifying .env file...');
|
|
56
|
+
await verifyFile(path.join(projectDir, '.env'), [
|
|
57
|
+
/ENVIRONMENT=development/,
|
|
58
|
+
/OPENAI_API_KEY=test-openai-key/,
|
|
59
|
+
/DB_FILE_NAME=file:.*\/local\.db/,
|
|
60
|
+
/INKEEP_AGENTS_MANAGE_API_URL="http:\/\/localhost:3002"/,
|
|
61
|
+
/INKEEP_AGENTS_RUN_API_URL="http:\/\/localhost:3003"/,
|
|
62
|
+
/INKEEP_AGENTS_JWT_SIGNING_SECRET=\w+/, // Random secret should be generated
|
|
63
|
+
]);
|
|
64
|
+
console.log('.env file verified');
|
|
65
|
+
// Verify inkeep.config.ts was created
|
|
66
|
+
console.log('Verifying inkeep.config.ts...');
|
|
67
|
+
await verifyFile(path.join(projectDir, 'src/inkeep.config.ts'));
|
|
68
|
+
console.log('inkeep.config.ts verified');
|
|
69
|
+
console.log('Starting dev servers');
|
|
70
|
+
// Start dev servers in background with output monitoring
|
|
71
|
+
const devProcess = execa('pnpm', ['dev'], {
|
|
72
|
+
cwd: path.join(projectDir, 'apps/manage-api'),
|
|
73
|
+
env: {
|
|
74
|
+
...process.env,
|
|
75
|
+
FORCE_COLOR: '0',
|
|
76
|
+
NODE_ENV: 'test',
|
|
77
|
+
},
|
|
78
|
+
cleanup: true,
|
|
79
|
+
detached: false,
|
|
80
|
+
stderr: 'pipe',
|
|
81
|
+
});
|
|
82
|
+
// Monitor output for errors and readiness signals
|
|
83
|
+
let serverOutput = '';
|
|
84
|
+
const outputHandler = (data) => {
|
|
85
|
+
const text = data.toString();
|
|
86
|
+
serverOutput += text;
|
|
87
|
+
// Log important messages in CI
|
|
88
|
+
if (process.env.CI) {
|
|
89
|
+
if (text.includes('Error') || text.includes('EADDRINUSE') || text.includes('ready')) {
|
|
90
|
+
console.log('[Server]:', text.trim());
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
};
|
|
94
|
+
if (devProcess.stderr)
|
|
95
|
+
devProcess.stderr.on('data', outputHandler);
|
|
96
|
+
// Handle process crashes during startup
|
|
97
|
+
devProcess.catch((error) => {
|
|
98
|
+
console.error('Dev process crashed during startup:', error.message);
|
|
99
|
+
console.error('Server output:', serverOutput);
|
|
100
|
+
});
|
|
101
|
+
console.log('Waiting for servers to be ready');
|
|
102
|
+
try {
|
|
103
|
+
// Wait for servers to be ready with retries
|
|
104
|
+
await waitForServerReady(`${manageApiUrl}/health`, 120000); // Increased to 2 minutes for CI
|
|
105
|
+
console.log('Manage API is ready');
|
|
106
|
+
console.log('Pushing project');
|
|
107
|
+
const pushResult = await runCommand('pnpm', [
|
|
108
|
+
'inkeep',
|
|
109
|
+
'push',
|
|
110
|
+
'--project',
|
|
111
|
+
`src/projects/${projectId}`,
|
|
112
|
+
'--config',
|
|
113
|
+
'src/inkeep.config.ts',
|
|
114
|
+
], projectDir, 30000);
|
|
115
|
+
expect(pushResult.exitCode).toBe(0);
|
|
116
|
+
console.log('Testing API requests');
|
|
117
|
+
// Test API requests
|
|
118
|
+
const response = await fetch(`${manageApiUrl}/tenants/default/projects/${projectId}`);
|
|
119
|
+
const data = await response.json();
|
|
120
|
+
expect(data.data.tenantId).toBe('default');
|
|
121
|
+
expect(data.data.id).toBe(projectId);
|
|
122
|
+
// Link to local monorepo packages
|
|
123
|
+
await linkLocalPackages(projectDir, monorepoRoot);
|
|
124
|
+
const pushResultLocal = await runCommand('pnpm', [
|
|
125
|
+
'inkeep',
|
|
126
|
+
'push',
|
|
127
|
+
'--project',
|
|
128
|
+
`src/projects/${projectId}`,
|
|
129
|
+
'--config',
|
|
130
|
+
'src/inkeep.config.ts',
|
|
131
|
+
], projectDir, 30000);
|
|
132
|
+
expect(pushResultLocal.exitCode).toBe(0);
|
|
133
|
+
// Test that the project works with local packages
|
|
134
|
+
const responseLocal = await fetch(`${manageApiUrl}/tenants/default/projects/${projectId}`);
|
|
135
|
+
expect(responseLocal.status).toBe(200);
|
|
136
|
+
}
|
|
137
|
+
catch (error) {
|
|
138
|
+
console.error('Test failed with error:', error);
|
|
139
|
+
// Print server output for debugging
|
|
140
|
+
if (devProcess.stdout) {
|
|
141
|
+
const stdout = await devProcess.stdout;
|
|
142
|
+
console.log('Server stdout:', stdout);
|
|
143
|
+
}
|
|
144
|
+
if (devProcess.stderr) {
|
|
145
|
+
const stderr = await devProcess.stderr;
|
|
146
|
+
console.error('Server stderr:', stderr);
|
|
147
|
+
}
|
|
148
|
+
throw error;
|
|
149
|
+
}
|
|
150
|
+
finally {
|
|
151
|
+
console.log('Killing dev process');
|
|
152
|
+
// Kill the process and wait for it to die
|
|
153
|
+
try {
|
|
154
|
+
devProcess.kill('SIGTERM');
|
|
155
|
+
}
|
|
156
|
+
catch {
|
|
157
|
+
// Might already be dead
|
|
158
|
+
}
|
|
159
|
+
// Give it 2 seconds to shut down gracefully, then force kill
|
|
160
|
+
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
161
|
+
try {
|
|
162
|
+
devProcess.kill('SIGKILL');
|
|
163
|
+
}
|
|
164
|
+
catch {
|
|
165
|
+
// Already dead or couldn't kill
|
|
166
|
+
}
|
|
167
|
+
// Wait for the process to be fully cleaned up (with timeout)
|
|
168
|
+
await Promise.race([
|
|
169
|
+
devProcess.catch(() => { }), // Wait for process to exit
|
|
170
|
+
new Promise((resolve) => setTimeout(resolve, 5000)), // Or timeout after 5s
|
|
171
|
+
]);
|
|
172
|
+
console.log('Dev process cleanup complete');
|
|
173
|
+
}
|
|
174
|
+
}, 720000); // 12 minute timeout for full flow with network calls (CI can be slow)
|
|
175
|
+
});
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Run the create-agents CLI with the given arguments
|
|
3
|
+
*/
|
|
4
|
+
export declare function runCreateAgentsCLI(args: string[], cwd: string, timeout?: number): Promise<{
|
|
5
|
+
stdout: string;
|
|
6
|
+
stderr: string;
|
|
7
|
+
exitCode: number | undefined;
|
|
8
|
+
}>;
|
|
9
|
+
/**
|
|
10
|
+
* Run a command in the created project directory
|
|
11
|
+
*/
|
|
12
|
+
export declare function runCommand(command: string, args: string[], cwd: string, timeout?: number): Promise<{
|
|
13
|
+
stdout: string;
|
|
14
|
+
stderr: string;
|
|
15
|
+
exitCode: number | undefined;
|
|
16
|
+
}>;
|
|
17
|
+
/**
|
|
18
|
+
* Create a temporary directory for testing
|
|
19
|
+
*/
|
|
20
|
+
export declare function createTempDir(prefix?: string): Promise<string>;
|
|
21
|
+
/**
|
|
22
|
+
* Clean up a test directory with retries
|
|
23
|
+
*/
|
|
24
|
+
export declare function cleanupDir(dir: string): Promise<void>;
|
|
25
|
+
/**
|
|
26
|
+
* Verify that a file exists and optionally check its contents
|
|
27
|
+
*/
|
|
28
|
+
export declare function verifyFile(filePath: string, expectedContents?: string[] | RegExp[]): Promise<void>;
|
|
29
|
+
/**
|
|
30
|
+
* Verify that a directory has the expected structure
|
|
31
|
+
*/
|
|
32
|
+
export declare function verifyDirectoryStructure(baseDir: string, expectedPaths: string[]): Promise<void>;
|
|
33
|
+
/**
|
|
34
|
+
* Link local monorepo packages to the created project
|
|
35
|
+
* This replaces published @inkeep packages with local versions for testing
|
|
36
|
+
*/
|
|
37
|
+
export declare function linkLocalPackages(projectDir: string, monorepoRoot: string): Promise<void>;
|
|
38
|
+
/**
|
|
39
|
+
* Wait for a server to be ready by polling a health endpoint
|
|
40
|
+
*/
|
|
41
|
+
export declare function waitForServerReady(url: string, timeout: number): Promise<void>;
|
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
import os from 'node:os';
|
|
2
|
+
import path, { dirname } from 'node:path';
|
|
3
|
+
import { fileURLToPath } from 'node:url';
|
|
4
|
+
import { execa } from 'execa';
|
|
5
|
+
import fs from 'fs-extra';
|
|
6
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
7
|
+
/**
|
|
8
|
+
* Run the create-agents CLI with the given arguments
|
|
9
|
+
*/
|
|
10
|
+
export async function runCreateAgentsCLI(args, cwd, timeout = 300000 // 5 minutes default for full flow
|
|
11
|
+
) {
|
|
12
|
+
const cliPath = path.join(__dirname, '../../../src/index.ts');
|
|
13
|
+
try {
|
|
14
|
+
// Run using tsx to execute TypeScript directly
|
|
15
|
+
const result = await execa('tsx', [cliPath, ...args], {
|
|
16
|
+
cwd,
|
|
17
|
+
timeout,
|
|
18
|
+
env: { ...process.env, FORCE_COLOR: '0' }, // Disable colors for easier assertion
|
|
19
|
+
all: true, // Capture combined stdout + stderr in order
|
|
20
|
+
});
|
|
21
|
+
return {
|
|
22
|
+
stdout: result.stdout,
|
|
23
|
+
stderr: result.stderr,
|
|
24
|
+
exitCode: result.exitCode,
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
catch (error) {
|
|
28
|
+
// execa throws on non-zero exit codes, capture the error info
|
|
29
|
+
return {
|
|
30
|
+
stdout: error.stdout || '',
|
|
31
|
+
stderr: error.stderr || '',
|
|
32
|
+
exitCode: error.exitCode || 1,
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Run a command in the created project directory
|
|
38
|
+
*/
|
|
39
|
+
export async function runCommand(command, args, cwd, timeout = 120000 // 2 minutes default
|
|
40
|
+
) {
|
|
41
|
+
try {
|
|
42
|
+
const result = await execa(command, args, {
|
|
43
|
+
cwd,
|
|
44
|
+
timeout,
|
|
45
|
+
env: { ...process.env, FORCE_COLOR: '0' },
|
|
46
|
+
shell: true,
|
|
47
|
+
});
|
|
48
|
+
return {
|
|
49
|
+
stdout: result.stdout,
|
|
50
|
+
stderr: result.stderr,
|
|
51
|
+
exitCode: result.exitCode,
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
catch (error) {
|
|
55
|
+
return {
|
|
56
|
+
stdout: error.stdout || '',
|
|
57
|
+
stderr: error.stderr || '',
|
|
58
|
+
exitCode: error.exitCode || 1,
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Create a temporary directory for testing
|
|
64
|
+
*/
|
|
65
|
+
export async function createTempDir(prefix = 'create-agents-e2e-') {
|
|
66
|
+
return fs.mkdtemp(path.join(os.tmpdir(), prefix));
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Clean up a test directory with retries
|
|
70
|
+
*/
|
|
71
|
+
export async function cleanupDir(dir) {
|
|
72
|
+
if (!(await fs.pathExists(dir))) {
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
try {
|
|
76
|
+
// Try multiple times with delays (common in CI)
|
|
77
|
+
for (let i = 0; i < 3; i++) {
|
|
78
|
+
try {
|
|
79
|
+
await fs.remove(dir);
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
catch (error) {
|
|
83
|
+
if (i === 2)
|
|
84
|
+
throw error; // Last attempt, throw the error
|
|
85
|
+
await new Promise((resolve) => setTimeout(resolve, 1000)); // Wait 1s before retry
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
catch (error) {
|
|
90
|
+
// If still failing, try force removal
|
|
91
|
+
if (error.code === 'ENOTEMPTY' || error.code === 'EBUSY') {
|
|
92
|
+
await execa('rm', ['-rf', dir], { shell: true }).catch(() => {
|
|
93
|
+
console.warn(`Failed to clean up ${dir}`);
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* Verify that a file exists and optionally check its contents
|
|
100
|
+
*/
|
|
101
|
+
export async function verifyFile(filePath, expectedContents) {
|
|
102
|
+
const exists = await fs.pathExists(filePath);
|
|
103
|
+
if (!exists) {
|
|
104
|
+
throw new Error(`Expected file to exist: ${filePath}`);
|
|
105
|
+
}
|
|
106
|
+
if (expectedContents) {
|
|
107
|
+
const content = await fs.readFile(filePath, 'utf-8');
|
|
108
|
+
for (const expected of expectedContents) {
|
|
109
|
+
if (typeof expected === 'string') {
|
|
110
|
+
if (!content.includes(expected)) {
|
|
111
|
+
throw new Error(`Expected file ${filePath} to contain: ${expected}`);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
else {
|
|
115
|
+
if (!expected.test(content)) {
|
|
116
|
+
throw new Error(`Expected file ${filePath} to match pattern: ${expected}`);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
/**
|
|
123
|
+
* Verify that a directory has the expected structure
|
|
124
|
+
*/
|
|
125
|
+
export async function verifyDirectoryStructure(baseDir, expectedPaths) {
|
|
126
|
+
for (const expectedPath of expectedPaths) {
|
|
127
|
+
const fullPath = path.join(baseDir, expectedPath);
|
|
128
|
+
const exists = await fs.pathExists(fullPath);
|
|
129
|
+
if (!exists) {
|
|
130
|
+
throw new Error(`Expected path to exist: ${fullPath}`);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
/**
|
|
135
|
+
* Link local monorepo packages to the created project
|
|
136
|
+
* This replaces published @inkeep packages with local versions for testing
|
|
137
|
+
*/
|
|
138
|
+
export async function linkLocalPackages(projectDir, monorepoRoot) {
|
|
139
|
+
const packageJsonPaths = [
|
|
140
|
+
path.join(projectDir, 'package.json'),
|
|
141
|
+
path.join(projectDir, 'apps/manage-api/package.json'),
|
|
142
|
+
path.join(projectDir, 'apps/run-api/package.json'),
|
|
143
|
+
];
|
|
144
|
+
const packageJsons = {};
|
|
145
|
+
for (const packageJsonPath of packageJsonPaths) {
|
|
146
|
+
packageJsons[packageJsonPath] = await fs.readJson(packageJsonPath);
|
|
147
|
+
}
|
|
148
|
+
// Define local @inkeep packages to link
|
|
149
|
+
const inkeepPackages = {
|
|
150
|
+
'@inkeep/agents-sdk': `link:${path.join(monorepoRoot, 'packages/agents-sdk')}`,
|
|
151
|
+
'@inkeep/agents-core': `link:${path.join(monorepoRoot, 'packages/agents-core')}`,
|
|
152
|
+
'@inkeep/agents-manage-api': `link:${path.join(monorepoRoot, 'agents-manage-api')}`,
|
|
153
|
+
'@inkeep/agents-run-api': `link:${path.join(monorepoRoot, 'agents-run-api')}`,
|
|
154
|
+
'@inkeep/agents-cli': `link:${path.join(monorepoRoot, 'agents-cli')}`,
|
|
155
|
+
};
|
|
156
|
+
// Replace package versions with local links
|
|
157
|
+
for (const [pkg, linkPath] of Object.entries(inkeepPackages)) {
|
|
158
|
+
for (const packageJsonPath of packageJsonPaths) {
|
|
159
|
+
if (packageJsons[packageJsonPath].dependencies?.[pkg]) {
|
|
160
|
+
packageJsons[packageJsonPath].dependencies[pkg] = linkPath;
|
|
161
|
+
}
|
|
162
|
+
if (packageJsons[packageJsonPath].devDependencies?.[pkg]) {
|
|
163
|
+
packageJsons[packageJsonPath].devDependencies[pkg] = linkPath;
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
// Write updated package.json
|
|
168
|
+
for (const packageJsonPath of packageJsonPaths) {
|
|
169
|
+
await fs.writeJson(packageJsonPath, packageJsons[packageJsonPath], { spaces: 2 });
|
|
170
|
+
}
|
|
171
|
+
// Reinstall to create the symlinks
|
|
172
|
+
await execa('pnpm', ['install', '--no-frozen-lockfile'], {
|
|
173
|
+
cwd: projectDir,
|
|
174
|
+
env: { ...process.env, FORCE_COLOR: '0' },
|
|
175
|
+
});
|
|
176
|
+
}
|
|
177
|
+
/**
|
|
178
|
+
* Wait for a server to be ready by polling a health endpoint
|
|
179
|
+
*/
|
|
180
|
+
export async function waitForServerReady(url, timeout) {
|
|
181
|
+
const start = Date.now();
|
|
182
|
+
let lastError = null;
|
|
183
|
+
let attempts = 0;
|
|
184
|
+
console.log(`Waiting for server at ${url}...`);
|
|
185
|
+
while (Date.now() - start < timeout) {
|
|
186
|
+
attempts++;
|
|
187
|
+
try {
|
|
188
|
+
const response = await fetch(url, {
|
|
189
|
+
signal: AbortSignal.timeout(5000), // 5 second timeout per request
|
|
190
|
+
});
|
|
191
|
+
if (response.ok) {
|
|
192
|
+
console.log(`✓ Server ready at ${url} after ${attempts} attempts (${Date.now() - start}ms)`);
|
|
193
|
+
return;
|
|
194
|
+
}
|
|
195
|
+
lastError = new Error(`HTTP ${response.status}: ${response.statusText}`);
|
|
196
|
+
// Log status every 10 attempts in CI
|
|
197
|
+
if (process.env.CI && attempts % 10 === 0) {
|
|
198
|
+
console.log(`Still waiting for ${url}... (attempt ${attempts}, ${Math.floor((Date.now() - start) / 1000)}s elapsed)`);
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
catch (error) {
|
|
202
|
+
// Server not ready yet or connection refused
|
|
203
|
+
lastError = error instanceof Error ? error : new Error(String(error));
|
|
204
|
+
// Log connection errors periodically in CI
|
|
205
|
+
if (process.env.CI && attempts % 15 === 0) {
|
|
206
|
+
console.log(`Connection attempt ${attempts} failed: ${lastError.message}`);
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
// Wait before next attempt (exponential backoff up to 5s)
|
|
210
|
+
const waitTime = Math.min(1000 + attempts * 100, 5000);
|
|
211
|
+
await new Promise((resolve) => setTimeout(resolve, waitTime));
|
|
212
|
+
}
|
|
213
|
+
// Timeout reached - provide detailed error
|
|
214
|
+
const elapsed = Date.now() - start;
|
|
215
|
+
const errorDetails = lastError ? `: ${lastError.message}` : '';
|
|
216
|
+
throw new Error(`Server not ready at ${url} after ${elapsed}ms (${attempts} attempts)${errorDetails}`);
|
|
217
|
+
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import * as p from '@clack/prompts';
|
|
2
2
|
import fs from 'fs-extra';
|
|
3
3
|
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
|
4
|
-
import { cloneTemplate, getAvailableTemplates } from '../templates';
|
|
4
|
+
import { cloneTemplate, cloneTemplateLocal, getAvailableTemplates } from '../templates';
|
|
5
5
|
import { createAgents } from '../utils';
|
|
6
6
|
// Mock all dependencies
|
|
7
7
|
vi.mock('fs-extra');
|
|
@@ -55,6 +55,7 @@ describe('createAgents - Template and Project ID Logic', () => {
|
|
|
55
55
|
'data-analysis',
|
|
56
56
|
]);
|
|
57
57
|
vi.mocked(cloneTemplate).mockResolvedValue(undefined);
|
|
58
|
+
vi.mocked(cloneTemplateLocal).mockResolvedValue(undefined);
|
|
58
59
|
// Mock util.promisify to return a mock exec function
|
|
59
60
|
const mockExecAsync = vi.fn().mockResolvedValue({ stdout: '', stderr: '' });
|
|
60
61
|
const util = require('node:util');
|
|
@@ -79,10 +80,10 @@ describe('createAgents - Template and Project ID Logic', () => {
|
|
|
79
80
|
openAiKey: 'test-openai-key',
|
|
80
81
|
anthropicKey: 'test-anthropic-key',
|
|
81
82
|
});
|
|
82
|
-
// Should clone base template and
|
|
83
|
+
// Should clone base template and activities-planner template
|
|
83
84
|
expect(cloneTemplate).toHaveBeenCalledTimes(2);
|
|
84
|
-
expect(cloneTemplate).toHaveBeenCalledWith('https://github.com/inkeep/create-agents-template', expect.any(String));
|
|
85
|
-
expect(cloneTemplate).toHaveBeenCalledWith('https://github.com/inkeep/agents-cookbook/template-projects/activities-planner', 'src/projects/activities-planner', expect.arrayContaining([
|
|
85
|
+
expect(cloneTemplate).toHaveBeenCalledWith('https://github.com/inkeep/agents/create-agents-template', expect.any(String), undefined);
|
|
86
|
+
expect(cloneTemplate).toHaveBeenCalledWith('https://github.com/inkeep/agents/agents-cookbook/template-projects/activities-planner', 'src/projects/activities-planner', expect.arrayContaining([
|
|
86
87
|
expect.objectContaining({
|
|
87
88
|
filePath: 'index.ts',
|
|
88
89
|
replacements: expect.objectContaining({
|
|
@@ -117,8 +118,8 @@ describe('createAgents - Template and Project ID Logic', () => {
|
|
|
117
118
|
expect(getAvailableTemplates).toHaveBeenCalled();
|
|
118
119
|
// Should clone base template and the specified template
|
|
119
120
|
expect(cloneTemplate).toHaveBeenCalledTimes(2);
|
|
120
|
-
expect(cloneTemplate).toHaveBeenCalledWith('https://github.com/inkeep/create-agents-template', expect.any(String));
|
|
121
|
-
expect(cloneTemplate).toHaveBeenCalledWith('https://github.com/inkeep/agents-cookbook/template-projects/chatbot', 'src/projects/chatbot', expect.arrayContaining([
|
|
121
|
+
expect(cloneTemplate).toHaveBeenCalledWith('https://github.com/inkeep/agents/create-agents-template', expect.any(String), undefined);
|
|
122
|
+
expect(cloneTemplate).toHaveBeenCalledWith('https://github.com/inkeep/agents/agents-cookbook/template-projects/chatbot', 'src/projects/chatbot', expect.arrayContaining([
|
|
122
123
|
expect.objectContaining({
|
|
123
124
|
filePath: 'index.ts',
|
|
124
125
|
replacements: expect.objectContaining({
|
|
@@ -168,7 +169,7 @@ describe('createAgents - Template and Project ID Logic', () => {
|
|
|
168
169
|
});
|
|
169
170
|
// Should clone base template but NOT project template
|
|
170
171
|
expect(cloneTemplate).toHaveBeenCalledTimes(1);
|
|
171
|
-
expect(cloneTemplate).toHaveBeenCalledWith('https://github.com/inkeep/create-agents-template', expect.any(String));
|
|
172
|
+
expect(cloneTemplate).toHaveBeenCalledWith('https://github.com/inkeep/agents/create-agents-template', expect.any(String), undefined);
|
|
172
173
|
// Should NOT validate templates
|
|
173
174
|
expect(getAvailableTemplates).not.toHaveBeenCalled();
|
|
174
175
|
// Should create empty project directory
|
|
@@ -190,7 +191,7 @@ describe('createAgents - Template and Project ID Logic', () => {
|
|
|
190
191
|
});
|
|
191
192
|
// Should only clone base template, not project template
|
|
192
193
|
expect(cloneTemplate).toHaveBeenCalledTimes(1);
|
|
193
|
-
expect(cloneTemplate).toHaveBeenCalledWith('https://github.com/inkeep/create-agents-template', expect.any(String));
|
|
194
|
+
expect(cloneTemplate).toHaveBeenCalledWith('https://github.com/inkeep/agents/create-agents-template', expect.any(String), undefined);
|
|
194
195
|
expect(getAvailableTemplates).not.toHaveBeenCalled();
|
|
195
196
|
expect(fs.ensureDir).toHaveBeenCalledWith('src/projects/my-custom-project');
|
|
196
197
|
// Check that .env file is created
|
|
@@ -214,7 +215,7 @@ describe('createAgents - Template and Project ID Logic', () => {
|
|
|
214
215
|
anthropicKey: 'test-key',
|
|
215
216
|
});
|
|
216
217
|
expect(cloneTemplate).toHaveBeenCalledTimes(2);
|
|
217
|
-
expect(cloneTemplate).toHaveBeenCalledWith('https://github.com/inkeep/agents-cookbook/template-projects/my-complex-template', 'src/projects/my-complex-template', expect.arrayContaining([
|
|
218
|
+
expect(cloneTemplate).toHaveBeenCalledWith('https://github.com/inkeep/agents/agents-cookbook/template-projects/my-complex-template', 'src/projects/my-complex-template', expect.arrayContaining([
|
|
218
219
|
expect.objectContaining({
|
|
219
220
|
filePath: 'index.ts',
|
|
220
221
|
replacements: expect.objectContaining({
|
|
@@ -328,4 +329,5 @@ function setupDefaultMocks() {
|
|
|
328
329
|
vi.mocked(fs.writeFile).mockResolvedValue(undefined);
|
|
329
330
|
vi.mocked(getAvailableTemplates).mockResolvedValue(['event-planner', 'chatbot', 'data-analysis']);
|
|
330
331
|
vi.mocked(cloneTemplate).mockResolvedValue(undefined);
|
|
332
|
+
vi.mocked(cloneTemplateLocal).mockResolvedValue(undefined);
|
|
331
333
|
}
|
package/dist/index.js
CHANGED
|
@@ -11,6 +11,8 @@ program
|
|
|
11
11
|
.option('--anthropic-key <anthropic-key>', 'Anthropic API key')
|
|
12
12
|
.option('--custom-project-id <custom-project-id>', 'Custom project id for experienced users who want an empty project directory')
|
|
13
13
|
.option('--disable-git', 'Disable git initialization')
|
|
14
|
+
.option('--local-agents-prefix <local-agents-prefix>', 'Local prefix for create-agents-template')
|
|
15
|
+
.option('--local-templates-prefix <local-templates-prefix>', 'Local prefix for project templates')
|
|
14
16
|
.parse();
|
|
15
17
|
async function main() {
|
|
16
18
|
const options = program.opts();
|
|
@@ -23,6 +25,8 @@ async function main() {
|
|
|
23
25
|
customProjectId: options.customProjectId,
|
|
24
26
|
template: options.template,
|
|
25
27
|
disableGit: options.disableGit,
|
|
28
|
+
localAgentsPrefix: options.localAgentsPrefix,
|
|
29
|
+
localTemplatesPrefix: options.localTemplatesPrefix,
|
|
26
30
|
});
|
|
27
31
|
}
|
|
28
32
|
catch (error) {
|
package/dist/templates.d.ts
CHANGED
|
@@ -5,7 +5,7 @@ export interface ContentReplacement {
|
|
|
5
5
|
replacements: Record<string, any>;
|
|
6
6
|
}
|
|
7
7
|
export declare function cloneTemplate(templatePath: string, targetPath: string, replacements?: ContentReplacement[]): Promise<void>;
|
|
8
|
-
export declare function cloneTemplateLocal(templatePath: string, targetPath: string, replacements
|
|
8
|
+
export declare function cloneTemplateLocal(templatePath: string, targetPath: string, replacements?: ContentReplacement[]): Promise<void>;
|
|
9
9
|
/**
|
|
10
10
|
* Replace content in cloned template files
|
|
11
11
|
*/
|
|
@@ -14,4 +14,4 @@ export declare function replaceContentInFiles(targetPath: string, replacements:
|
|
|
14
14
|
* Replace object properties in TypeScript code content
|
|
15
15
|
*/
|
|
16
16
|
export declare function replaceObjectProperties(content: string, replacements: Record<string, any>): Promise<string>;
|
|
17
|
-
export declare function getAvailableTemplates(): Promise<string[]>;
|
|
17
|
+
export declare function getAvailableTemplates(localPrefix?: string): Promise<string[]>;
|
package/dist/templates.js
CHANGED
|
@@ -253,9 +253,16 @@ function injectPropertyIntoObject(content, propertyPath, replacement) {
|
|
|
253
253
|
console.warn(`Could not inject property "${propertyPath}" - no suitable object found in content`);
|
|
254
254
|
return content;
|
|
255
255
|
}
|
|
256
|
-
export async function getAvailableTemplates() {
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
256
|
+
export async function getAvailableTemplates(localPrefix) {
|
|
257
|
+
if (localPrefix && localPrefix.length > 0) {
|
|
258
|
+
const fullTemplatePath = path.join(localPrefix, 'template-projects');
|
|
259
|
+
const response = await fs.readdir(fullTemplatePath);
|
|
260
|
+
return response.filter((item) => fs.stat(path.join(fullTemplatePath, item)).then((stat) => stat.isDirectory()));
|
|
261
|
+
}
|
|
262
|
+
else {
|
|
263
|
+
// Fetch the list of templates from your repo
|
|
264
|
+
const response = await fetch(`https://api.github.com/repos/inkeep/agents/contents/agents-cookbook/template-projects`);
|
|
265
|
+
const contents = await response.json();
|
|
266
|
+
return contents.filter((item) => item.type === 'dir').map((item) => item.name);
|
|
267
|
+
}
|
|
261
268
|
}
|
package/dist/utils.d.ts
CHANGED
|
@@ -40,5 +40,7 @@ export declare const createAgents: (args?: {
|
|
|
40
40
|
template?: string;
|
|
41
41
|
customProjectId?: string;
|
|
42
42
|
disableGit?: boolean;
|
|
43
|
+
localAgentsPrefix?: string;
|
|
44
|
+
localTemplatesPrefix?: string;
|
|
43
45
|
}) => Promise<void>;
|
|
44
46
|
export declare function createCommand(dirName?: string, options?: any): Promise<void>;
|
package/dist/utils.js
CHANGED
|
@@ -6,7 +6,7 @@ import * as p from '@clack/prompts';
|
|
|
6
6
|
import { ANTHROPIC_MODELS, GOOGLE_MODELS, OPENAI_MODELS } from '@inkeep/agents-core';
|
|
7
7
|
import fs from 'fs-extra';
|
|
8
8
|
import color from 'picocolors';
|
|
9
|
-
import { cloneTemplate, getAvailableTemplates } from './templates.js';
|
|
9
|
+
import { cloneTemplate, cloneTemplateLocal, getAvailableTemplates, } from './templates.js';
|
|
10
10
|
// Shared validation utility
|
|
11
11
|
const DIRECTORY_VALIDATION = {
|
|
12
12
|
pattern: /^[a-zA-Z0-9][a-zA-Z0-9_-]*$/,
|
|
@@ -28,6 +28,8 @@ const DIRECTORY_VALIDATION = {
|
|
|
28
28
|
return undefined;
|
|
29
29
|
},
|
|
30
30
|
};
|
|
31
|
+
const agentsTemplateRepo = 'https://github.com/inkeep/agents/create-agents-template';
|
|
32
|
+
const projectTemplateRepo = 'https://github.com/inkeep/agents/agents-cookbook/template-projects';
|
|
31
33
|
const execAsync = promisify(exec);
|
|
32
34
|
const manageApiPort = '3002';
|
|
33
35
|
const runApiPort = '3003';
|
|
@@ -65,7 +67,7 @@ export const defaultAnthropicModelConfigurations = {
|
|
|
65
67
|
},
|
|
66
68
|
};
|
|
67
69
|
export const createAgents = async (args = {}) => {
|
|
68
|
-
let { dirName, openAiKey, anthropicKey, googleKey, template, customProjectId, disableGit } = args;
|
|
70
|
+
let { dirName, openAiKey, anthropicKey, googleKey, template, customProjectId, disableGit, localAgentsPrefix, localTemplatesPrefix, } = args;
|
|
69
71
|
const tenantId = 'default';
|
|
70
72
|
let projectId;
|
|
71
73
|
let templateName;
|
|
@@ -74,7 +76,7 @@ export const createAgents = async (args = {}) => {
|
|
|
74
76
|
templateName = '';
|
|
75
77
|
}
|
|
76
78
|
else if (template) {
|
|
77
|
-
const availableTemplates = await getAvailableTemplates();
|
|
79
|
+
const availableTemplates = await getAvailableTemplates(localTemplatesPrefix);
|
|
78
80
|
if (!availableTemplates.includes(template)) {
|
|
79
81
|
p.cancel(`${color.red('✗')} Template "${template}" not found\n\n` +
|
|
80
82
|
`${color.yellow('Available templates:')}\n` +
|
|
@@ -188,10 +190,6 @@ export const createAgents = async (args = {}) => {
|
|
|
188
190
|
const s = p.spinner();
|
|
189
191
|
s.start('Creating directory structure...');
|
|
190
192
|
try {
|
|
191
|
-
const agentsTemplateRepo = 'https://github.com/inkeep/create-agents-template';
|
|
192
|
-
const projectTemplateRepo = templateName
|
|
193
|
-
? `https://github.com/inkeep/agents-cookbook/template-projects/${templateName}`
|
|
194
|
-
: null;
|
|
195
193
|
const directoryPath = path.resolve(process.cwd(), dirName);
|
|
196
194
|
if (await fs.pathExists(directoryPath)) {
|
|
197
195
|
s.stop();
|
|
@@ -206,7 +204,10 @@ export const createAgents = async (args = {}) => {
|
|
|
206
204
|
await fs.emptyDir(directoryPath);
|
|
207
205
|
}
|
|
208
206
|
s.message('Building template...');
|
|
209
|
-
await
|
|
207
|
+
await cloneTemplateHelper({
|
|
208
|
+
targetPath: directoryPath,
|
|
209
|
+
localPrefix: localAgentsPrefix,
|
|
210
|
+
});
|
|
210
211
|
process.chdir(directoryPath);
|
|
211
212
|
const config = {
|
|
212
213
|
dirName,
|
|
@@ -223,7 +224,7 @@ export const createAgents = async (args = {}) => {
|
|
|
223
224
|
await createWorkspaceStructure();
|
|
224
225
|
s.message('Setting up environment files...');
|
|
225
226
|
await createEnvironmentFiles(config);
|
|
226
|
-
if (
|
|
227
|
+
if (templateName && templateName.length > 0) {
|
|
227
228
|
s.message('Creating project template folder...');
|
|
228
229
|
const templateTargetPath = `src/projects/${projectId}`;
|
|
229
230
|
const contentReplacements = [
|
|
@@ -234,7 +235,12 @@ export const createAgents = async (args = {}) => {
|
|
|
234
235
|
},
|
|
235
236
|
},
|
|
236
237
|
];
|
|
237
|
-
await
|
|
238
|
+
await cloneTemplateHelper({
|
|
239
|
+
templateName,
|
|
240
|
+
targetPath: templateTargetPath,
|
|
241
|
+
localPrefix: localTemplatesPrefix,
|
|
242
|
+
replacements: contentReplacements,
|
|
243
|
+
});
|
|
238
244
|
}
|
|
239
245
|
else {
|
|
240
246
|
s.message('Creating empty project folder...');
|
|
@@ -316,14 +322,18 @@ INKEEP_AGENTS_JWT_SIGNING_SECRET=${jwtSigningSecret}
|
|
|
316
322
|
}
|
|
317
323
|
async function createInkeepConfig(config) {
|
|
318
324
|
const inkeepConfig = `import { defineConfig } from '@inkeep/agents-cli/config';
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
}
|
|
325
|
-
|
|
326
|
-
|
|
325
|
+
|
|
326
|
+
const config = defineConfig({
|
|
327
|
+
tenantId: "${config.tenantId}",
|
|
328
|
+
agentsManageApi: {
|
|
329
|
+
url: 'http://localhost:3002',
|
|
330
|
+
},
|
|
331
|
+
agentsRunApi: {
|
|
332
|
+
url: 'http://localhost:3003',
|
|
333
|
+
},
|
|
334
|
+
});
|
|
335
|
+
|
|
336
|
+
export default config;`;
|
|
327
337
|
await fs.writeFile(`src/inkeep.config.ts`, inkeepConfig);
|
|
328
338
|
if (config.customProject) {
|
|
329
339
|
const customIndexContent = `import { project } from '@inkeep/agents-sdk';
|
|
@@ -358,14 +368,16 @@ async function isPortAvailable(port) {
|
|
|
358
368
|
const net = await import('node:net');
|
|
359
369
|
return new Promise((resolve) => {
|
|
360
370
|
const server = net.createServer();
|
|
361
|
-
server.once('error', () => {
|
|
362
|
-
|
|
371
|
+
server.once('error', (err) => {
|
|
372
|
+
// Only treat EADDRINUSE as "port in use", other errors might be transient
|
|
373
|
+
resolve(err.code === 'EADDRINUSE' ? false : true);
|
|
363
374
|
});
|
|
364
375
|
server.once('listening', () => {
|
|
365
|
-
server.close()
|
|
366
|
-
|
|
376
|
+
server.close(() => {
|
|
377
|
+
resolve(true);
|
|
378
|
+
});
|
|
367
379
|
});
|
|
368
|
-
server.listen(port);
|
|
380
|
+
server.listen(port, 'localhost');
|
|
369
381
|
});
|
|
370
382
|
}
|
|
371
383
|
/**
|
|
@@ -399,6 +411,25 @@ async function checkPortsAvailability() {
|
|
|
399
411
|
});
|
|
400
412
|
}
|
|
401
413
|
}
|
|
414
|
+
/**
|
|
415
|
+
* Wait for a server to be ready by polling a health endpoint
|
|
416
|
+
*/
|
|
417
|
+
async function waitForServerReady(url, timeout) {
|
|
418
|
+
const start = Date.now();
|
|
419
|
+
while (Date.now() - start < timeout) {
|
|
420
|
+
try {
|
|
421
|
+
const response = await fetch(url);
|
|
422
|
+
if (response.ok) {
|
|
423
|
+
return;
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
catch {
|
|
427
|
+
// Server not ready yet, continue polling
|
|
428
|
+
}
|
|
429
|
+
await new Promise((resolve) => setTimeout(resolve, 1000)); // Check every second
|
|
430
|
+
}
|
|
431
|
+
throw new Error(`Server not ready at ${url} after ${timeout}ms`);
|
|
432
|
+
}
|
|
402
433
|
async function setupProjectInDatabase(config) {
|
|
403
434
|
// Proactively check if ports are available BEFORE starting servers
|
|
404
435
|
await checkPortsAvailability();
|
|
@@ -411,7 +442,6 @@ async function setupProjectInDatabase(config) {
|
|
|
411
442
|
shell: true,
|
|
412
443
|
windowsHide: true,
|
|
413
444
|
});
|
|
414
|
-
await new Promise((resolve) => setTimeout(resolve, 5000));
|
|
415
445
|
// Track if port errors occur during startup (as a safety fallback)
|
|
416
446
|
const portErrors = { runApi: false, manageApi: false };
|
|
417
447
|
// Regex patterns for detecting port errors in output
|
|
@@ -430,8 +460,15 @@ async function setupProjectInDatabase(config) {
|
|
|
430
460
|
}
|
|
431
461
|
};
|
|
432
462
|
devProcess.stdout.on('data', checkForPortErrors);
|
|
433
|
-
//
|
|
434
|
-
|
|
463
|
+
// Wait for servers to be ready
|
|
464
|
+
try {
|
|
465
|
+
await waitForServerReady(`http://localhost:${manageApiPort}/health`, 60000);
|
|
466
|
+
await waitForServerReady(`http://localhost:${runApiPort}/health`, 60000);
|
|
467
|
+
}
|
|
468
|
+
catch (error) {
|
|
469
|
+
// If servers don't start, we'll still try push but it will likely fail
|
|
470
|
+
console.warn('Warning: Servers may not be fully ready:', error instanceof Error ? error.message : String(error));
|
|
471
|
+
}
|
|
435
472
|
// Check if any port errors occurred during startup
|
|
436
473
|
if (portErrors.runApi || portErrors.manageApi) {
|
|
437
474
|
displayPortConflictError(portErrors);
|
|
@@ -474,6 +511,27 @@ async function setupDatabase() {
|
|
|
474
511
|
throw new Error(`Failed to setup database: ${error instanceof Error ? error.message : 'Unknown error'}`);
|
|
475
512
|
}
|
|
476
513
|
}
|
|
514
|
+
async function cloneTemplateHelper(options) {
|
|
515
|
+
const { targetPath, templateName, localPrefix, replacements } = options;
|
|
516
|
+
// If local prefix is provided, use it to clone the template. This is useful for local development and testing.
|
|
517
|
+
if (localPrefix && localPrefix.length > 0) {
|
|
518
|
+
if (templateName) {
|
|
519
|
+
const fullTemplatePath = path.join(localPrefix, templateName);
|
|
520
|
+
await cloneTemplateLocal(fullTemplatePath, targetPath, replacements);
|
|
521
|
+
}
|
|
522
|
+
else {
|
|
523
|
+
await cloneTemplateLocal(localPrefix, targetPath, replacements);
|
|
524
|
+
}
|
|
525
|
+
}
|
|
526
|
+
else {
|
|
527
|
+
if (templateName) {
|
|
528
|
+
await cloneTemplate(`${projectTemplateRepo}/${templateName}`, targetPath, replacements);
|
|
529
|
+
}
|
|
530
|
+
else {
|
|
531
|
+
await cloneTemplate(agentsTemplateRepo, targetPath, replacements);
|
|
532
|
+
}
|
|
533
|
+
}
|
|
534
|
+
}
|
|
477
535
|
export async function createCommand(dirName, options) {
|
|
478
536
|
await createAgents({
|
|
479
537
|
dirName,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@inkeep/create-agents",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.30.0",
|
|
4
4
|
"description": "Create an Inkeep Agent Framework project",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -31,15 +31,16 @@
|
|
|
31
31
|
"@clack/prompts": "^0.11.0",
|
|
32
32
|
"commander": "^12.0.0",
|
|
33
33
|
"degit": "^2.8.4",
|
|
34
|
+
"drizzle-kit": "^0.31.5",
|
|
34
35
|
"fs-extra": "^11.0.0",
|
|
35
36
|
"picocolors": "^1.0.0",
|
|
36
|
-
"
|
|
37
|
-
"@inkeep/agents-core": "0.29.10"
|
|
37
|
+
"@inkeep/agents-core": "0.30.0"
|
|
38
38
|
},
|
|
39
39
|
"devDependencies": {
|
|
40
40
|
"@types/degit": "^2.8.6",
|
|
41
41
|
"@types/fs-extra": "^11.0.0",
|
|
42
42
|
"@types/node": "^20.12.0",
|
|
43
|
+
"execa": "^9.6.0",
|
|
43
44
|
"tsx": "^4.7.0",
|
|
44
45
|
"typescript": "^5.4.0",
|
|
45
46
|
"vitest": "^3.2.4"
|
|
@@ -62,8 +63,12 @@
|
|
|
62
63
|
"lint": "biome lint src",
|
|
63
64
|
"lint:fix": "biome check --write .",
|
|
64
65
|
"format": "biome format --write .",
|
|
65
|
-
"test": "vitest --run --passWithNoTests",
|
|
66
|
-
"test:watch": "vitest",
|
|
66
|
+
"test": "vitest --run src/__tests__ --exclude src/__tests__/e2e/** --passWithNoTests",
|
|
67
|
+
"test:watch": "vitest src/__tests__ --exclude src/__tests__/e2e/**",
|
|
68
|
+
"test:unit": "vitest --run src/__tests__ --exclude src/__tests__/e2e/**",
|
|
69
|
+
"test:e2e": "vitest --run src/__tests__/e2e",
|
|
70
|
+
"test:e2e:watch": "vitest src/__tests__/e2e",
|
|
71
|
+
"test:all": "vitest --run --passWithNoTests",
|
|
67
72
|
"typecheck": "tsc --noEmit"
|
|
68
73
|
}
|
|
69
74
|
}
|