@inkeep/create-agents 0.52.0 → 0.53.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.js +6 -4
- package/dist/__tests__/e2e/utils.js +6 -2
- package/dist/__tests__/utils.test.js +285 -2
- package/dist/utils.d.ts +1 -0
- package/dist/utils.js +71 -75
- package/package.json +4 -4
|
@@ -62,13 +62,15 @@ describe('create-agents quickstart e2e', () => {
|
|
|
62
62
|
]);
|
|
63
63
|
console.log('Directory structure verified');
|
|
64
64
|
// Verify .env file has required variables
|
|
65
|
+
// After createEnvironmentFiles(), .env is a copy of .env.example with CLI-prompted
|
|
66
|
+
// values injected. Secrets (JWT keys, signing secret, etc.) remain as placeholders
|
|
67
|
+
// until setup-dev runs generateSecrets().
|
|
65
68
|
console.log('Verifying .env file...');
|
|
66
69
|
await verifyFile(path.join(projectDir, '.env'), [
|
|
67
70
|
/ENVIRONMENT=development/,
|
|
68
71
|
/INKEEP_AGENTS_MANAGE_DATABASE_URL=postgresql:\/\/appuser:password@localhost:5432\/inkeep_agents/,
|
|
69
72
|
/INKEEP_AGENTS_RUN_DATABASE_URL=postgresql:\/\/appuser:password@localhost:5433\/inkeep_agents/,
|
|
70
|
-
/INKEEP_AGENTS_API_URL=
|
|
71
|
-
/INKEEP_AGENTS_JWT_SIGNING_SECRET=\w+/,
|
|
73
|
+
/INKEEP_AGENTS_API_URL=http:\/\/localhost:3002/,
|
|
72
74
|
]);
|
|
73
75
|
console.log('.env file verified');
|
|
74
76
|
// Verify inkeep.config.ts was created
|
|
@@ -167,7 +169,7 @@ describe('create-agents quickstart e2e', () => {
|
|
|
167
169
|
try {
|
|
168
170
|
const signupRes = await fetch(`${manageApiUrl}/api/auth/sign-up/email`, {
|
|
169
171
|
method: 'POST',
|
|
170
|
-
headers: { 'Content-Type': 'application/json', Origin:
|
|
172
|
+
headers: { 'Content-Type': 'application/json', Origin: dashboardApiUrl },
|
|
171
173
|
body: JSON.stringify({
|
|
172
174
|
email: 'admin@example.com',
|
|
173
175
|
password: 'adminADMIN!@12',
|
|
@@ -211,7 +213,7 @@ describe('create-agents quickstart e2e', () => {
|
|
|
211
213
|
try {
|
|
212
214
|
const loginTestRes = await fetch(`${manageApiUrl}/api/auth/sign-in/email`, {
|
|
213
215
|
method: 'POST',
|
|
214
|
-
headers: { 'Content-Type': 'application/json', Origin:
|
|
216
|
+
headers: { 'Content-Type': 'application/json', Origin: dashboardApiUrl },
|
|
215
217
|
body: JSON.stringify({
|
|
216
218
|
email: 'admin@example.com',
|
|
217
219
|
password: 'adminADMIN!@12',
|
|
@@ -222,11 +222,15 @@ export async function waitForServerReady(url, timeout) {
|
|
|
222
222
|
}
|
|
223
223
|
export async function startDashboardServer(projectDir, env = {}) {
|
|
224
224
|
const manageUiPkgJson = path.join(projectDir, 'node_modules/@inkeep/agents-manage-ui/package.json');
|
|
225
|
-
|
|
225
|
+
// Resolve symlinks so linked packages (link:) point to the actual monorepo directory
|
|
226
|
+
const manageUiRoot = await fs.realpath(path.dirname(manageUiPkgJson));
|
|
226
227
|
const standaloneDir = path.join(manageUiRoot, '.next/standalone/agents-manage-ui');
|
|
227
228
|
const serverEntry = path.join(standaloneDir, 'server.js');
|
|
228
229
|
if (!(await fs.pathExists(serverEntry))) {
|
|
229
|
-
|
|
230
|
+
const originalPath = path.dirname(manageUiPkgJson);
|
|
231
|
+
throw new Error(`Dashboard standalone server not found at ${serverEntry}` +
|
|
232
|
+
(originalPath !== manageUiRoot ? ` (symlink resolved from ${originalPath})` : '') +
|
|
233
|
+
`. Ensure the package is built with 'output: standalone' (run turbo build).`);
|
|
230
234
|
}
|
|
231
235
|
const child = fork(serverEntry, [], {
|
|
232
236
|
cwd: standaloneDir,
|
|
@@ -2,7 +2,7 @@ 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
4
|
import { cloneTemplate, cloneTemplateLocal, getAvailableTemplates } from '../templates';
|
|
5
|
-
import { createAgents, defaultMockModelConfigurations } from '../utils';
|
|
5
|
+
import { createAgents, defaultMockModelConfigurations, syncTemplateDependencies } from '../utils';
|
|
6
6
|
// Create the mock execAsync function that will be used by promisify - hoisted so it's available in mocks
|
|
7
7
|
const { mockExecAsync } = vi.hoisted(() => ({
|
|
8
8
|
mockExecAsync: vi.fn().mockResolvedValue({ stdout: '', stderr: '' }),
|
|
@@ -23,12 +23,54 @@ vi.mock('node:child_process', () => ({
|
|
|
23
23
|
vi.mock('node:util', () => ({
|
|
24
24
|
promisify: vi.fn(() => mockExecAsync),
|
|
25
25
|
}));
|
|
26
|
+
vi.mock('node:fs', async (importOriginal) => {
|
|
27
|
+
const actual = await importOriginal();
|
|
28
|
+
return {
|
|
29
|
+
...actual,
|
|
30
|
+
readFileSync: vi.fn(() => JSON.stringify({ version: '1.2.3' })),
|
|
31
|
+
};
|
|
32
|
+
});
|
|
33
|
+
vi.mock('node:url', async (importOriginal) => {
|
|
34
|
+
const actual = await importOriginal();
|
|
35
|
+
return {
|
|
36
|
+
...actual,
|
|
37
|
+
fileURLToPath: vi.fn(() => '/fake/dist/utils.js'),
|
|
38
|
+
};
|
|
39
|
+
});
|
|
26
40
|
// Setup default mocks
|
|
27
41
|
const mockSpinner = {
|
|
28
42
|
start: vi.fn().mockReturnThis(),
|
|
29
43
|
stop: vi.fn().mockReturnThis(),
|
|
30
44
|
message: vi.fn().mockReturnThis(),
|
|
31
45
|
};
|
|
46
|
+
const mockEnvExample = [
|
|
47
|
+
'ENVIRONMENT=development',
|
|
48
|
+
'NODE_ENV=development',
|
|
49
|
+
'LOG_LEVEL=info',
|
|
50
|
+
'INKEEP_AGENTS_MANAGE_DATABASE_URL=postgresql://appuser:password@localhost:5432/inkeep_agents',
|
|
51
|
+
'INKEEP_AGENTS_RUN_DATABASE_URL=postgresql://appuser:password@localhost:5433/inkeep_agents',
|
|
52
|
+
'INKEEP_AGENTS_API_URL=http://localhost:3002',
|
|
53
|
+
'PUBLIC_INKEEP_AGENTS_API_URL=http://localhost:3002',
|
|
54
|
+
'TENANT_ID=default',
|
|
55
|
+
'ANTHROPIC_API_KEY=',
|
|
56
|
+
'OPENAI_API_KEY=',
|
|
57
|
+
'GOOGLE_GENERATIVE_AI_API_KEY=',
|
|
58
|
+
'AZURE_API_KEY=',
|
|
59
|
+
'DEFAULT_PROJECT_ID=',
|
|
60
|
+
'NANGO_SECRET_KEY=',
|
|
61
|
+
'NANGO_SERVER_URL=http://localhost:3050',
|
|
62
|
+
'SIGNOZ_URL=http://localhost:3080',
|
|
63
|
+
'OTEL_EXPORTER_OTLP_TRACES_ENDPOINT=',
|
|
64
|
+
'INKEEP_AGENTS_MANAGE_UI_USERNAME=admin@example.com',
|
|
65
|
+
'INKEEP_AGENTS_MANAGE_UI_PASSWORD=adminADMIN!@12',
|
|
66
|
+
'BETTER_AUTH_SECRET=your-secret-key-change-in-production',
|
|
67
|
+
'SPICEDB_ENDPOINT=localhost:50051',
|
|
68
|
+
'SPICEDB_PRESHARED_KEY=dev-secret-key',
|
|
69
|
+
'INKEEP_AGENTS_MANAGE_API_BYPASS_SECRET=test-bypass-secret-for-ci',
|
|
70
|
+
'# INKEEP_AGENTS_JWT_SIGNING_SECRET=',
|
|
71
|
+
'# INKEEP_AGENTS_TEMP_JWT_PRIVATE_KEY=',
|
|
72
|
+
'# INKEEP_AGENTS_TEMP_JWT_PUBLIC_KEY=',
|
|
73
|
+
].join('\n');
|
|
32
74
|
describe('createAgents - Template and Project ID Logic', () => {
|
|
33
75
|
let processExitSpy;
|
|
34
76
|
let processChdirSpy;
|
|
@@ -64,6 +106,8 @@ describe('createAgents - Template and Project ID Logic', () => {
|
|
|
64
106
|
vi.mocked(fs.writeFile).mockResolvedValue(undefined);
|
|
65
107
|
vi.mocked(fs.writeJson).mockResolvedValue(undefined);
|
|
66
108
|
vi.mocked(fs.readJson).mockResolvedValue({});
|
|
109
|
+
vi.mocked(fs.readFile).mockResolvedValue(mockEnvExample);
|
|
110
|
+
vi.mocked(fs.readdir).mockResolvedValue([]);
|
|
67
111
|
vi.mocked(fs.mkdir).mockResolvedValue(undefined);
|
|
68
112
|
vi.mocked(fs.remove).mockResolvedValue(undefined);
|
|
69
113
|
// Mock templates
|
|
@@ -331,6 +375,49 @@ describe('createAgents - Template and Project ID Logic', () => {
|
|
|
331
375
|
]));
|
|
332
376
|
});
|
|
333
377
|
});
|
|
378
|
+
describe('Environment file generation', () => {
|
|
379
|
+
it('should contain INKEEP_AGENTS_MANAGE_API_BYPASS_SECRET from .env.example', async () => {
|
|
380
|
+
await createAgents({
|
|
381
|
+
dirName: 'test-dir',
|
|
382
|
+
openAiKey: 'test-openai-key',
|
|
383
|
+
anthropicKey: 'test-anthropic-key',
|
|
384
|
+
});
|
|
385
|
+
expect(fs.writeFile).toHaveBeenCalledWith('.env', expect.stringContaining('INKEEP_AGENTS_MANAGE_API_BYPASS_SECRET=test-bypass-secret-for-ci'));
|
|
386
|
+
});
|
|
387
|
+
it('should inject CLI-prompted API keys into the .env', async () => {
|
|
388
|
+
await createAgents({
|
|
389
|
+
dirName: 'test-dir',
|
|
390
|
+
openAiKey: 'sk-openai-123',
|
|
391
|
+
anthropicKey: 'sk-ant-456',
|
|
392
|
+
googleKey: 'google-789',
|
|
393
|
+
azureKey: 'azure-abc',
|
|
394
|
+
});
|
|
395
|
+
expect(fs.writeFile).toHaveBeenCalledWith('.env', expect.stringContaining('ANTHROPIC_API_KEY=sk-ant-456'));
|
|
396
|
+
expect(fs.writeFile).toHaveBeenCalledWith('.env', expect.stringContaining('OPENAI_API_KEY=sk-openai-123'));
|
|
397
|
+
expect(fs.writeFile).toHaveBeenCalledWith('.env', expect.stringContaining('GOOGLE_GENERATIVE_AI_API_KEY=google-789'));
|
|
398
|
+
expect(fs.writeFile).toHaveBeenCalledWith('.env', expect.stringContaining('AZURE_API_KEY=azure-abc'));
|
|
399
|
+
});
|
|
400
|
+
it('should use localhost URLs (not 127.0.0.1)', async () => {
|
|
401
|
+
await createAgents({
|
|
402
|
+
dirName: 'test-dir',
|
|
403
|
+
openAiKey: 'test-key',
|
|
404
|
+
anthropicKey: 'test-key',
|
|
405
|
+
});
|
|
406
|
+
expect(fs.writeFile).toHaveBeenCalledWith('.env', expect.stringContaining('PUBLIC_INKEEP_AGENTS_API_URL=http://localhost:3002'));
|
|
407
|
+
});
|
|
408
|
+
it('should not generate any secrets inline', async () => {
|
|
409
|
+
await createAgents({
|
|
410
|
+
dirName: 'test-dir',
|
|
411
|
+
openAiKey: 'test-key',
|
|
412
|
+
anthropicKey: 'test-key',
|
|
413
|
+
});
|
|
414
|
+
const envWriteCall = vi.mocked(fs.writeFile).mock.calls.find((call) => call[0] === '.env');
|
|
415
|
+
const envContent = envWriteCall?.[1];
|
|
416
|
+
expect(envContent).toContain('BETTER_AUTH_SECRET=your-secret-key-change-in-production');
|
|
417
|
+
expect(envContent).toContain('INKEEP_AGENTS_MANAGE_UI_PASSWORD=adminADMIN!@12');
|
|
418
|
+
expect(envContent).toContain('# INKEEP_AGENTS_JWT_SIGNING_SECRET=');
|
|
419
|
+
});
|
|
420
|
+
});
|
|
334
421
|
describe('Security - Password input for API keys', () => {
|
|
335
422
|
it('should use password input instead of text input for API keys', async () => {
|
|
336
423
|
// Mock the select to return 'anthropic' to trigger the API key prompt
|
|
@@ -388,9 +475,205 @@ function setupDefaultMocks() {
|
|
|
388
475
|
vi.mocked(fs.writeFile).mockResolvedValue(undefined);
|
|
389
476
|
vi.mocked(fs.writeJson).mockResolvedValue(undefined);
|
|
390
477
|
vi.mocked(fs.readJson).mockResolvedValue({});
|
|
478
|
+
vi.mocked(fs.readFile).mockResolvedValue(mockEnvExample);
|
|
479
|
+
vi.mocked(fs.readdir).mockResolvedValue([]);
|
|
391
480
|
vi.mocked(getAvailableTemplates).mockResolvedValue(['event-planner', 'chatbot', 'data-analysis']);
|
|
392
481
|
vi.mocked(cloneTemplate).mockResolvedValue(undefined);
|
|
393
482
|
vi.mocked(cloneTemplateLocal).mockResolvedValue(undefined);
|
|
394
|
-
// Reset mockExecAsync for tests that clear mocks
|
|
395
483
|
mockExecAsync.mockResolvedValue({ stdout: '', stderr: '' });
|
|
396
484
|
}
|
|
485
|
+
describe('syncTemplateDependencies', () => {
|
|
486
|
+
function mockDirent(name, isDir) {
|
|
487
|
+
return { name, isDirectory: () => isDir, isFile: () => !isDir };
|
|
488
|
+
}
|
|
489
|
+
function setupFlatTemplate(rootPkg) {
|
|
490
|
+
vi.mocked(fs.pathExists).mockResolvedValue(true);
|
|
491
|
+
vi.mocked(fs.readdir).mockResolvedValue([]);
|
|
492
|
+
vi.mocked(fs.readJson).mockResolvedValue(rootPkg);
|
|
493
|
+
vi.mocked(fs.writeJson).mockResolvedValue(undefined);
|
|
494
|
+
}
|
|
495
|
+
beforeEach(() => {
|
|
496
|
+
vi.clearAllMocks();
|
|
497
|
+
});
|
|
498
|
+
it('should update @inkeep/* dependencies to match CLI version', async () => {
|
|
499
|
+
const mockPkg = {
|
|
500
|
+
name: 'test-project',
|
|
501
|
+
dependencies: {
|
|
502
|
+
'@inkeep/agents-core': '^0.50.3',
|
|
503
|
+
'@inkeep/agents-sdk': '^0.50.3',
|
|
504
|
+
'some-other-package': '^1.0.0',
|
|
505
|
+
},
|
|
506
|
+
};
|
|
507
|
+
setupFlatTemplate(mockPkg);
|
|
508
|
+
await syncTemplateDependencies('/test/path');
|
|
509
|
+
expect(fs.writeJson).toHaveBeenCalledWith('/test/path/package.json', expect.objectContaining({
|
|
510
|
+
dependencies: {
|
|
511
|
+
'@inkeep/agents-core': '^1.2.3',
|
|
512
|
+
'@inkeep/agents-sdk': '^1.2.3',
|
|
513
|
+
'some-other-package': '^1.0.0',
|
|
514
|
+
},
|
|
515
|
+
}), { spaces: 2 });
|
|
516
|
+
});
|
|
517
|
+
it('should skip if template package.json does not exist', async () => {
|
|
518
|
+
vi.mocked(fs.pathExists).mockResolvedValue(false);
|
|
519
|
+
vi.mocked(fs.readdir).mockResolvedValue([]);
|
|
520
|
+
await syncTemplateDependencies('/test/path');
|
|
521
|
+
expect(fs.readJson).not.toHaveBeenCalled();
|
|
522
|
+
expect(fs.writeJson).not.toHaveBeenCalled();
|
|
523
|
+
});
|
|
524
|
+
it('should handle template with no @inkeep/* dependencies', async () => {
|
|
525
|
+
const mockPkg = {
|
|
526
|
+
name: 'test-project',
|
|
527
|
+
dependencies: {
|
|
528
|
+
'some-other-package': '^1.0.0',
|
|
529
|
+
},
|
|
530
|
+
};
|
|
531
|
+
setupFlatTemplate(mockPkg);
|
|
532
|
+
await syncTemplateDependencies('/test/path');
|
|
533
|
+
expect(fs.writeJson).toHaveBeenCalledWith('/test/path/package.json', expect.objectContaining({
|
|
534
|
+
dependencies: {
|
|
535
|
+
'some-other-package': '^1.0.0',
|
|
536
|
+
},
|
|
537
|
+
}), { spaces: 2 });
|
|
538
|
+
});
|
|
539
|
+
it('should handle template with no devDependencies', async () => {
|
|
540
|
+
const mockPkg = {
|
|
541
|
+
name: 'test-project',
|
|
542
|
+
dependencies: {
|
|
543
|
+
'@inkeep/agents-core': '^0.50.3',
|
|
544
|
+
},
|
|
545
|
+
};
|
|
546
|
+
setupFlatTemplate(mockPkg);
|
|
547
|
+
await syncTemplateDependencies('/test/path');
|
|
548
|
+
expect(fs.writeJson).toHaveBeenCalledWith('/test/path/package.json', expect.objectContaining({
|
|
549
|
+
dependencies: {
|
|
550
|
+
'@inkeep/agents-core': '^1.2.3',
|
|
551
|
+
},
|
|
552
|
+
}), { spaces: 2 });
|
|
553
|
+
});
|
|
554
|
+
it('should update devDependencies @inkeep/* packages', async () => {
|
|
555
|
+
const mockPkg = {
|
|
556
|
+
name: 'test-project',
|
|
557
|
+
dependencies: {},
|
|
558
|
+
devDependencies: {
|
|
559
|
+
'@inkeep/agents-sdk': '^0.49.0',
|
|
560
|
+
vitest: '^1.0.0',
|
|
561
|
+
},
|
|
562
|
+
};
|
|
563
|
+
setupFlatTemplate(mockPkg);
|
|
564
|
+
await syncTemplateDependencies('/test/path');
|
|
565
|
+
expect(fs.writeJson).toHaveBeenCalledWith('/test/path/package.json', expect.objectContaining({
|
|
566
|
+
devDependencies: {
|
|
567
|
+
'@inkeep/agents-sdk': '^1.2.3',
|
|
568
|
+
vitest: '^1.0.0',
|
|
569
|
+
},
|
|
570
|
+
}), { spaces: 2 });
|
|
571
|
+
});
|
|
572
|
+
it('should not modify non-@inkeep dependencies', async () => {
|
|
573
|
+
const mockPkg = {
|
|
574
|
+
name: 'test-project',
|
|
575
|
+
dependencies: {
|
|
576
|
+
'@inkeep/agents-core': '^0.50.3',
|
|
577
|
+
react: '^18.0.0',
|
|
578
|
+
next: '^14.0.0',
|
|
579
|
+
},
|
|
580
|
+
};
|
|
581
|
+
setupFlatTemplate(mockPkg);
|
|
582
|
+
await syncTemplateDependencies('/test/path');
|
|
583
|
+
expect(fs.writeJson).toHaveBeenCalledWith('/test/path/package.json', expect.objectContaining({
|
|
584
|
+
dependencies: {
|
|
585
|
+
'@inkeep/agents-core': '^1.2.3',
|
|
586
|
+
react: '^18.0.0',
|
|
587
|
+
next: '^14.0.0',
|
|
588
|
+
},
|
|
589
|
+
}), { spaces: 2 });
|
|
590
|
+
});
|
|
591
|
+
it('should not update excluded packages like @inkeep/agents-ui', async () => {
|
|
592
|
+
const mockPkg = {
|
|
593
|
+
name: 'test-project',
|
|
594
|
+
dependencies: {
|
|
595
|
+
'@inkeep/agents-core': '^0.50.3',
|
|
596
|
+
'@inkeep/agents-ui': '^0.50.3',
|
|
597
|
+
'@inkeep/agents-sdk': '^0.50.3',
|
|
598
|
+
},
|
|
599
|
+
};
|
|
600
|
+
setupFlatTemplate(mockPkg);
|
|
601
|
+
await syncTemplateDependencies('/test/path');
|
|
602
|
+
expect(fs.writeJson).toHaveBeenCalledWith('/test/path/package.json', expect.objectContaining({
|
|
603
|
+
dependencies: {
|
|
604
|
+
'@inkeep/agents-core': '^1.2.3',
|
|
605
|
+
'@inkeep/agents-ui': '^0.50.3',
|
|
606
|
+
'@inkeep/agents-sdk': '^1.2.3',
|
|
607
|
+
},
|
|
608
|
+
}), { spaces: 2 });
|
|
609
|
+
});
|
|
610
|
+
it('should skip sync when CLI version cannot be determined', async () => {
|
|
611
|
+
const nodeFs = await import('node:fs');
|
|
612
|
+
vi.mocked(nodeFs.readFileSync).mockImplementation(() => {
|
|
613
|
+
throw new Error('ENOENT');
|
|
614
|
+
});
|
|
615
|
+
vi.mocked(fs.pathExists).mockResolvedValue(true);
|
|
616
|
+
vi.mocked(fs.readdir).mockResolvedValue([]);
|
|
617
|
+
vi.mocked(fs.readJson).mockResolvedValue({
|
|
618
|
+
name: 'test-project',
|
|
619
|
+
dependencies: { '@inkeep/agents-core': '^0.50.3' },
|
|
620
|
+
});
|
|
621
|
+
await syncTemplateDependencies('/test/path');
|
|
622
|
+
expect(fs.writeJson).not.toHaveBeenCalled();
|
|
623
|
+
});
|
|
624
|
+
it('should sync nested package.json files in subdirectories', async () => {
|
|
625
|
+
const nodeFs = await import('node:fs');
|
|
626
|
+
vi.mocked(nodeFs.readFileSync).mockReturnValue(JSON.stringify({ version: '1.2.3' }));
|
|
627
|
+
const rootPkg = {
|
|
628
|
+
name: 'monorepo',
|
|
629
|
+
dependencies: { '@inkeep/agents-core': '^0.50.3' },
|
|
630
|
+
};
|
|
631
|
+
const nestedPkg = {
|
|
632
|
+
name: 'nested-app',
|
|
633
|
+
dependencies: { '@inkeep/agents-sdk': '^0.50.3', express: '^4.0.0' },
|
|
634
|
+
};
|
|
635
|
+
vi.mocked(fs.pathExists).mockResolvedValue(true);
|
|
636
|
+
vi.mocked(fs.readdir)
|
|
637
|
+
.mockResolvedValueOnce([mockDirent('apps', true), mockDirent('README.md', false)])
|
|
638
|
+
.mockResolvedValueOnce([mockDirent('api', true)])
|
|
639
|
+
.mockResolvedValueOnce([]);
|
|
640
|
+
vi.mocked(fs.readJson)
|
|
641
|
+
.mockResolvedValueOnce(rootPkg)
|
|
642
|
+
.mockResolvedValueOnce(nestedPkg)
|
|
643
|
+
.mockResolvedValueOnce(nestedPkg);
|
|
644
|
+
vi.mocked(fs.writeJson).mockResolvedValue(undefined);
|
|
645
|
+
await syncTemplateDependencies('/test/path');
|
|
646
|
+
expect(fs.writeJson).toHaveBeenCalledTimes(3);
|
|
647
|
+
expect(fs.writeJson).toHaveBeenCalledWith('/test/path/package.json', expect.objectContaining({
|
|
648
|
+
dependencies: { '@inkeep/agents-core': '^1.2.3' },
|
|
649
|
+
}), { spaces: 2 });
|
|
650
|
+
expect(fs.writeJson).toHaveBeenCalledWith(expect.stringContaining('apps/package.json'), expect.anything(), { spaces: 2 });
|
|
651
|
+
expect(fs.writeJson).toHaveBeenCalledWith(expect.stringContaining('apps/api/package.json'), expect.objectContaining({
|
|
652
|
+
dependencies: { '@inkeep/agents-sdk': '^1.2.3', express: '^4.0.0' },
|
|
653
|
+
}), { spaces: 2 });
|
|
654
|
+
});
|
|
655
|
+
it('should skip node_modules and dot directories', async () => {
|
|
656
|
+
const nodeFs = await import('node:fs');
|
|
657
|
+
vi.mocked(nodeFs.readFileSync).mockReturnValue(JSON.stringify({ version: '1.2.3' }));
|
|
658
|
+
const rootPkg = {
|
|
659
|
+
name: 'test-project',
|
|
660
|
+
dependencies: { '@inkeep/agents-core': '^0.50.3' },
|
|
661
|
+
};
|
|
662
|
+
vi.mocked(fs.pathExists).mockResolvedValue(true);
|
|
663
|
+
vi.mocked(fs.readdir)
|
|
664
|
+
.mockResolvedValueOnce([
|
|
665
|
+
mockDirent('node_modules', true),
|
|
666
|
+
mockDirent('.git', true),
|
|
667
|
+
mockDirent('src', true),
|
|
668
|
+
])
|
|
669
|
+
.mockResolvedValueOnce([]);
|
|
670
|
+
vi.mocked(fs.readJson).mockResolvedValue(rootPkg);
|
|
671
|
+
vi.mocked(fs.writeJson).mockResolvedValue(undefined);
|
|
672
|
+
await syncTemplateDependencies('/test/path');
|
|
673
|
+
expect(fs.readdir).toHaveBeenCalledTimes(2);
|
|
674
|
+
expect(fs.readdir).toHaveBeenCalledWith('/test/path', { withFileTypes: true });
|
|
675
|
+
expect(fs.readdir).toHaveBeenCalledWith(expect.stringContaining('src'), {
|
|
676
|
+
withFileTypes: true,
|
|
677
|
+
});
|
|
678
|
+
});
|
|
679
|
+
});
|
package/dist/utils.d.ts
CHANGED
package/dist/utils.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { exec } from 'node:child_process';
|
|
2
|
-
import
|
|
2
|
+
import { readFileSync } from 'node:fs';
|
|
3
3
|
import os from 'node:os';
|
|
4
4
|
import path from 'node:path';
|
|
5
|
+
import { fileURLToPath } from 'node:url';
|
|
5
6
|
import { promisify } from 'node:util';
|
|
6
7
|
import * as p from '@clack/prompts';
|
|
7
8
|
import { ANTHROPIC_MODELS, GOOGLE_MODELS, OPENAI_MODELS } from '@inkeep/agents-core';
|
|
@@ -33,6 +34,52 @@ const agentsTemplateRepo = 'https://github.com/inkeep/agents/create-agents-templ
|
|
|
33
34
|
const projectTemplateRepo = 'https://github.com/inkeep/agents/agents-cookbook/template-projects';
|
|
34
35
|
const execAsync = promisify(exec);
|
|
35
36
|
const agentsApiPort = '3002';
|
|
37
|
+
function getCliVersion() {
|
|
38
|
+
try {
|
|
39
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
40
|
+
const pkgJson = JSON.parse(readFileSync(path.join(__dirname, '..', 'package.json'), 'utf-8'));
|
|
41
|
+
return pkgJson.version;
|
|
42
|
+
}
|
|
43
|
+
catch {
|
|
44
|
+
return '';
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
export async function syncTemplateDependencies(templatePath) {
|
|
48
|
+
const cliVersion = getCliVersion();
|
|
49
|
+
if (!cliVersion)
|
|
50
|
+
return;
|
|
51
|
+
const packageJsonPaths = await findPackageJsonFiles(templatePath);
|
|
52
|
+
await Promise.all(packageJsonPaths.map(async (pkgPath) => {
|
|
53
|
+
const pkg = await fs.readJson(pkgPath);
|
|
54
|
+
for (const depType of ['dependencies', 'devDependencies']) {
|
|
55
|
+
const deps = pkg[depType];
|
|
56
|
+
if (!deps)
|
|
57
|
+
continue;
|
|
58
|
+
for (const name of Object.keys(deps)) {
|
|
59
|
+
if (name.startsWith('@inkeep/') && name !== '@inkeep/agents-ui') {
|
|
60
|
+
deps[name] = `^${cliVersion}`;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
await fs.writeJson(pkgPath, pkg, { spaces: 2 });
|
|
65
|
+
}));
|
|
66
|
+
}
|
|
67
|
+
async function findPackageJsonFiles(dir) {
|
|
68
|
+
const results = [];
|
|
69
|
+
const rootPkg = path.join(dir, 'package.json');
|
|
70
|
+
if (await fs.pathExists(rootPkg)) {
|
|
71
|
+
results.push(rootPkg);
|
|
72
|
+
}
|
|
73
|
+
const entries = await fs.readdir(dir, { withFileTypes: true });
|
|
74
|
+
for (const entry of entries) {
|
|
75
|
+
if (!entry.isDirectory() || entry.name === 'node_modules' || entry.name.startsWith('.')) {
|
|
76
|
+
continue;
|
|
77
|
+
}
|
|
78
|
+
const nested = await findPackageJsonFiles(path.join(dir, entry.name));
|
|
79
|
+
results.push(...nested);
|
|
80
|
+
}
|
|
81
|
+
return results;
|
|
82
|
+
}
|
|
36
83
|
export const defaultGoogleModelConfigurations = {
|
|
37
84
|
base: {
|
|
38
85
|
model: GOOGLE_MODELS.GEMINI_2_5_FLASH,
|
|
@@ -314,6 +361,7 @@ export const createAgents = async (args = {}) => {
|
|
|
314
361
|
localPrefix: localAgentsPrefix,
|
|
315
362
|
});
|
|
316
363
|
process.chdir(directoryPath);
|
|
364
|
+
await syncTemplateDependencies('.');
|
|
317
365
|
const config = {
|
|
318
366
|
dirName,
|
|
319
367
|
tenantId,
|
|
@@ -397,8 +445,8 @@ export const createAgents = async (args = {}) => {
|
|
|
397
445
|
` pnpm setup-dev\n` +
|
|
398
446
|
` pnpm dev\n\n` +
|
|
399
447
|
`${color.yellow('2. Explore:')}\n` +
|
|
400
|
-
` • Dashboard: http://
|
|
401
|
-
` • Agents API: http://
|
|
448
|
+
` • Dashboard: http://localhost:3000\n` +
|
|
449
|
+
` • Agents API: http://localhost:3002\n\n` +
|
|
402
450
|
`${color.yellow('3. Customize:')}\n` +
|
|
403
451
|
` • Edit your agents in src/projects/\n` +
|
|
404
452
|
` • Use 'inkeep push' to apply`, 'Next steps 🚀');
|
|
@@ -413,80 +461,29 @@ async function createWorkspaceStructure() {
|
|
|
413
461
|
await fs.ensureDir(`src`);
|
|
414
462
|
}
|
|
415
463
|
async function createEnvironmentFiles(config) {
|
|
416
|
-
|
|
417
|
-
const jwtSigningSecret = crypto.randomBytes(32).toString('hex');
|
|
418
|
-
const betterAuthSecret = crypto.randomBytes(32).toString('hex');
|
|
419
|
-
const manageUiPassword = crypto.randomBytes(6).toString('base64url');
|
|
420
|
-
// Generate RSA key pair for temporary JWT tokens
|
|
421
|
-
let tempJwtPrivateKey = '';
|
|
422
|
-
let tempJwtPublicKey = '';
|
|
464
|
+
let envExampleContent;
|
|
423
465
|
try {
|
|
424
|
-
|
|
425
|
-
const { privateKey, publicKey } = generateKeyPairSync('rsa', {
|
|
426
|
-
modulusLength: 2048,
|
|
427
|
-
publicKeyEncoding: { type: 'spki', format: 'pem' },
|
|
428
|
-
privateKeyEncoding: { type: 'pkcs8', format: 'pem' },
|
|
429
|
-
});
|
|
430
|
-
tempJwtPrivateKey = Buffer.from(privateKey).toString('base64');
|
|
431
|
-
tempJwtPublicKey = Buffer.from(publicKey).toString('base64');
|
|
466
|
+
envExampleContent = await fs.readFile('.env.example', 'utf-8');
|
|
432
467
|
}
|
|
433
468
|
catch {
|
|
434
|
-
|
|
435
|
-
console.warn('You can manually generate keys later with: pnpm run generate-jwt-keys');
|
|
469
|
+
throw new Error('Could not read .env.example from the template. The template may be corrupted — try running the command again.');
|
|
436
470
|
}
|
|
437
|
-
const
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
# Internal URLs (server-side, Docker internal networking)
|
|
454
|
-
# Using 127.0.0.1 instead of localhost to avoid IPv6/IPv4 resolution issues
|
|
455
|
-
INKEEP_AGENTS_API_URL="http://127.0.0.1:3002"
|
|
456
|
-
|
|
457
|
-
# Public URLs (client-side, browser accessible)
|
|
458
|
-
PUBLIC_INKEEP_AGENTS_API_URL="http://127.0.0.1:3002"
|
|
459
|
-
|
|
460
|
-
# SigNoz Configuration
|
|
461
|
-
SIGNOZ_URL=your-signoz-url-here
|
|
462
|
-
SIGNOZ_API_KEY=your-signoz-api-key-here
|
|
463
|
-
|
|
464
|
-
# OTEL Configuration
|
|
465
|
-
OTEL_EXPORTER_OTLP_TRACES_ENDPOINT=https://ingest.us.signoz.cloud:443/v1/traces
|
|
466
|
-
OTEL_EXPORTER_OTLP_TRACES_HEADERS="signoz-ingestion-key=<your-ingestion-key>"
|
|
467
|
-
|
|
468
|
-
# Nango Configuration
|
|
469
|
-
NANGO_SECRET_KEY=
|
|
470
|
-
|
|
471
|
-
# JWT Signing Secret
|
|
472
|
-
INKEEP_AGENTS_JWT_SIGNING_SECRET=${jwtSigningSecret}
|
|
473
|
-
|
|
474
|
-
# Temporary JWT Keys for Playground
|
|
475
|
-
INKEEP_AGENTS_TEMP_JWT_PRIVATE_KEY=${tempJwtPrivateKey}
|
|
476
|
-
INKEEP_AGENTS_TEMP_JWT_PUBLIC_KEY=${tempJwtPublicKey}
|
|
477
|
-
|
|
478
|
-
# initial project information
|
|
479
|
-
DEFAULT_PROJECT_ID=${config.projectId}
|
|
480
|
-
|
|
481
|
-
# Auth Configuration
|
|
482
|
-
INKEEP_AGENTS_MANAGE_UI_USERNAME=admin@example.com
|
|
483
|
-
INKEEP_AGENTS_MANAGE_UI_PASSWORD=${manageUiPassword}
|
|
484
|
-
BETTER_AUTH_SECRET=${betterAuthSecret}
|
|
485
|
-
SPICEDB_ENDPOINT=localhost:50051
|
|
486
|
-
SPICEDB_PRESHARED_KEY=dev-secret-key
|
|
487
|
-
|
|
488
|
-
`;
|
|
489
|
-
await fs.writeFile('.env', envContent);
|
|
471
|
+
const lines = envExampleContent.split('\n');
|
|
472
|
+
const injections = {
|
|
473
|
+
ANTHROPIC_API_KEY: config.anthropicKey || '',
|
|
474
|
+
OPENAI_API_KEY: config.openAiKey || '',
|
|
475
|
+
GOOGLE_GENERATIVE_AI_API_KEY: config.googleKey || '',
|
|
476
|
+
AZURE_API_KEY: config.azureKey || '',
|
|
477
|
+
DEFAULT_PROJECT_ID: config.projectId,
|
|
478
|
+
};
|
|
479
|
+
for (let i = 0; i < lines.length; i++) {
|
|
480
|
+
for (const [varName, value] of Object.entries(injections)) {
|
|
481
|
+
if (lines[i].startsWith(`${varName}=`)) {
|
|
482
|
+
lines[i] = `${varName}=${value}`;
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
await fs.writeFile('.env', lines.join('\n'));
|
|
490
487
|
}
|
|
491
488
|
async function createInkeepConfig(config) {
|
|
492
489
|
const inkeepConfig = `import { defineConfig } from '@inkeep/agents-cli/config';
|
|
@@ -494,8 +491,7 @@ async function createInkeepConfig(config) {
|
|
|
494
491
|
const config = defineConfig({
|
|
495
492
|
tenantId: "${config.tenantId}",
|
|
496
493
|
agentsApi: {
|
|
497
|
-
|
|
498
|
-
url: 'http://127.0.0.1:3002',
|
|
494
|
+
url: 'http://localhost:3002',
|
|
499
495
|
},
|
|
500
496
|
});
|
|
501
497
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@inkeep/create-agents",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.53.0",
|
|
4
4
|
"description": "Create an Inkeep Agent Framework project",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -33,7 +33,7 @@
|
|
|
33
33
|
"degit": "^2.8.4",
|
|
34
34
|
"fs-extra": "^11.0.0",
|
|
35
35
|
"picocolors": "^1.0.0",
|
|
36
|
-
"@inkeep/agents-core": "0.
|
|
36
|
+
"@inkeep/agents-core": "0.53.0"
|
|
37
37
|
},
|
|
38
38
|
"devDependencies": {
|
|
39
39
|
"@types/degit": "^2.8.6",
|
|
@@ -67,8 +67,8 @@
|
|
|
67
67
|
"test": "vitest --run src/__tests__ --exclude src/__tests__/e2e/** --passWithNoTests",
|
|
68
68
|
"test:watch": "vitest src/__tests__ --exclude src/__tests__/e2e/**",
|
|
69
69
|
"test:unit": "vitest --run src/__tests__ --exclude src/__tests__/e2e/**",
|
|
70
|
-
"test:e2e": "vitest --run src/__tests__/e2e",
|
|
71
|
-
"test:e2e:watch": "vitest src/__tests__/e2e",
|
|
70
|
+
"test:e2e": "turbo run build --filter=@inkeep/create-agents --filter=@inkeep/agents-api --filter=@inkeep/agents-cli --filter=@inkeep/agents-manage-ui && vitest --run src/__tests__/e2e",
|
|
71
|
+
"test:e2e:watch": "turbo run build --filter=@inkeep/create-agents --filter=@inkeep/agents-api --filter=@inkeep/agents-cli --filter=@inkeep/agents-manage-ui && vitest src/__tests__/e2e",
|
|
72
72
|
"test:all": "vitest --run --passWithNoTests",
|
|
73
73
|
"typecheck": "tsc --noEmit"
|
|
74
74
|
}
|