@inkeep/create-agents 0.53.0 → 0.53.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -40,23 +40,19 @@ Ensure Docker Desktop (or Docker daemon) is running before running the setup scr
40
40
  pnpm setup-dev
41
41
  ```
42
42
 
43
- Or if you are using a cloud database, you can skip the local Docker database startup by running:
44
-
45
- ```bash
46
- pnpm setup-dev:cloud
47
- ```
48
-
49
- Make sure your `INKEEP_AGENTS_MANAGE_DATABASE_URL` and `INKEEP_AGENTS_RUN_DATABASE_URL` environment variables are configured in `.env` for your cloud databases.
50
-
51
43
  ### Step 3: Launch the dev environment
52
44
 
53
45
  ```bash
54
46
  pnpm dev
55
47
  ```
56
48
 
57
- The Visual Builder will auto-open at http://localhost:3000. You'll be signed in automatically.
49
+ The Visual Builder will auto-open at http://localhost:3000.
50
+
51
+ ### Step 4: Login to the Visual Builder
52
+
53
+ Use the credentials from your root `.env` file (`INKEEP_AGENTS_MANAGE_UI_USERNAME` and `INKEEP_AGENTS_MANAGE_UI_PASSWORD`) to log in when the Visual Builder opens.
58
54
 
59
- ### Step 4: Chat with your agent
55
+ ### Step 5: Chat with your agent
60
56
 
61
57
  Navigate to the **Activities Planner** agent at http://localhost:3000 and ask about fun activities at a location of your choice:
62
58
 
@@ -229,8 +229,12 @@ describe('create-agents quickstart e2e', () => {
229
229
  console.warn('Login API test failed (non-fatal):', loginTestError);
230
230
  }
231
231
  // --- Dashboard Lap ---
232
+ // Start the dashboard with ENVIRONMENT=development so the proxy middleware
233
+ // auto-logs in using the bypass secret — this mirrors the real quickstart
234
+ // experience where users never see a login form.
232
235
  console.log('Starting dashboard lap');
233
236
  const dashboardProcess = await startDashboardServer(projectDir, {
237
+ ENVIRONMENT: 'development',
234
238
  INKEEP_AGENTS_API_URL: dashboardApiUrl,
235
239
  NEXT_PUBLIC_API_URL: dashboardApiUrl,
236
240
  PUBLIC_INKEEP_AGENTS_API_URL: dashboardApiUrl,
@@ -241,21 +245,19 @@ describe('create-agents quickstart e2e', () => {
241
245
  const browser = await chromium.launch({ headless: true });
242
246
  try {
243
247
  const page = await browser.newPage();
244
- console.log('Navigating to login page');
245
- await page.goto('http://localhost:3000/login', {
248
+ // Navigate to root — the proxy middleware auto-logs in via the bypass
249
+ // secret and sets a session cookie, so no manual login is needed.
250
+ console.log('Navigating to dashboard (auto-login via proxy)');
251
+ await page.goto('http://localhost:3000/', {
246
252
  waitUntil: 'networkidle',
247
- timeout: 15000,
253
+ timeout: 30000,
248
254
  });
249
- console.log('Filling login form');
250
- await page.fill('input[type="email"]', 'admin@example.com');
251
- await page.fill('input[type="password"]', 'adminADMIN!@12');
252
- await page.click('button[type="submit"]');
253
255
  console.log('Waiting for redirect to projects page');
254
256
  await page.waitForURL('**/default/projects**', {
255
- timeout: 15000,
257
+ timeout: 30000,
256
258
  waitUntil: 'domcontentloaded',
257
259
  });
258
- console.log('Redirected to projects page');
260
+ console.log('Auto-login succeeded — redirected to projects page');
259
261
  console.log('Clicking activities-planner project');
260
262
  // Use force:true because card uses a linkoverlay pattern that intercepts pointer events
261
263
  await page.click(`a[href*="${projectId}"]`, { timeout: 15000, force: true });
@@ -137,15 +137,32 @@ export async function verifyDirectoryStructure(baseDir, expectedPaths) {
137
137
  }
138
138
  }
139
139
  }
140
+ /**
141
+ * Recursively find all package.json files in a directory, skipping node_modules and dot-dirs.
142
+ * Mirrors the discovery logic in syncTemplateDependencies so E2E tests cover the same files.
143
+ */
144
+ async function findPackageJsonFiles(dir) {
145
+ const results = [];
146
+ const rootPkg = path.join(dir, 'package.json');
147
+ if (await fs.pathExists(rootPkg)) {
148
+ results.push(rootPkg);
149
+ }
150
+ const entries = await fs.readdir(dir, { withFileTypes: true });
151
+ for (const entry of entries) {
152
+ if (!entry.isDirectory() || entry.name === 'node_modules' || entry.name.startsWith('.')) {
153
+ continue;
154
+ }
155
+ const nested = await findPackageJsonFiles(path.join(dir, entry.name));
156
+ results.push(...nested);
157
+ }
158
+ return results;
159
+ }
140
160
  /**
141
161
  * Link local monorepo packages to the created project
142
162
  * This replaces published @inkeep packages with local versions for testing
143
163
  */
144
164
  export async function linkLocalPackages(projectDir, monorepoRoot) {
145
- const packageJsonPaths = [
146
- path.join(projectDir, 'package.json'),
147
- path.join(projectDir, 'apps/agents-api/package.json'),
148
- ];
165
+ const packageJsonPaths = await findPackageJsonFiles(projectDir);
149
166
  const packageJsons = {};
150
167
  for (const packageJsonPath of packageJsonPaths) {
151
168
  packageJsons[packageJsonPath] = await fs.readJson(packageJsonPath);
@@ -376,13 +376,19 @@ describe('createAgents - Template and Project ID Logic', () => {
376
376
  });
377
377
  });
378
378
  describe('Environment file generation', () => {
379
- it('should contain INKEEP_AGENTS_MANAGE_API_BYPASS_SECRET from .env.example', async () => {
379
+ it('should generate a unique INKEEP_AGENTS_MANAGE_API_BYPASS_SECRET', async () => {
380
380
  await createAgents({
381
381
  dirName: 'test-dir',
382
382
  openAiKey: 'test-openai-key',
383
383
  anthropicKey: 'test-anthropic-key',
384
384
  });
385
- expect(fs.writeFile).toHaveBeenCalledWith('.env', expect.stringContaining('INKEEP_AGENTS_MANAGE_API_BYPASS_SECRET=test-bypass-secret-for-ci'));
385
+ const envWriteCall = vi.mocked(fs.writeFile).mock.calls.find((call) => call[0] === '.env');
386
+ expect(envWriteCall).toBeDefined();
387
+ const envContent = envWriteCall?.[1];
388
+ const match = envContent.match(/INKEEP_AGENTS_MANAGE_API_BYPASS_SECRET=(.+)/);
389
+ expect(match).not.toBeNull();
390
+ expect(match?.[1]).not.toBe('test-bypass-secret-for-ci');
391
+ expect(match?.[1]).toHaveLength(64);
386
392
  });
387
393
  it('should inject CLI-prompted API keys into the .env', async () => {
388
394
  await createAgents({
@@ -494,6 +500,10 @@ describe('syncTemplateDependencies', () => {
494
500
  }
495
501
  beforeEach(() => {
496
502
  vi.clearAllMocks();
503
+ vi.stubGlobal('fetch', vi.fn().mockRejectedValue(new Error('no network in tests')));
504
+ });
505
+ afterEach(() => {
506
+ vi.unstubAllGlobals();
497
507
  });
498
508
  it('should update @inkeep/* dependencies to match CLI version', async () => {
499
509
  const mockPkg = {
@@ -588,21 +598,127 @@ describe('syncTemplateDependencies', () => {
588
598
  },
589
599
  }), { spaces: 2 });
590
600
  });
591
- it('should not update excluded packages like @inkeep/agents-ui', async () => {
601
+ it('should resolve @inkeep/agents-ui version from npm registry', async () => {
602
+ const mockFetch = vi.fn().mockResolvedValue({
603
+ ok: true,
604
+ json: () => Promise.resolve({ version: '0.16.0' }),
605
+ });
606
+ vi.stubGlobal('fetch', mockFetch);
592
607
  const mockPkg = {
593
608
  name: 'test-project',
594
609
  dependencies: {
595
610
  '@inkeep/agents-core': '^0.50.3',
596
- '@inkeep/agents-ui': '^0.50.3',
611
+ '@inkeep/agents-ui': '^0.15.12',
597
612
  '@inkeep/agents-sdk': '^0.50.3',
598
613
  },
599
614
  };
600
615
  setupFlatTemplate(mockPkg);
601
616
  await syncTemplateDependencies('/test/path');
617
+ expect(mockFetch).toHaveBeenCalledWith('https://registry.npmjs.org/@inkeep/agents-ui/latest', expect.objectContaining({ signal: expect.any(AbortSignal) }));
618
+ expect(mockFetch).toHaveBeenCalledTimes(1);
619
+ expect(fs.writeJson).toHaveBeenCalledWith('/test/path/package.json', expect.objectContaining({
620
+ dependencies: {
621
+ '@inkeep/agents-core': '^1.2.3',
622
+ '@inkeep/agents-ui': '^0.16.0',
623
+ '@inkeep/agents-sdk': '^1.2.3',
624
+ },
625
+ }), { spaces: 2 });
626
+ });
627
+ it('should leave divergent package specifier unchanged when registry fetch fails', async () => {
628
+ const mockFetch = vi.fn().mockRejectedValue(new Error('network error'));
629
+ vi.stubGlobal('fetch', mockFetch);
630
+ const mockPkg = {
631
+ name: 'test-project',
632
+ dependencies: {
633
+ '@inkeep/agents-core': '^0.50.3',
634
+ '@inkeep/agents-ui': '^0.15.12',
635
+ },
636
+ };
637
+ setupFlatTemplate(mockPkg);
638
+ await syncTemplateDependencies('/test/path');
639
+ expect(fs.writeJson).toHaveBeenCalledWith('/test/path/package.json', expect.objectContaining({
640
+ dependencies: {
641
+ '@inkeep/agents-core': '^1.2.3',
642
+ '@inkeep/agents-ui': '^0.15.12',
643
+ },
644
+ }), { spaces: 2 });
645
+ });
646
+ it('should leave divergent package specifier unchanged when registry returns non-ok', async () => {
647
+ const mockFetch = vi.fn().mockResolvedValue({
648
+ ok: false,
649
+ status: 404,
650
+ });
651
+ vi.stubGlobal('fetch', mockFetch);
652
+ const mockPkg = {
653
+ name: 'test-project',
654
+ dependencies: {
655
+ '@inkeep/agents-ui': '^0.15.12',
656
+ },
657
+ };
658
+ setupFlatTemplate(mockPkg);
659
+ await syncTemplateDependencies('/test/path');
660
+ expect(fs.writeJson).toHaveBeenCalledWith('/test/path/package.json', expect.objectContaining({
661
+ dependencies: {
662
+ '@inkeep/agents-ui': '^0.15.12',
663
+ },
664
+ }), { spaces: 2 });
665
+ });
666
+ it('should resolve divergent package version in devDependencies from npm registry', async () => {
667
+ const mockFetch = vi.fn().mockResolvedValue({
668
+ ok: true,
669
+ json: () => Promise.resolve({ version: '0.16.0' }),
670
+ });
671
+ vi.stubGlobal('fetch', mockFetch);
672
+ const mockPkg = {
673
+ name: 'test-project',
674
+ dependencies: { '@inkeep/agents-core': '^0.50.3' },
675
+ devDependencies: { '@inkeep/agents-ui': '^0.15.12' },
676
+ };
677
+ setupFlatTemplate(mockPkg);
678
+ await syncTemplateDependencies('/test/path');
679
+ expect(fs.writeJson).toHaveBeenCalledWith('/test/path/package.json', expect.objectContaining({
680
+ dependencies: { '@inkeep/agents-core': '^1.2.3' },
681
+ devDependencies: { '@inkeep/agents-ui': '^0.16.0' },
682
+ }), { spaces: 2 });
683
+ });
684
+ it('should only fetch once when same divergent package appears in multiple files', async () => {
685
+ const mockFetch = vi.fn().mockResolvedValue({
686
+ ok: true,
687
+ json: () => Promise.resolve({ version: '0.16.0' }),
688
+ });
689
+ vi.stubGlobal('fetch', mockFetch);
690
+ const nodeFs = await import('node:fs');
691
+ vi.mocked(nodeFs.readFileSync).mockReturnValue(JSON.stringify({ version: '1.2.3' }));
692
+ const rootPkg = {
693
+ name: 'monorepo',
694
+ dependencies: { '@inkeep/agents-core': '^0.50.3', '@inkeep/agents-ui': '^0.15.12' },
695
+ };
696
+ const nestedPkg = {
697
+ name: 'nested-app',
698
+ dependencies: { '@inkeep/agents-ui': '^0.15.12', '@inkeep/agents-sdk': '^0.50.3' },
699
+ };
700
+ vi.mocked(fs.pathExists).mockImplementation(async (p) => {
701
+ const s = String(p);
702
+ return s === '/test/path/package.json' || s === '/test/path/apps/api/package.json';
703
+ });
704
+ vi.mocked(fs.readdir)
705
+ .mockResolvedValueOnce([mockDirent('apps', true)])
706
+ .mockResolvedValueOnce([mockDirent('api', true)])
707
+ .mockResolvedValueOnce([]);
708
+ vi.mocked(fs.readJson).mockResolvedValueOnce(rootPkg).mockResolvedValueOnce(nestedPkg);
709
+ vi.mocked(fs.writeJson).mockResolvedValue(undefined);
710
+ await syncTemplateDependencies('/test/path');
711
+ expect(mockFetch).toHaveBeenCalledTimes(1);
712
+ expect(fs.writeJson).toHaveBeenCalledTimes(2);
602
713
  expect(fs.writeJson).toHaveBeenCalledWith('/test/path/package.json', expect.objectContaining({
603
714
  dependencies: {
604
715
  '@inkeep/agents-core': '^1.2.3',
605
- '@inkeep/agents-ui': '^0.50.3',
716
+ '@inkeep/agents-ui': '^0.16.0',
717
+ },
718
+ }), { spaces: 2 });
719
+ expect(fs.writeJson).toHaveBeenCalledWith('/test/path/apps/api/package.json', expect.objectContaining({
720
+ dependencies: {
721
+ '@inkeep/agents-ui': '^0.16.0',
606
722
  '@inkeep/agents-sdk': '^1.2.3',
607
723
  },
608
724
  }), { spaces: 2 });
@@ -632,23 +748,22 @@ describe('syncTemplateDependencies', () => {
632
748
  name: 'nested-app',
633
749
  dependencies: { '@inkeep/agents-sdk': '^0.50.3', express: '^4.0.0' },
634
750
  };
635
- vi.mocked(fs.pathExists).mockResolvedValue(true);
751
+ vi.mocked(fs.pathExists).mockImplementation(async (p) => {
752
+ const s = String(p);
753
+ return s === '/test/path/package.json' || s === '/test/path/apps/api/package.json';
754
+ });
636
755
  vi.mocked(fs.readdir)
637
756
  .mockResolvedValueOnce([mockDirent('apps', true), mockDirent('README.md', false)])
638
757
  .mockResolvedValueOnce([mockDirent('api', true)])
639
758
  .mockResolvedValueOnce([]);
640
- vi.mocked(fs.readJson)
641
- .mockResolvedValueOnce(rootPkg)
642
- .mockResolvedValueOnce(nestedPkg)
643
- .mockResolvedValueOnce(nestedPkg);
759
+ vi.mocked(fs.readJson).mockResolvedValueOnce(rootPkg).mockResolvedValueOnce(nestedPkg);
644
760
  vi.mocked(fs.writeJson).mockResolvedValue(undefined);
645
761
  await syncTemplateDependencies('/test/path');
646
- expect(fs.writeJson).toHaveBeenCalledTimes(3);
762
+ expect(fs.writeJson).toHaveBeenCalledTimes(2);
647
763
  expect(fs.writeJson).toHaveBeenCalledWith('/test/path/package.json', expect.objectContaining({
648
764
  dependencies: { '@inkeep/agents-core': '^1.2.3' },
649
765
  }), { 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({
766
+ expect(fs.writeJson).toHaveBeenCalledWith('/test/path/apps/api/package.json', expect.objectContaining({
652
767
  dependencies: { '@inkeep/agents-sdk': '^1.2.3', express: '^4.0.0' },
653
768
  }), { spaces: 2 });
654
769
  });
package/dist/utils.js CHANGED
@@ -1,4 +1,5 @@
1
1
  import { exec } from 'node:child_process';
2
+ import crypto from 'node:crypto';
2
3
  import { readFileSync } from 'node:fs';
3
4
  import os from 'node:os';
4
5
  import path from 'node:path';
@@ -34,6 +35,7 @@ const agentsTemplateRepo = 'https://github.com/inkeep/agents/create-agents-templ
34
35
  const projectTemplateRepo = 'https://github.com/inkeep/agents/agents-cookbook/template-projects';
35
36
  const execAsync = promisify(exec);
36
37
  const agentsApiPort = '3002';
38
+ const DIVERGENT_PACKAGES = new Set(['@inkeep/agents-ui']);
37
39
  function getCliVersion() {
38
40
  try {
39
41
  const __dirname = path.dirname(fileURLToPath(import.meta.url));
@@ -44,19 +46,66 @@ function getCliVersion() {
44
46
  return '';
45
47
  }
46
48
  }
49
+ async function fetchLatestVersion(packageName) {
50
+ const controller = new AbortController();
51
+ const timeoutId = setTimeout(() => controller.abort(), 10000);
52
+ try {
53
+ const response = await fetch(`https://registry.npmjs.org/${packageName}/latest`, {
54
+ signal: controller.signal,
55
+ });
56
+ if (!response.ok)
57
+ return null;
58
+ const data = (await response.json());
59
+ return data.version ?? null;
60
+ }
61
+ catch {
62
+ return null;
63
+ }
64
+ finally {
65
+ clearTimeout(timeoutId);
66
+ }
67
+ }
47
68
  export async function syncTemplateDependencies(templatePath) {
48
69
  const cliVersion = getCliVersion();
49
70
  if (!cliVersion)
50
71
  return;
51
72
  const packageJsonPaths = await findPackageJsonFiles(templatePath);
52
- await Promise.all(packageJsonPaths.map(async (pkgPath) => {
73
+ const divergentVersions = new Map();
74
+ const allDeps = new Set();
75
+ const pkgDataList = [];
76
+ for (const pkgPath of packageJsonPaths) {
53
77
  const pkg = await fs.readJson(pkgPath);
78
+ pkgDataList.push({ pkgPath, pkg });
54
79
  for (const depType of ['dependencies', 'devDependencies']) {
55
80
  const deps = pkg[depType];
56
81
  if (!deps)
57
82
  continue;
58
83
  for (const name of Object.keys(deps)) {
59
- if (name.startsWith('@inkeep/') && name !== '@inkeep/agents-ui') {
84
+ if (name.startsWith('@inkeep/') && DIVERGENT_PACKAGES.has(name)) {
85
+ allDeps.add(name);
86
+ }
87
+ }
88
+ }
89
+ }
90
+ await Promise.all([...allDeps].map(async (name) => {
91
+ const version = await fetchLatestVersion(name);
92
+ if (version)
93
+ divergentVersions.set(name, version);
94
+ }));
95
+ await Promise.all(pkgDataList.map(async ({ pkgPath, pkg }) => {
96
+ for (const depType of ['dependencies', 'devDependencies']) {
97
+ const deps = pkg[depType];
98
+ if (!deps)
99
+ continue;
100
+ for (const name of Object.keys(deps)) {
101
+ if (!name.startsWith('@inkeep/'))
102
+ continue;
103
+ if (DIVERGENT_PACKAGES.has(name)) {
104
+ const resolved = divergentVersions.get(name);
105
+ if (resolved)
106
+ deps[name] = `^${resolved}`;
107
+ }
108
+ else {
60
109
  deps[name] = `^${cliVersion}`;
61
110
  }
62
111
  }
@@ -461,6 +510,7 @@ async function createWorkspaceStructure() {
461
510
  await fs.ensureDir(`src`);
462
511
  }
463
512
  async function createEnvironmentFiles(config) {
513
+ const bypassSecret = crypto.randomBytes(32).toString('hex');
464
514
  let envExampleContent;
465
515
  try {
466
516
  envExampleContent = await fs.readFile('.env.example', 'utf-8');
@@ -475,6 +525,7 @@ async function createEnvironmentFiles(config) {
475
525
  GOOGLE_GENERATIVE_AI_API_KEY: config.googleKey || '',
476
526
  AZURE_API_KEY: config.azureKey || '',
477
527
  DEFAULT_PROJECT_ID: config.projectId,
528
+ INKEEP_AGENTS_MANAGE_API_BYPASS_SECRET: bypassSecret,
478
529
  };
479
530
  for (let i = 0; i < lines.length; i++) {
480
531
  for (const [varName, value] of Object.entries(injections)) {
@@ -491,8 +542,11 @@ async function createInkeepConfig(config) {
491
542
  const config = defineConfig({
492
543
  tenantId: "${config.tenantId}",
493
544
  agentsApi: {
494
- url: 'http://localhost:3002',
545
+ // Using 127.0.0.1 instead of localhost to avoid IPv6/IPv4 resolution issues
546
+ url: 'http://127.0.0.1:3002',
547
+ apiKey: process.env.INKEEP_AGENTS_MANAGE_API_BYPASS_SECRET,
495
548
  },
549
+ manageUiUrl: 'http://localhost:3000',
496
550
  });
497
551
 
498
552
  export default config;`;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@inkeep/create-agents",
3
- "version": "0.53.0",
3
+ "version": "0.53.2",
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.53.0"
36
+ "@inkeep/agents-core": "0.53.2"
37
37
  },
38
38
  "devDependencies": {
39
39
  "@types/degit": "^2.8.6",