@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.
- package/dist/arkServices.d.ts +4 -12
- package/dist/arkServices.js +19 -34
- package/dist/arkServices.spec.d.ts +1 -0
- package/dist/arkServices.spec.js +24 -0
- package/dist/commands/agents/index.d.ts +2 -1
- package/dist/commands/agents/index.js +2 -7
- package/dist/commands/agents/index.spec.d.ts +1 -0
- package/dist/commands/agents/index.spec.js +67 -0
- package/dist/commands/chat/index.d.ts +2 -1
- package/dist/commands/chat/index.js +5 -21
- package/dist/commands/cluster/get.spec.d.ts +1 -0
- package/dist/commands/cluster/get.spec.js +92 -0
- package/dist/commands/cluster/index.d.ts +2 -1
- package/dist/commands/cluster/index.js +1 -1
- package/dist/commands/cluster/index.spec.d.ts +1 -0
- package/dist/commands/cluster/index.spec.js +24 -0
- package/dist/commands/completion/index.d.ts +2 -1
- package/dist/commands/completion/index.js +1 -1
- package/dist/commands/completion/index.spec.d.ts +1 -0
- package/dist/commands/completion/index.spec.js +34 -0
- package/dist/commands/config/index.d.ts +2 -1
- package/dist/commands/config/index.js +2 -2
- package/dist/commands/config/index.spec.d.ts +1 -0
- package/dist/commands/config/index.spec.js +78 -0
- package/dist/commands/dashboard/index.d.ts +2 -1
- package/dist/commands/dashboard/index.js +1 -1
- package/dist/commands/dev/index.d.ts +2 -1
- package/dist/commands/dev/index.js +1 -1
- package/dist/commands/dev/tool-generate.spec.d.ts +1 -0
- package/dist/commands/dev/tool-generate.spec.js +163 -0
- package/dist/commands/dev/tool.spec.d.ts +1 -0
- package/dist/commands/dev/tool.spec.js +48 -0
- package/dist/commands/generate/generators/project.js +22 -41
- package/dist/commands/generate/index.d.ts +2 -1
- package/dist/commands/generate/index.js +1 -1
- package/dist/commands/install/index.d.ts +4 -2
- package/dist/commands/install/index.js +215 -78
- package/dist/commands/install/index.spec.d.ts +1 -0
- package/dist/commands/install/index.spec.js +135 -0
- package/dist/commands/models/create.spec.d.ts +1 -0
- package/dist/commands/models/create.spec.js +125 -0
- package/dist/commands/models/index.d.ts +2 -1
- package/dist/commands/models/index.js +2 -7
- package/dist/commands/models/index.spec.d.ts +1 -0
- package/dist/commands/models/index.spec.js +76 -0
- package/dist/commands/routes/index.d.ts +2 -1
- package/dist/commands/routes/index.js +1 -9
- package/dist/commands/status/index.d.ts +3 -2
- package/dist/commands/status/index.js +210 -11
- package/dist/commands/targets/index.d.ts +2 -1
- package/dist/commands/targets/index.js +1 -1
- package/dist/commands/targets/index.spec.d.ts +1 -0
- package/dist/commands/targets/index.spec.js +105 -0
- package/dist/commands/teams/index.d.ts +2 -1
- package/dist/commands/teams/index.js +2 -7
- package/dist/commands/teams/index.spec.d.ts +1 -0
- package/dist/commands/teams/index.spec.js +70 -0
- package/dist/commands/tools/index.d.ts +2 -1
- package/dist/commands/tools/index.js +2 -7
- package/dist/commands/tools/index.spec.d.ts +1 -0
- package/dist/commands/tools/index.spec.js +70 -0
- package/dist/commands/uninstall/index.d.ts +2 -1
- package/dist/commands/uninstall/index.js +61 -38
- package/dist/commands/uninstall/index.spec.d.ts +1 -0
- package/dist/commands/uninstall/index.spec.js +117 -0
- package/dist/components/ChatUI.js +4 -4
- package/dist/components/statusChecker.d.ts +5 -12
- package/dist/components/statusChecker.js +172 -89
- package/dist/config.d.ts +3 -22
- package/dist/config.js +7 -151
- package/dist/index.js +22 -19
- package/dist/lib/arkServiceProxy.js +4 -2
- package/dist/lib/arkStatus.d.ts +5 -0
- package/dist/lib/arkStatus.js +61 -2
- package/dist/lib/arkStatus.spec.d.ts +1 -0
- package/dist/lib/arkStatus.spec.js +49 -0
- package/dist/lib/chatClient.js +1 -3
- package/dist/lib/cluster.js +11 -14
- package/dist/lib/cluster.spec.d.ts +1 -0
- package/dist/lib/cluster.spec.js +338 -0
- package/dist/lib/commandUtils.js +7 -7
- package/dist/lib/commands.d.ts +16 -0
- package/dist/lib/commands.js +29 -0
- package/dist/lib/commands.spec.d.ts +1 -0
- package/dist/lib/commands.spec.js +146 -0
- package/dist/lib/config.d.ts +2 -0
- package/dist/lib/config.js +6 -4
- package/dist/lib/config.spec.d.ts +1 -0
- package/dist/lib/config.spec.js +99 -0
- package/dist/lib/consts.d.ts +0 -1
- package/dist/lib/consts.js +0 -2
- package/dist/lib/consts.spec.d.ts +1 -0
- package/dist/lib/consts.spec.js +15 -0
- package/dist/lib/errors.js +1 -1
- package/dist/lib/errors.spec.d.ts +1 -0
- package/dist/lib/errors.spec.js +221 -0
- package/dist/lib/exec.d.ts +0 -4
- package/dist/lib/exec.js +0 -11
- package/dist/lib/output.spec.d.ts +1 -0
- package/dist/lib/output.spec.js +123 -0
- package/dist/lib/portUtils.d.ts +8 -0
- package/dist/lib/portUtils.js +39 -0
- package/dist/lib/startup.d.ts +5 -0
- package/dist/lib/startup.js +73 -0
- package/dist/lib/startup.spec.d.ts +1 -0
- package/dist/lib/startup.spec.js +168 -0
- package/dist/lib/types.d.ts +2 -0
- package/dist/ui/AgentSelector.d.ts +8 -0
- package/dist/ui/AgentSelector.js +53 -0
- package/dist/ui/MainMenu.d.ts +5 -1
- package/dist/ui/MainMenu.js +117 -54
- package/dist/ui/ModelSelector.d.ts +8 -0
- package/dist/ui/ModelSelector.js +53 -0
- package/dist/ui/TeamSelector.d.ts +8 -0
- package/dist/ui/TeamSelector.js +55 -0
- package/dist/ui/ToolSelector.d.ts +8 -0
- package/dist/ui/ToolSelector.js +53 -0
- package/dist/ui/statusFormatter.d.ts +22 -10
- package/dist/ui/statusFormatter.js +37 -109
- package/dist/ui/statusFormatter.spec.d.ts +1 -0
- package/dist/ui/statusFormatter.spec.js +58 -0
- 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,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
|
+
});
|
package/dist/lib/types.d.ts
CHANGED
|
@@ -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
|
+
}
|
package/dist/ui/MainMenu.d.ts
CHANGED