@agents-at-scale/ark 0.1.35-rc.1 → 0.1.35-rc1

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.
Files changed (122) hide show
  1. package/dist/arkServices.d.ts +4 -12
  2. package/dist/arkServices.js +19 -34
  3. package/dist/arkServices.spec.d.ts +1 -0
  4. package/dist/arkServices.spec.js +24 -0
  5. package/dist/commands/agents/index.d.ts +2 -1
  6. package/dist/commands/agents/index.js +2 -7
  7. package/dist/commands/agents/index.spec.d.ts +1 -0
  8. package/dist/commands/agents/index.spec.js +67 -0
  9. package/dist/commands/chat/index.d.ts +2 -1
  10. package/dist/commands/chat/index.js +5 -21
  11. package/dist/commands/cluster/get.spec.d.ts +1 -0
  12. package/dist/commands/cluster/get.spec.js +92 -0
  13. package/dist/commands/cluster/index.d.ts +2 -1
  14. package/dist/commands/cluster/index.js +1 -1
  15. package/dist/commands/cluster/index.spec.d.ts +1 -0
  16. package/dist/commands/cluster/index.spec.js +24 -0
  17. package/dist/commands/completion/index.d.ts +2 -1
  18. package/dist/commands/completion/index.js +1 -1
  19. package/dist/commands/completion/index.spec.d.ts +1 -0
  20. package/dist/commands/completion/index.spec.js +34 -0
  21. package/dist/commands/config/index.d.ts +2 -1
  22. package/dist/commands/config/index.js +2 -2
  23. package/dist/commands/config/index.spec.d.ts +1 -0
  24. package/dist/commands/config/index.spec.js +78 -0
  25. package/dist/commands/dashboard/index.d.ts +2 -1
  26. package/dist/commands/dashboard/index.js +1 -1
  27. package/dist/commands/dev/index.d.ts +2 -1
  28. package/dist/commands/dev/index.js +1 -1
  29. package/dist/commands/dev/tool-generate.spec.d.ts +1 -0
  30. package/dist/commands/dev/tool-generate.spec.js +163 -0
  31. package/dist/commands/dev/tool.spec.d.ts +1 -0
  32. package/dist/commands/dev/tool.spec.js +48 -0
  33. package/dist/commands/generate/generators/project.js +22 -41
  34. package/dist/commands/generate/index.d.ts +2 -1
  35. package/dist/commands/generate/index.js +1 -1
  36. package/dist/commands/install/index.d.ts +4 -2
  37. package/dist/commands/install/index.js +215 -78
  38. package/dist/commands/install/index.spec.d.ts +1 -0
  39. package/dist/commands/install/index.spec.js +135 -0
  40. package/dist/commands/models/create.spec.d.ts +1 -0
  41. package/dist/commands/models/create.spec.js +125 -0
  42. package/dist/commands/models/index.d.ts +2 -1
  43. package/dist/commands/models/index.js +2 -7
  44. package/dist/commands/models/index.spec.d.ts +1 -0
  45. package/dist/commands/models/index.spec.js +76 -0
  46. package/dist/commands/routes/index.d.ts +2 -1
  47. package/dist/commands/routes/index.js +1 -9
  48. package/dist/commands/status/index.d.ts +3 -2
  49. package/dist/commands/status/index.js +210 -11
  50. package/dist/commands/targets/index.d.ts +2 -1
  51. package/dist/commands/targets/index.js +1 -1
  52. package/dist/commands/targets/index.spec.d.ts +1 -0
  53. package/dist/commands/targets/index.spec.js +105 -0
  54. package/dist/commands/teams/index.d.ts +2 -1
  55. package/dist/commands/teams/index.js +2 -7
  56. package/dist/commands/teams/index.spec.d.ts +1 -0
  57. package/dist/commands/teams/index.spec.js +70 -0
  58. package/dist/commands/tools/index.d.ts +2 -1
  59. package/dist/commands/tools/index.js +2 -7
  60. package/dist/commands/tools/index.spec.d.ts +1 -0
  61. package/dist/commands/tools/index.spec.js +70 -0
  62. package/dist/commands/uninstall/index.d.ts +2 -1
  63. package/dist/commands/uninstall/index.js +61 -38
  64. package/dist/commands/uninstall/index.spec.d.ts +1 -0
  65. package/dist/commands/uninstall/index.spec.js +117 -0
  66. package/dist/components/ChatUI.js +4 -4
  67. package/dist/components/statusChecker.d.ts +5 -12
  68. package/dist/components/statusChecker.js +172 -89
  69. package/dist/config.d.ts +3 -22
  70. package/dist/config.js +7 -151
  71. package/dist/index.js +22 -19
  72. package/dist/lib/arkServiceProxy.js +4 -2
  73. package/dist/lib/arkStatus.d.ts +5 -0
  74. package/dist/lib/arkStatus.js +61 -2
  75. package/dist/lib/arkStatus.spec.d.ts +1 -0
  76. package/dist/lib/arkStatus.spec.js +49 -0
  77. package/dist/lib/chatClient.js +1 -3
  78. package/dist/lib/cluster.js +11 -14
  79. package/dist/lib/cluster.spec.d.ts +1 -0
  80. package/dist/lib/cluster.spec.js +338 -0
  81. package/dist/lib/commandUtils.js +7 -7
  82. package/dist/lib/commands.d.ts +16 -0
  83. package/dist/lib/commands.js +29 -0
  84. package/dist/lib/commands.spec.d.ts +1 -0
  85. package/dist/lib/commands.spec.js +146 -0
  86. package/dist/lib/config.d.ts +2 -0
  87. package/dist/lib/config.js +6 -4
  88. package/dist/lib/config.spec.d.ts +1 -0
  89. package/dist/lib/config.spec.js +99 -0
  90. package/dist/lib/consts.d.ts +0 -1
  91. package/dist/lib/consts.js +0 -2
  92. package/dist/lib/consts.spec.d.ts +1 -0
  93. package/dist/lib/consts.spec.js +15 -0
  94. package/dist/lib/errors.js +1 -1
  95. package/dist/lib/errors.spec.d.ts +1 -0
  96. package/dist/lib/errors.spec.js +221 -0
  97. package/dist/lib/exec.d.ts +0 -4
  98. package/dist/lib/exec.js +0 -11
  99. package/dist/lib/output.spec.d.ts +1 -0
  100. package/dist/lib/output.spec.js +123 -0
  101. package/dist/lib/portUtils.d.ts +8 -0
  102. package/dist/lib/portUtils.js +39 -0
  103. package/dist/lib/startup.d.ts +5 -0
  104. package/dist/lib/startup.js +73 -0
  105. package/dist/lib/startup.spec.d.ts +1 -0
  106. package/dist/lib/startup.spec.js +168 -0
  107. package/dist/lib/types.d.ts +2 -0
  108. package/dist/ui/AgentSelector.d.ts +8 -0
  109. package/dist/ui/AgentSelector.js +53 -0
  110. package/dist/ui/MainMenu.d.ts +5 -1
  111. package/dist/ui/MainMenu.js +117 -54
  112. package/dist/ui/ModelSelector.d.ts +8 -0
  113. package/dist/ui/ModelSelector.js +53 -0
  114. package/dist/ui/TeamSelector.d.ts +8 -0
  115. package/dist/ui/TeamSelector.js +55 -0
  116. package/dist/ui/ToolSelector.d.ts +8 -0
  117. package/dist/ui/ToolSelector.js +53 -0
  118. package/dist/ui/statusFormatter.d.ts +22 -10
  119. package/dist/ui/statusFormatter.js +37 -109
  120. package/dist/ui/statusFormatter.spec.d.ts +1 -0
  121. package/dist/ui/statusFormatter.spec.js +58 -0
  122. package/package.json +3 -3
@@ -0,0 +1,123 @@
1
+ import { describe, it, expect, beforeEach, afterEach, jest } from '@jest/globals';
2
+ import chalk from 'chalk';
3
+ import output from './output.js';
4
+ describe('output', () => {
5
+ let consoleErrorSpy;
6
+ let consoleLogSpy;
7
+ beforeEach(() => {
8
+ consoleErrorSpy = jest
9
+ .spyOn(console, 'error')
10
+ .mockImplementation(() => undefined);
11
+ consoleLogSpy = jest
12
+ .spyOn(console, 'log')
13
+ .mockImplementation(() => undefined);
14
+ });
15
+ afterEach(() => {
16
+ jest.clearAllMocks();
17
+ });
18
+ describe('error', () => {
19
+ it('should output error message with red cross and prefix', () => {
20
+ output.error('Something went wrong');
21
+ expect(consoleErrorSpy).toHaveBeenCalledWith(chalk.red('✗'), chalk.red('error:'), 'Something went wrong');
22
+ });
23
+ it('should handle additional arguments', () => {
24
+ const error = new Error('Test error');
25
+ output.error('Failed to connect', error, 123);
26
+ expect(consoleErrorSpy).toHaveBeenCalledWith(chalk.red('✗'), chalk.red('error:'), 'Failed to connect', error, 123);
27
+ });
28
+ });
29
+ describe('success', () => {
30
+ it('should output success message with green checkmark', () => {
31
+ output.success('Operation completed');
32
+ expect(consoleLogSpy).toHaveBeenCalledWith(chalk.green('✓'), 'Operation completed');
33
+ });
34
+ it('should handle additional arguments', () => {
35
+ output.success('Deployed', 'v1.0.0', { status: 'ok' });
36
+ expect(consoleLogSpy).toHaveBeenCalledWith(chalk.green('✓'), 'Deployed', 'v1.0.0', { status: 'ok' });
37
+ });
38
+ });
39
+ describe('info', () => {
40
+ it('should output info message in gray', () => {
41
+ output.info('Processing request...');
42
+ expect(consoleLogSpy).toHaveBeenCalledWith(chalk.gray('Processing request...'));
43
+ });
44
+ it('should handle additional arguments', () => {
45
+ output.info('Status:', 'running', 42);
46
+ expect(consoleLogSpy).toHaveBeenCalledWith(chalk.gray('Status:'), 'running', 42);
47
+ });
48
+ });
49
+ describe('warning', () => {
50
+ it('should output warning message with yellow exclamation and prefix', () => {
51
+ output.warning('Resource limit approaching');
52
+ expect(consoleLogSpy).toHaveBeenCalledWith(chalk.yellow.bold('!'), chalk.yellow('warning:'), 'Resource limit approaching');
53
+ });
54
+ it('should handle additional arguments', () => {
55
+ const details = { cpu: '85%', memory: '92%' };
56
+ output.warning('High resource usage', details);
57
+ expect(consoleLogSpy).toHaveBeenCalledWith(chalk.yellow.bold('!'), chalk.yellow('warning:'), 'High resource usage', details);
58
+ });
59
+ });
60
+ describe('statusCheck', () => {
61
+ it('should display found status with value and details', () => {
62
+ output.statusCheck('found', 'platform', 'python3', 'v3.10');
63
+ expect(consoleLogSpy).toHaveBeenCalledWith(` ${chalk.green('✓')} ${chalk.green('platform')} ${chalk.bold.white('python3')}${chalk.gray(' v3.10')}`);
64
+ });
65
+ it('should display found status with value only', () => {
66
+ output.statusCheck('found', 'fastmcp', 'v0.1.0');
67
+ expect(consoleLogSpy).toHaveBeenCalledWith(` ${chalk.green('✓')} ${chalk.green('fastmcp')} ${chalk.bold.white('v0.1.0')}`);
68
+ });
69
+ it('should display found status with label only', () => {
70
+ output.statusCheck('found', '3 tools');
71
+ expect(consoleLogSpy).toHaveBeenCalledWith(` ${chalk.green('✓')} ${chalk.green('3 tools')}`);
72
+ });
73
+ it('should display missing status with details', () => {
74
+ output.statusCheck('missing', 'fastmcp', undefined, 'not in dependencies');
75
+ expect(consoleLogSpy).toHaveBeenCalledWith(` ${chalk.yellow('?')} ${chalk.yellow('fastmcp')}${chalk.gray(' not in dependencies')}`);
76
+ });
77
+ it('should display warning status', () => {
78
+ output.statusCheck('warning', 'version', '0.0.1', 'pre-release');
79
+ expect(consoleLogSpy).toHaveBeenCalledWith(` ${chalk.yellow('!')} ${chalk.yellow('version')} ${chalk.bold.white('0.0.1')}${chalk.gray(' pre-release')}`);
80
+ });
81
+ it('should display error status', () => {
82
+ output.statusCheck('error', 'tools', undefined, 'discovery failed');
83
+ expect(consoleLogSpy).toHaveBeenCalledWith(` ${chalk.red('✗')} ${chalk.red('tools')}${chalk.gray(' discovery failed')}`);
84
+ });
85
+ });
86
+ describe('section', () => {
87
+ it('should display section header with colon', () => {
88
+ output.section('dev-tests');
89
+ expect(consoleLogSpy).toHaveBeenCalledWith(chalk.cyan.bold('dev-tests:'));
90
+ });
91
+ it('should display section header with custom text', () => {
92
+ output.section('ark services');
93
+ expect(consoleLogSpy).toHaveBeenCalledWith(chalk.cyan.bold('ark services:'));
94
+ });
95
+ });
96
+ describe('statusMessage', () => {
97
+ it('should output success status with title and message', () => {
98
+ output.statusMessage('success', 'fastmcp', 'installed (v0.1.0)');
99
+ expect(consoleLogSpy).toHaveBeenCalledWith(chalk.green('✓'), chalk.green('fastmcp:'), 'installed (v0.1.0)');
100
+ });
101
+ it('should output warning status with title and message', () => {
102
+ output.statusMessage('warning', 'virtual environment', 'not found');
103
+ expect(consoleLogSpy).toHaveBeenCalledWith(chalk.yellow.bold('!'), chalk.yellow('virtual environment:'), 'not found');
104
+ });
105
+ it('should output error status with title and message', () => {
106
+ output.statusMessage('error', 'fastmcp', 'not found in dependencies');
107
+ expect(consoleErrorSpy).toHaveBeenCalledWith(chalk.red('✗'), chalk.red('fastmcp:'), 'not found in dependencies');
108
+ });
109
+ it('should output info status with title and message', () => {
110
+ output.statusMessage('info', 'platform', 'python3');
111
+ expect(consoleLogSpy).toHaveBeenCalledWith(chalk.blue('ℹ'), chalk.blue('platform:'), 'python3');
112
+ });
113
+ it('should handle title-only messages', () => {
114
+ output.statusMessage('success', 'Operation completed');
115
+ expect(consoleLogSpy).toHaveBeenCalledWith(chalk.green('✓'), 'Operation completed');
116
+ });
117
+ it('should handle additional arguments', () => {
118
+ const extra = { version: '1.0' };
119
+ output.statusMessage('info', 'status', 'running', extra, 42);
120
+ expect(consoleLogSpy).toHaveBeenCalledWith(chalk.blue('ℹ'), chalk.blue('status:'), 'running', extra, 42);
121
+ });
122
+ });
123
+ });
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Check if a port is available
3
+ */
4
+ export declare function isPortAvailable(port: number): Promise<boolean>;
5
+ /**
6
+ * Find an available port, starting from the preferred port
7
+ */
8
+ export declare function findAvailablePort(preferredPort: number, maxAttempts?: number): Promise<number>;
@@ -0,0 +1,39 @@
1
+ import net from 'net';
2
+ /**
3
+ * Check if a port is available
4
+ */
5
+ export async function isPortAvailable(port) {
6
+ return new Promise((resolve) => {
7
+ const server = net.createServer();
8
+ server.once('error', (err) => {
9
+ if (err.code === 'EADDRINUSE') {
10
+ resolve(false);
11
+ }
12
+ else {
13
+ resolve(false);
14
+ }
15
+ });
16
+ server.once('listening', () => {
17
+ server.close();
18
+ resolve(true);
19
+ });
20
+ server.listen(port, '127.0.0.1');
21
+ });
22
+ }
23
+ /**
24
+ * Find an available port, starting from the preferred port
25
+ */
26
+ export async function findAvailablePort(preferredPort, maxAttempts = 10) {
27
+ // First try the preferred port
28
+ if (await isPortAvailable(preferredPort)) {
29
+ return preferredPort;
30
+ }
31
+ // Try random ports
32
+ for (let i = 0; i < maxAttempts; i++) {
33
+ const randomPort = Math.floor(Math.random() * (65535 - 1024) + 1024);
34
+ if (await isPortAvailable(randomPort)) {
35
+ return randomPort;
36
+ }
37
+ }
38
+ throw new Error(`Could not find an available port after ${maxAttempts} attempts`);
39
+ }
@@ -0,0 +1,5 @@
1
+ import type { ArkConfig } from './config.js';
2
+ /**
3
+ * Initialize the CLI by checking requirements and loading config
4
+ */
5
+ export declare function startup(): Promise<ArkConfig>;
@@ -0,0 +1,73 @@
1
+ import chalk from 'chalk';
2
+ import { checkCommandExists } from './commands.js';
3
+ import { loadConfig } from './config.js';
4
+ import { getArkVersion } from './arkStatus.js';
5
+ const REQUIRED_COMMANDS = [
6
+ {
7
+ name: 'kubectl',
8
+ command: 'kubectl',
9
+ args: ['version', '--client'],
10
+ installUrl: 'https://kubernetes.io/docs/tasks/tools/',
11
+ },
12
+ {
13
+ name: 'helm',
14
+ command: 'helm',
15
+ args: ['version', '--short'],
16
+ installUrl: 'https://helm.sh/docs/intro/install/',
17
+ },
18
+ ];
19
+ async function checkRequirements() {
20
+ const missing = [];
21
+ for (const cmd of REQUIRED_COMMANDS) {
22
+ const exists = await checkCommandExists(cmd.command, cmd.args);
23
+ if (!exists) {
24
+ missing.push(cmd);
25
+ }
26
+ }
27
+ if (missing.length > 0) {
28
+ for (const cmd of missing) {
29
+ console.error(chalk.red('error:') + ` ${cmd.name} is required`);
30
+ console.error(' ' + chalk.blue(cmd.installUrl));
31
+ }
32
+ process.exit(1);
33
+ }
34
+ }
35
+ /**
36
+ * Fetch version information (non-blocking)
37
+ */
38
+ async function fetchVersionInfo(config) {
39
+ // Fetch latest version from GitHub
40
+ try {
41
+ const response = await fetch('https://api.github.com/repos/mckinsey/agents-at-scale-ark/releases/latest');
42
+ if (response.ok) {
43
+ const data = (await response.json());
44
+ // Remove 'v' prefix if present for consistent comparison
45
+ config.latestVersion = data.tag_name.replace(/^v/, '');
46
+ }
47
+ }
48
+ catch {
49
+ // Silently fail - latestVersion will remain undefined
50
+ }
51
+ // Fetch current installed version (already without 'v' from helm)
52
+ try {
53
+ const currentVersion = await getArkVersion();
54
+ if (currentVersion) {
55
+ config.currentVersion = currentVersion;
56
+ }
57
+ }
58
+ catch {
59
+ // Silently fail - currentVersion will remain undefined
60
+ }
61
+ }
62
+ /**
63
+ * Initialize the CLI by checking requirements and loading config
64
+ */
65
+ export async function startup() {
66
+ // Check required commands
67
+ await checkRequirements();
68
+ // Load config
69
+ const config = loadConfig();
70
+ // Fetch version info synchronously so it's available immediately
71
+ await fetchVersionInfo(config);
72
+ return config;
73
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,168 @@
1
+ import { describe, it, expect, jest, beforeEach, afterEach } from '@jest/globals';
2
+ // Mock chalk to avoid ANSI codes in tests
3
+ jest.unstable_mockModule('chalk', () => ({
4
+ default: {
5
+ red: (str) => str,
6
+ yellow: (str) => str,
7
+ gray: (str) => str,
8
+ blue: (str) => str,
9
+ },
10
+ }));
11
+ // Mock commands module
12
+ jest.unstable_mockModule('./commands.js', () => ({
13
+ checkCommandExists: jest.fn(),
14
+ }));
15
+ // Mock config module
16
+ jest.unstable_mockModule('./config.js', () => ({
17
+ loadConfig: jest.fn(),
18
+ }));
19
+ // Dynamic imports after mocks
20
+ const { checkCommandExists } = await import('./commands.js');
21
+ const { loadConfig } = await import('./config.js');
22
+ const { startup } = await import('./startup.js');
23
+ // Type the mocks
24
+ const mockCheckCommandExists = checkCommandExists;
25
+ const mockLoadConfig = loadConfig;
26
+ // Mock fetch globally
27
+ globalThis.fetch = jest.fn();
28
+ describe('startup', () => {
29
+ let mockExit;
30
+ let mockConsoleError;
31
+ beforeEach(() => {
32
+ jest.clearAllMocks();
33
+ globalThis.fetch.mockClear();
34
+ mockExit = jest.spyOn(process, 'exit').mockImplementation(() => {
35
+ throw new Error('process.exit');
36
+ });
37
+ mockConsoleError = jest
38
+ .spyOn(console, 'error')
39
+ .mockImplementation(() => { });
40
+ });
41
+ afterEach(() => {
42
+ mockExit.mockRestore();
43
+ mockConsoleError.mockRestore();
44
+ });
45
+ it('returns config when all required commands are installed', async () => {
46
+ const expectedConfig = {
47
+ chat: {
48
+ streaming: true,
49
+ outputFormat: 'text',
50
+ },
51
+ };
52
+ // Mock all commands as available
53
+ mockCheckCommandExists.mockResolvedValue(true);
54
+ mockLoadConfig.mockReturnValue(expectedConfig);
55
+ const config = await startup();
56
+ expect(config).toEqual(expectedConfig);
57
+ expect(mockCheckCommandExists).toHaveBeenCalledWith('kubectl', [
58
+ 'version',
59
+ '--client',
60
+ ]);
61
+ expect(mockCheckCommandExists).toHaveBeenCalledWith('helm', [
62
+ 'version',
63
+ '--short',
64
+ ]);
65
+ expect(mockLoadConfig).toHaveBeenCalledTimes(1);
66
+ expect(mockExit).not.toHaveBeenCalled();
67
+ });
68
+ it('exits with error when kubectl is missing', async () => {
69
+ // Mock kubectl as missing, helm as available
70
+ mockCheckCommandExists
71
+ .mockResolvedValueOnce(false) // kubectl
72
+ .mockResolvedValueOnce(true); // helm
73
+ await expect(startup()).rejects.toThrow('process.exit');
74
+ expect(mockConsoleError).toHaveBeenCalledWith('error: kubectl is required');
75
+ expect(mockConsoleError).toHaveBeenCalledWith(' https://kubernetes.io/docs/tasks/tools/');
76
+ expect(mockExit).toHaveBeenCalledWith(1);
77
+ });
78
+ it('exits with error when helm is missing', async () => {
79
+ // Mock kubectl as available, helm as missing
80
+ mockCheckCommandExists
81
+ .mockResolvedValueOnce(true) // kubectl
82
+ .mockResolvedValueOnce(false); // helm
83
+ await expect(startup()).rejects.toThrow('process.exit');
84
+ expect(mockConsoleError).toHaveBeenCalledWith('error: helm is required');
85
+ expect(mockConsoleError).toHaveBeenCalledWith(' https://helm.sh/docs/intro/install/');
86
+ expect(mockExit).toHaveBeenCalledWith(1);
87
+ });
88
+ it('exits with error when both commands are missing', async () => {
89
+ // Mock both commands as missing
90
+ mockCheckCommandExists.mockResolvedValue(false);
91
+ await expect(startup()).rejects.toThrow('process.exit');
92
+ expect(mockConsoleError).toHaveBeenCalledWith('error: kubectl is required');
93
+ expect(mockConsoleError).toHaveBeenCalledWith(' https://kubernetes.io/docs/tasks/tools/');
94
+ expect(mockConsoleError).toHaveBeenCalledWith('error: helm is required');
95
+ expect(mockConsoleError).toHaveBeenCalledWith(' https://helm.sh/docs/intro/install/');
96
+ expect(mockExit).toHaveBeenCalledWith(1);
97
+ });
98
+ it('checks commands with correct arguments', async () => {
99
+ mockCheckCommandExists.mockResolvedValue(true);
100
+ mockLoadConfig.mockReturnValue({ chat: {} });
101
+ await startup();
102
+ expect(mockCheckCommandExists).toHaveBeenCalledTimes(2);
103
+ expect(mockCheckCommandExists).toHaveBeenNthCalledWith(1, 'kubectl', [
104
+ 'version',
105
+ '--client',
106
+ ]);
107
+ expect(mockCheckCommandExists).toHaveBeenNthCalledWith(2, 'helm', [
108
+ 'version',
109
+ '--short',
110
+ ]);
111
+ });
112
+ it('loads config after checking requirements', async () => {
113
+ mockCheckCommandExists.mockResolvedValue(true);
114
+ const expectedConfig = { chat: { streaming: false } };
115
+ mockLoadConfig.mockReturnValue(expectedConfig);
116
+ const config = await startup();
117
+ // Verify order - checkCommandExists should be called before loadConfig
118
+ const checkCallOrder = mockCheckCommandExists.mock.invocationCallOrder[0];
119
+ const loadCallOrder = mockLoadConfig.mock.invocationCallOrder[0];
120
+ expect(checkCallOrder).toBeLessThan(loadCallOrder);
121
+ expect(config).toEqual(expectedConfig);
122
+ });
123
+ describe('version fetching', () => {
124
+ beforeEach(() => {
125
+ // Setup successful requirements check and config
126
+ mockCheckCommandExists.mockResolvedValue(true);
127
+ mockLoadConfig.mockReturnValue({ chat: { streaming: true } });
128
+ });
129
+ it('fetches latest version from GitHub API', async () => {
130
+ globalThis.fetch.mockResolvedValue({
131
+ ok: true,
132
+ json: async () => ({ tag_name: 'v0.1.35' }),
133
+ });
134
+ const config = await startup();
135
+ expect(globalThis.fetch).toHaveBeenCalledWith('https://api.github.com/repos/mckinsey/agents-at-scale-ark/releases/latest');
136
+ // Wait for async fetch to complete
137
+ await new Promise((resolve) => setTimeout(resolve, 50));
138
+ expect(config.latestVersion).toBe('v0.1.35');
139
+ });
140
+ it('handles GitHub API failure gracefully', async () => {
141
+ globalThis.fetch.mockRejectedValue(new Error('Network error'));
142
+ const config = await startup();
143
+ // Wait for async fetch attempt
144
+ await new Promise((resolve) => setTimeout(resolve, 50));
145
+ // Should not have latestVersion set
146
+ expect(config.latestVersion).toBeUndefined();
147
+ });
148
+ it('handles non-OK response from GitHub API', async () => {
149
+ globalThis.fetch.mockResolvedValue({
150
+ ok: false,
151
+ status: 403,
152
+ });
153
+ const config = await startup();
154
+ // Wait for async fetch to complete
155
+ await new Promise((resolve) => setTimeout(resolve, 50));
156
+ // Should not have latestVersion set
157
+ expect(config.latestVersion).toBeUndefined();
158
+ });
159
+ it('continues startup even if version fetch fails', async () => {
160
+ globalThis.fetch.mockRejectedValue(new Error('API Error'));
161
+ const config = await startup();
162
+ // Startup should complete successfully
163
+ expect(config).toBeDefined();
164
+ expect(config.chat).toBeDefined();
165
+ expect(mockExit).not.toHaveBeenCalled();
166
+ });
167
+ });
168
+ });
@@ -22,6 +22,8 @@ export type ServiceStatus = {
22
22
  version?: string;
23
23
  revision?: string;
24
24
  details?: string;
25
+ isDev?: boolean;
26
+ namespace?: string;
25
27
  };
26
28
  export interface DependencyStatus {
27
29
  name: string;
@@ -0,0 +1,8 @@
1
+ import { Agent, ArkApiClient } from '../lib/arkApiClient.js';
2
+ interface AgentSelectorProps {
3
+ arkApiClient: ArkApiClient;
4
+ onSelect: (agent: Agent) => void;
5
+ onExit: () => void;
6
+ }
7
+ export declare function AgentSelector({ arkApiClient, onSelect, onExit, }: AgentSelectorProps): import("react/jsx-runtime").JSX.Element;
8
+ export {};
@@ -0,0 +1,53 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useState, useEffect } from 'react';
3
+ import { Box, Text, useInput } from 'ink';
4
+ export function AgentSelector({ arkApiClient, onSelect, onExit, }) {
5
+ const [selectedIndex, setSelectedIndex] = useState(0);
6
+ const [agents, setAgents] = useState([]);
7
+ const [loading, setLoading] = useState(true);
8
+ const [error, setError] = useState(null);
9
+ useEffect(() => {
10
+ arkApiClient
11
+ .getAgents()
12
+ .then((fetchedAgents) => {
13
+ setAgents(fetchedAgents);
14
+ setLoading(false);
15
+ })
16
+ .catch((err) => {
17
+ setError(err.message || 'Failed to fetch agents');
18
+ setLoading(false);
19
+ });
20
+ }, [arkApiClient]);
21
+ useInput((input, key) => {
22
+ if (key.escape) {
23
+ onExit();
24
+ }
25
+ else if (key.upArrow || input === 'k') {
26
+ setSelectedIndex((prev) => (prev === 0 ? agents.length - 1 : prev - 1));
27
+ }
28
+ else if (key.downArrow || input === 'j') {
29
+ setSelectedIndex((prev) => (prev === agents.length - 1 ? 0 : prev + 1));
30
+ }
31
+ else if (key.return) {
32
+ onSelect(agents[selectedIndex]);
33
+ }
34
+ else {
35
+ // Handle number keys for quick selection
36
+ const num = parseInt(input, 10);
37
+ if (!isNaN(num) && num >= 1 && num <= agents.length) {
38
+ onSelect(agents[num - 1]);
39
+ }
40
+ }
41
+ });
42
+ if (loading) {
43
+ return (_jsx(Box, { children: _jsx(Text, { children: "Loading agents..." }) }));
44
+ }
45
+ if (error) {
46
+ return (_jsx(Box, { children: _jsxs(Text, { color: "red", children: ["Error: ", error] }) }));
47
+ }
48
+ if (agents.length === 0) {
49
+ return (_jsx(Box, { children: _jsx(Text, { children: "No agents available" }) }));
50
+ }
51
+ const selectedAgent = agents[selectedIndex];
52
+ return (_jsxs(Box, { flexDirection: "column", borderStyle: "round", borderColor: "gray", paddingX: 2, paddingY: 1, children: [_jsx(Box, { marginBottom: 1, children: _jsx(Text, { bold: true, children: "Select Agent" }) }), _jsx(Box, { marginBottom: 1, children: _jsx(Text, { dimColor: true, children: "Choose an agent to start a conversation with" }) }), _jsx(Box, { flexDirection: "column", children: agents.map((agent, index) => (_jsx(Box, { marginBottom: 0, children: _jsxs(Text, { color: index === selectedIndex ? 'green' : undefined, children: [index === selectedIndex ? '❯ ' : ' ', index + 1, ". ", agent.name] }) }, agent.name))) }), selectedAgent.description && (_jsx(Box, { marginTop: 1, paddingLeft: 2, children: _jsx(Text, { dimColor: true, wrap: "wrap", children: selectedAgent.description }) })), _jsx(Box, { marginTop: 1, children: _jsx(Text, { dimColor: true, children: "Enter to confirm \u00B7 Number to select \u00B7 Esc to exit" }) })] }));
53
+ }
@@ -1,3 +1,7 @@
1
1
  import * as React from 'react';
2
- declare const MainMenu: React.FC;
2
+ import type { ArkConfig } from '../lib/config.js';
3
+ interface MainMenuProps {
4
+ config: ArkConfig;
5
+ }
6
+ declare const MainMenu: React.FC<MainMenuProps>;
3
7
  export default MainMenu;