@inkeep/create-agents 0.51.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.
@@ -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="http:\/\/127\.0\.0\.1:3002"/,
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: manageApiUrl },
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: manageApiUrl },
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
- const manageUiRoot = path.dirname(manageUiPkgJson);
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
- throw new Error(`Dashboard standalone server not found at ${serverEntry}`);
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
@@ -1,3 +1,4 @@
1
+ export declare function syncTemplateDependencies(templatePath: string): Promise<void>;
1
2
  export declare const defaultGoogleModelConfigurations: {
2
3
  base: {
3
4
  model: "google/gemini-2.5-flash";
package/dist/utils.js CHANGED
@@ -1,7 +1,8 @@
1
1
  import { exec } from 'node:child_process';
2
- import crypto from 'node:crypto';
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://127.0.0.1:3000\n` +
401
- ` • Agents API: http://127.0.0.1:3002\n\n` +
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
- // Convert to forward slashes for cross-platform SQLite URI compatibility
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
- const { generateKeyPairSync } = await import('node:crypto');
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
- console.warn('Warning: Failed to generate JWT keys. Playground may not work.');
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 envContent = `# Environment
438
- ENVIRONMENT=development
439
-
440
- # Database Configuration (Split Database Setup)
441
- # Management entities database uses DoltgreSQL on port 5432 for version control features
442
- INKEEP_AGENTS_MANAGE_DATABASE_URL=postgresql://appuser:password@localhost:5432/inkeep_agents
443
- # Runtime entities database uses PostgreSQL on port 5433 for runtime operations
444
- INKEEP_AGENTS_RUN_DATABASE_URL=postgresql://appuser:password@localhost:5433/inkeep_agents
445
-
446
- # AI Provider Keys
447
- ANTHROPIC_API_KEY=${config.anthropicKey || 'your-anthropic-key-here'}
448
- OPENAI_API_KEY=${config.openAiKey || 'your-openai-key-here'}
449
- GOOGLE_GENERATIVE_AI_API_KEY=${config.googleKey || 'your-google-key-here'}
450
- AZURE_API_KEY=${config.azureKey || 'your-azure-key-here'}
451
-
452
- # Inkeep API URLs
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
- // Using 127.0.0.1 instead of localhost to avoid IPv6/IPv4 resolution issues
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.51.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.51.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
  }