@agents-at-scale/ark 0.1.35-rc.1 → 0.1.35-rc2
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 +24 -2
- 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/docs/index.d.ts +4 -0
- package/dist/commands/docs/index.js +18 -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 +225 -90
- package/dist/commands/install/index.spec.d.ts +1 -0
- package/dist/commands/install/index.spec.js +143 -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/query/index.d.ts +3 -0
- package/dist/commands/query/index.js +131 -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 +240 -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 +66 -44
- package/dist/commands/uninstall/index.spec.d.ts +1 -0
- package/dist/commands/uninstall/index.spec.js +125 -0
- package/dist/components/ChatUI.js +4 -4
- package/dist/components/statusChecker.d.ts +5 -12
- package/dist/components/statusChecker.js +193 -90
- package/dist/config.d.ts +3 -22
- package/dist/config.js +7 -151
- package/dist/index.js +26 -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 +4 -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/nextSteps.d.ts +4 -0
- package/dist/lib/nextSteps.js +20 -0
- package/dist/lib/nextSteps.spec.d.ts +1 -0
- package/dist/lib/nextSteps.spec.js +59 -0
- 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 +9 -0
- package/dist/lib/startup.js +93 -0
- package/dist/lib/startup.spec.d.ts +1 -0
- package/dist/lib/startup.spec.js +168 -0
- package/dist/lib/types.d.ts +9 -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
|
@@ -26,16 +26,11 @@ async function listModels(options) {
|
|
|
26
26
|
}
|
|
27
27
|
}
|
|
28
28
|
catch (error) {
|
|
29
|
-
|
|
30
|
-
output.error('Model CRDs not installed. Is the ARK controller running?');
|
|
31
|
-
}
|
|
32
|
-
else {
|
|
33
|
-
output.error(`Failed to list models: ${error instanceof Error ? error.message : 'Unknown error'}`);
|
|
34
|
-
}
|
|
29
|
+
output.error('fetching models:', error instanceof Error ? error.message : error);
|
|
35
30
|
process.exit(1);
|
|
36
31
|
}
|
|
37
32
|
}
|
|
38
|
-
export function createModelsCommand() {
|
|
33
|
+
export function createModelsCommand(_) {
|
|
39
34
|
const modelsCommand = new Command('models');
|
|
40
35
|
modelsCommand
|
|
41
36
|
.description('List available models')
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { jest } from '@jest/globals';
|
|
2
|
+
import { Command } from 'commander';
|
|
3
|
+
const mockExeca = jest.fn();
|
|
4
|
+
jest.unstable_mockModule('execa', () => ({
|
|
5
|
+
execa: mockExeca,
|
|
6
|
+
}));
|
|
7
|
+
const mockOutput = {
|
|
8
|
+
info: jest.fn(),
|
|
9
|
+
error: jest.fn(),
|
|
10
|
+
};
|
|
11
|
+
jest.unstable_mockModule('../../lib/output.js', () => ({
|
|
12
|
+
default: mockOutput,
|
|
13
|
+
}));
|
|
14
|
+
const mockCreateModel = jest.fn();
|
|
15
|
+
jest.unstable_mockModule('./create.js', () => ({
|
|
16
|
+
createModel: mockCreateModel,
|
|
17
|
+
}));
|
|
18
|
+
const mockExit = jest.spyOn(process, 'exit').mockImplementation((() => {
|
|
19
|
+
throw new Error('process.exit called');
|
|
20
|
+
}));
|
|
21
|
+
const mockConsoleLog = jest.spyOn(console, 'log').mockImplementation(() => { });
|
|
22
|
+
const { createModelsCommand } = await import('./index.js');
|
|
23
|
+
describe('models command', () => {
|
|
24
|
+
beforeEach(() => {
|
|
25
|
+
jest.clearAllMocks();
|
|
26
|
+
});
|
|
27
|
+
it('creates command with correct structure', () => {
|
|
28
|
+
const command = createModelsCommand({});
|
|
29
|
+
expect(command).toBeInstanceOf(Command);
|
|
30
|
+
expect(command.name()).toBe('models');
|
|
31
|
+
});
|
|
32
|
+
it('lists models in text format', async () => {
|
|
33
|
+
const mockModels = {
|
|
34
|
+
items: [{ metadata: { name: 'gpt-4' } }, { metadata: { name: 'claude-3' } }],
|
|
35
|
+
};
|
|
36
|
+
mockExeca.mockResolvedValue({ stdout: JSON.stringify(mockModels) });
|
|
37
|
+
const command = createModelsCommand({});
|
|
38
|
+
await command.parseAsync(['node', 'test']);
|
|
39
|
+
expect(mockExeca).toHaveBeenCalledWith('kubectl', ['get', 'models', '-o', 'json'], { stdio: 'pipe' });
|
|
40
|
+
expect(mockConsoleLog).toHaveBeenCalledWith('gpt-4');
|
|
41
|
+
expect(mockConsoleLog).toHaveBeenCalledWith('claude-3');
|
|
42
|
+
});
|
|
43
|
+
it('lists models in json format', async () => {
|
|
44
|
+
const mockModels = {
|
|
45
|
+
items: [{ metadata: { name: 'gpt-4' } }],
|
|
46
|
+
};
|
|
47
|
+
mockExeca.mockResolvedValue({ stdout: JSON.stringify(mockModels) });
|
|
48
|
+
const command = createModelsCommand({});
|
|
49
|
+
await command.parseAsync(['node', 'test', '-o', 'json']);
|
|
50
|
+
expect(mockConsoleLog).toHaveBeenCalledWith(JSON.stringify(mockModels.items, null, 2));
|
|
51
|
+
});
|
|
52
|
+
it('shows info when no models', async () => {
|
|
53
|
+
mockExeca.mockResolvedValue({ stdout: JSON.stringify({ items: [] }) });
|
|
54
|
+
const command = createModelsCommand({});
|
|
55
|
+
await command.parseAsync(['node', 'test']);
|
|
56
|
+
expect(mockOutput.info).toHaveBeenCalledWith('No models found');
|
|
57
|
+
});
|
|
58
|
+
it('handles errors', async () => {
|
|
59
|
+
mockExeca.mockRejectedValue(new Error('kubectl failed'));
|
|
60
|
+
const command = createModelsCommand({});
|
|
61
|
+
await expect(command.parseAsync(['node', 'test'])).rejects.toThrow('process.exit called');
|
|
62
|
+
expect(mockOutput.error).toHaveBeenCalledWith('fetching models:', 'kubectl failed');
|
|
63
|
+
expect(mockExit).toHaveBeenCalledWith(1);
|
|
64
|
+
});
|
|
65
|
+
it('list subcommand works', async () => {
|
|
66
|
+
mockExeca.mockResolvedValue({ stdout: JSON.stringify({ items: [] }) });
|
|
67
|
+
const command = createModelsCommand({});
|
|
68
|
+
await command.parseAsync(['node', 'test', 'list']);
|
|
69
|
+
expect(mockExeca).toHaveBeenCalled();
|
|
70
|
+
});
|
|
71
|
+
it('create subcommand works', async () => {
|
|
72
|
+
const command = createModelsCommand({});
|
|
73
|
+
await command.parseAsync(['node', 'test', 'create', 'my-model']);
|
|
74
|
+
expect(mockCreateModel).toHaveBeenCalledWith('my-model');
|
|
75
|
+
});
|
|
76
|
+
});
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
import { Command } from 'commander';
|
|
2
|
+
import { execa } from 'execa';
|
|
3
|
+
import ora from 'ora';
|
|
4
|
+
import output from '../../lib/output.js';
|
|
5
|
+
async function runQuery(target, message) {
|
|
6
|
+
const spinner = ora('Creating query...').start();
|
|
7
|
+
// Generate a unique query name
|
|
8
|
+
const timestamp = Date.now();
|
|
9
|
+
const queryName = `cli-query-${timestamp}`;
|
|
10
|
+
// Parse the target format (e.g., model/default -> type: model, name: default)
|
|
11
|
+
const [targetType, targetName] = target.split('/');
|
|
12
|
+
// Create the Query resource
|
|
13
|
+
const queryManifest = {
|
|
14
|
+
apiVersion: 'ark.mckinsey.com/v1alpha1',
|
|
15
|
+
kind: 'Query',
|
|
16
|
+
metadata: {
|
|
17
|
+
name: queryName,
|
|
18
|
+
},
|
|
19
|
+
spec: {
|
|
20
|
+
input: message,
|
|
21
|
+
targets: [
|
|
22
|
+
{
|
|
23
|
+
type: targetType,
|
|
24
|
+
name: targetName,
|
|
25
|
+
},
|
|
26
|
+
],
|
|
27
|
+
},
|
|
28
|
+
};
|
|
29
|
+
try {
|
|
30
|
+
// Apply the query
|
|
31
|
+
spinner.text = 'Submitting query...';
|
|
32
|
+
await execa('kubectl', ['apply', '-f', '-'], {
|
|
33
|
+
input: JSON.stringify(queryManifest),
|
|
34
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
35
|
+
});
|
|
36
|
+
// Watch for query completion
|
|
37
|
+
spinner.text = 'Query status: initializing';
|
|
38
|
+
let queryComplete = false;
|
|
39
|
+
let attempts = 0;
|
|
40
|
+
const maxAttempts = 300; // 5 minutes with 1 second intervals
|
|
41
|
+
while (!queryComplete && attempts < maxAttempts) {
|
|
42
|
+
attempts++;
|
|
43
|
+
try {
|
|
44
|
+
const { stdout } = await execa('kubectl', [
|
|
45
|
+
'get',
|
|
46
|
+
'query',
|
|
47
|
+
queryName,
|
|
48
|
+
'-o',
|
|
49
|
+
'json',
|
|
50
|
+
], { stdio: 'pipe' });
|
|
51
|
+
const query = JSON.parse(stdout);
|
|
52
|
+
const phase = query.status?.phase;
|
|
53
|
+
// Update spinner with current phase
|
|
54
|
+
if (phase) {
|
|
55
|
+
spinner.text = `Query status: ${phase}`;
|
|
56
|
+
}
|
|
57
|
+
// Check if query is complete based on phase
|
|
58
|
+
if (phase === 'done') {
|
|
59
|
+
queryComplete = true;
|
|
60
|
+
spinner.succeed('Query completed');
|
|
61
|
+
// Extract and display the response from responses array
|
|
62
|
+
if (query.status?.responses && query.status.responses.length > 0) {
|
|
63
|
+
const response = query.status.responses[0];
|
|
64
|
+
console.log('\n' + (response.content || response));
|
|
65
|
+
}
|
|
66
|
+
else {
|
|
67
|
+
output.warning('No response received');
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
else if (phase === 'error') {
|
|
71
|
+
queryComplete = true;
|
|
72
|
+
spinner.fail('Query failed');
|
|
73
|
+
// Try to get error message from conditions or status
|
|
74
|
+
const errorCondition = query.status?.conditions?.find((c) => {
|
|
75
|
+
const condition = c;
|
|
76
|
+
return condition.type === 'Complete' && condition.status === 'False';
|
|
77
|
+
});
|
|
78
|
+
if (errorCondition?.message) {
|
|
79
|
+
output.error(errorCondition.message);
|
|
80
|
+
}
|
|
81
|
+
else if (query.status?.error) {
|
|
82
|
+
output.error(query.status.error);
|
|
83
|
+
}
|
|
84
|
+
else {
|
|
85
|
+
output.error('Query failed with unknown error');
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
else if (phase === 'canceled') {
|
|
89
|
+
queryComplete = true;
|
|
90
|
+
spinner.warn('Query canceled');
|
|
91
|
+
// Try to get cancellation reason if available
|
|
92
|
+
if (query.status?.message) {
|
|
93
|
+
output.warning(query.status.message);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
catch {
|
|
98
|
+
// Query might not exist yet, continue waiting
|
|
99
|
+
spinner.text = 'Running query: waiting for query to be created';
|
|
100
|
+
}
|
|
101
|
+
if (!queryComplete) {
|
|
102
|
+
await new Promise(resolve => setTimeout(resolve, 1000)); // Wait 1 second
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
if (!queryComplete) {
|
|
106
|
+
spinner.fail('Query timed out');
|
|
107
|
+
output.error('Query did not complete within 5 minutes');
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
catch (error) {
|
|
111
|
+
spinner.fail('Query failed');
|
|
112
|
+
output.error(error instanceof Error ? error.message : 'Unknown error');
|
|
113
|
+
process.exit(1);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
export function createQueryCommand(_) {
|
|
117
|
+
const queryCommand = new Command('query');
|
|
118
|
+
queryCommand
|
|
119
|
+
.description('Execute a single query against a model or agent')
|
|
120
|
+
.argument('<target>', 'Query target (e.g., model/default, agent/my-agent)')
|
|
121
|
+
.argument('<message>', 'Message to send')
|
|
122
|
+
.action(async (target, message) => {
|
|
123
|
+
// Validate target format
|
|
124
|
+
if (!target.includes('/')) {
|
|
125
|
+
output.error('Invalid target format. Use: model/name or agent/name etc');
|
|
126
|
+
process.exit(1);
|
|
127
|
+
}
|
|
128
|
+
await runQuery(target, message);
|
|
129
|
+
});
|
|
130
|
+
return queryCommand;
|
|
131
|
+
}
|
|
@@ -2,15 +2,7 @@ import { Command } from 'commander';
|
|
|
2
2
|
import chalk from 'chalk';
|
|
3
3
|
import { execa } from 'execa';
|
|
4
4
|
import output from '../../lib/output.js';
|
|
5
|
-
import { isCommandAvailable } from '../../lib/commandUtils.js';
|
|
6
5
|
async function listRoutes() {
|
|
7
|
-
// Check if kubectl is installed
|
|
8
|
-
const kubectlInstalled = await isCommandAvailable('kubectl');
|
|
9
|
-
if (!kubectlInstalled) {
|
|
10
|
-
output.error('kubectl is not installed. please install kubectl first:');
|
|
11
|
-
output.info('https://kubernetes.io/docs/tasks/tools/');
|
|
12
|
-
process.exit(1);
|
|
13
|
-
}
|
|
14
6
|
const namespace = 'ark-system';
|
|
15
7
|
const port = 8080;
|
|
16
8
|
const portSuffix = `:${port}`;
|
|
@@ -90,7 +82,7 @@ async function listRoutes() {
|
|
|
90
82
|
process.exit(1);
|
|
91
83
|
}
|
|
92
84
|
}
|
|
93
|
-
export function createRoutesCommand() {
|
|
85
|
+
export function createRoutesCommand(_) {
|
|
94
86
|
const command = new Command('routes');
|
|
95
87
|
command
|
|
96
88
|
.description('show available gateway routes and their urls')
|
|
@@ -1,3 +1,4 @@
|
|
|
1
1
|
import { Command } from 'commander';
|
|
2
|
-
|
|
3
|
-
export declare function
|
|
2
|
+
import type { ArkConfig } from '../../lib/config.js';
|
|
3
|
+
export declare function checkStatus(config: ArkConfig): Promise<void>;
|
|
4
|
+
export declare function createStatusCommand(config: ArkConfig): Command;
|
|
@@ -2,22 +2,249 @@ import { Command } from 'commander';
|
|
|
2
2
|
import chalk from 'chalk';
|
|
3
3
|
import ora from 'ora';
|
|
4
4
|
import { StatusChecker } from '../../components/statusChecker.js';
|
|
5
|
-
import {
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
5
|
+
import { StatusFormatter, } from '../../ui/statusFormatter.js';
|
|
6
|
+
/**
|
|
7
|
+
* Enrich service with formatted details including version/revision
|
|
8
|
+
*/
|
|
9
|
+
function enrichServiceDetails(service, _) {
|
|
10
|
+
const statusMap = {
|
|
11
|
+
healthy: { icon: '✓', text: 'healthy', color: 'green' },
|
|
12
|
+
unhealthy: { icon: '✗', text: 'unhealthy', color: 'red' },
|
|
13
|
+
warning: { icon: '⚠', text: 'warning', color: 'yellow' },
|
|
14
|
+
'not ready': { icon: '○', text: 'not ready', color: 'yellow' },
|
|
15
|
+
'not installed': { icon: '?', text: 'not installed', color: 'yellow' },
|
|
16
|
+
};
|
|
17
|
+
const statusInfo = statusMap[service.status] || {
|
|
18
|
+
icon: '?',
|
|
19
|
+
text: service.status,
|
|
20
|
+
color: 'yellow',
|
|
21
|
+
};
|
|
22
|
+
// Build details array
|
|
23
|
+
const details = [];
|
|
24
|
+
if (service.status === 'healthy') {
|
|
25
|
+
if (service.version)
|
|
26
|
+
details.push(service.version);
|
|
27
|
+
if (service.revision)
|
|
28
|
+
details.push(`revision ${service.revision}`);
|
|
29
|
+
}
|
|
30
|
+
if (service.details)
|
|
31
|
+
details.push(service.details);
|
|
32
|
+
// Build display name with formatting
|
|
33
|
+
let displayName = chalk.bold(service.name);
|
|
34
|
+
if (service.namespace) {
|
|
35
|
+
displayName += ` ${chalk.blue(service.namespace)}`;
|
|
36
|
+
}
|
|
37
|
+
if (service.isDev) {
|
|
38
|
+
displayName += ' (dev)';
|
|
39
|
+
}
|
|
40
|
+
return {
|
|
41
|
+
statusInfo,
|
|
42
|
+
displayName,
|
|
43
|
+
details: details.join(', '),
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
function buildStatusSections(data, config) {
|
|
47
|
+
const sections = [];
|
|
48
|
+
// Dependencies section
|
|
49
|
+
sections.push({
|
|
50
|
+
title: 'system dependencies:',
|
|
51
|
+
lines: data.dependencies.map((dep) => ({
|
|
52
|
+
icon: dep.installed ? '✓' : '✗',
|
|
53
|
+
iconColor: (dep.installed ? 'green' : 'red'),
|
|
54
|
+
status: dep.installed ? 'installed' : 'missing',
|
|
55
|
+
statusColor: (dep.installed ? 'green' : 'red'),
|
|
56
|
+
name: chalk.bold(dep.name),
|
|
57
|
+
details: dep.version || '',
|
|
58
|
+
subtext: dep.installed ? undefined : dep.details,
|
|
59
|
+
})),
|
|
60
|
+
});
|
|
61
|
+
// Cluster access section
|
|
62
|
+
const clusterLines = [];
|
|
63
|
+
if (data.clusterAccess) {
|
|
64
|
+
const contextName = data.clusterInfo?.context || 'kubernetes cluster';
|
|
65
|
+
const namespace = data.clusterInfo?.namespace || 'default';
|
|
66
|
+
// Add bold context name with blue namespace
|
|
67
|
+
const name = `${chalk.bold(contextName)} ${chalk.blue(namespace)}`;
|
|
68
|
+
const details = [];
|
|
69
|
+
if (data.clusterInfo?.type && data.clusterInfo.type !== 'unknown') {
|
|
70
|
+
details.push(data.clusterInfo.type);
|
|
71
|
+
}
|
|
72
|
+
if (data.clusterInfo?.ip) {
|
|
73
|
+
details.push(data.clusterInfo.ip);
|
|
74
|
+
}
|
|
75
|
+
clusterLines.push({
|
|
76
|
+
icon: '✓',
|
|
77
|
+
iconColor: 'green',
|
|
78
|
+
status: 'accessible',
|
|
79
|
+
statusColor: 'green',
|
|
80
|
+
name,
|
|
81
|
+
details: details.join(', '),
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
else {
|
|
85
|
+
clusterLines.push({
|
|
86
|
+
icon: '✗',
|
|
87
|
+
iconColor: 'red',
|
|
88
|
+
status: 'unreachable',
|
|
89
|
+
statusColor: 'red',
|
|
90
|
+
name: 'kubernetes cluster',
|
|
91
|
+
subtext: 'Install minikube: https://minikube.sigs.k8s.io/docs/start',
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
sections.push({ title: 'cluster access:', lines: clusterLines });
|
|
95
|
+
// Ark services section
|
|
96
|
+
if (data.clusterAccess) {
|
|
97
|
+
const serviceLines = data.services
|
|
98
|
+
.filter((s) => s.name !== 'ark-controller')
|
|
99
|
+
.map((service) => {
|
|
100
|
+
const { statusInfo, displayName, details } = enrichServiceDetails(service, config);
|
|
101
|
+
return {
|
|
102
|
+
icon: statusInfo.icon,
|
|
103
|
+
iconColor: statusInfo.color,
|
|
104
|
+
status: statusInfo.text,
|
|
105
|
+
statusColor: statusInfo.color,
|
|
106
|
+
name: displayName,
|
|
107
|
+
details: details,
|
|
108
|
+
};
|
|
109
|
+
});
|
|
110
|
+
sections.push({ title: 'ark services:', lines: serviceLines });
|
|
111
|
+
}
|
|
112
|
+
else {
|
|
113
|
+
sections.push({
|
|
114
|
+
title: 'ark services:',
|
|
115
|
+
lines: [
|
|
116
|
+
{
|
|
117
|
+
icon: '',
|
|
118
|
+
status: '',
|
|
119
|
+
name: 'Cannot check ARK services - cluster not accessible',
|
|
120
|
+
},
|
|
121
|
+
],
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
// Ark status section
|
|
125
|
+
const arkStatusLines = [];
|
|
126
|
+
if (!data.clusterAccess) {
|
|
127
|
+
arkStatusLines.push({
|
|
128
|
+
icon: '✗',
|
|
129
|
+
iconColor: 'red',
|
|
130
|
+
status: 'no cluster access',
|
|
131
|
+
statusColor: 'red',
|
|
132
|
+
name: '',
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
else {
|
|
136
|
+
const controller = data.services?.find((s) => s.name === 'ark-controller');
|
|
137
|
+
if (!controller) {
|
|
138
|
+
arkStatusLines.push({
|
|
139
|
+
icon: '○',
|
|
140
|
+
iconColor: 'yellow',
|
|
141
|
+
status: 'not ready',
|
|
142
|
+
statusColor: 'yellow',
|
|
143
|
+
name: 'ark-controller',
|
|
144
|
+
});
|
|
145
|
+
}
|
|
146
|
+
else {
|
|
147
|
+
const { statusInfo, displayName, details } = enrichServiceDetails(controller, config);
|
|
148
|
+
// Map service status to ark status display
|
|
149
|
+
const statusText = controller.status === 'healthy'
|
|
150
|
+
? 'ready'
|
|
151
|
+
: controller.status === 'not installed'
|
|
152
|
+
? 'not ready'
|
|
153
|
+
: controller.status;
|
|
154
|
+
arkStatusLines.push({
|
|
155
|
+
icon: statusInfo.icon,
|
|
156
|
+
iconColor: statusInfo.color,
|
|
157
|
+
status: statusText,
|
|
158
|
+
statusColor: statusInfo.color,
|
|
159
|
+
name: displayName,
|
|
160
|
+
details: details,
|
|
161
|
+
});
|
|
162
|
+
// Add version update status as separate line
|
|
163
|
+
if (controller.status === 'healthy' && controller.version && config) {
|
|
164
|
+
if (config.latestVersion === undefined) {
|
|
165
|
+
arkStatusLines.push({
|
|
166
|
+
icon: '?',
|
|
167
|
+
iconColor: 'yellow',
|
|
168
|
+
status: 'version check',
|
|
169
|
+
statusColor: 'yellow',
|
|
170
|
+
name: '',
|
|
171
|
+
details: 'unable to check for updates',
|
|
172
|
+
});
|
|
173
|
+
}
|
|
174
|
+
else {
|
|
175
|
+
// Use currentVersion from config if available, otherwise use controller.version
|
|
176
|
+
const currentVersion = config.currentVersion || controller.version;
|
|
177
|
+
if (currentVersion === config.latestVersion) {
|
|
178
|
+
arkStatusLines.push({
|
|
179
|
+
icon: '✓',
|
|
180
|
+
iconColor: 'green',
|
|
181
|
+
status: 'up to date',
|
|
182
|
+
statusColor: 'green',
|
|
183
|
+
name: '',
|
|
184
|
+
details: config.latestVersion,
|
|
185
|
+
});
|
|
186
|
+
}
|
|
187
|
+
else {
|
|
188
|
+
arkStatusLines.push({
|
|
189
|
+
icon: '↑',
|
|
190
|
+
iconColor: 'yellow',
|
|
191
|
+
status: 'update available',
|
|
192
|
+
statusColor: 'yellow',
|
|
193
|
+
name: '',
|
|
194
|
+
details: config.latestVersion,
|
|
195
|
+
});
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
// Add default model status
|
|
200
|
+
if (data.defaultModel) {
|
|
201
|
+
if (!data.defaultModel.exists) {
|
|
202
|
+
arkStatusLines.push({
|
|
203
|
+
icon: '○',
|
|
204
|
+
iconColor: 'yellow',
|
|
205
|
+
status: 'default model',
|
|
206
|
+
statusColor: 'yellow',
|
|
207
|
+
name: '',
|
|
208
|
+
details: 'not configured',
|
|
209
|
+
});
|
|
210
|
+
}
|
|
211
|
+
else if (data.defaultModel.available) {
|
|
212
|
+
arkStatusLines.push({
|
|
213
|
+
icon: '●',
|
|
214
|
+
iconColor: 'green',
|
|
215
|
+
status: 'default model',
|
|
216
|
+
statusColor: 'green',
|
|
217
|
+
name: '',
|
|
218
|
+
details: data.defaultModel.provider || 'configured',
|
|
219
|
+
});
|
|
220
|
+
}
|
|
221
|
+
else {
|
|
222
|
+
arkStatusLines.push({
|
|
223
|
+
icon: '●',
|
|
224
|
+
iconColor: 'yellow',
|
|
225
|
+
status: 'default model',
|
|
226
|
+
statusColor: 'yellow',
|
|
227
|
+
name: '',
|
|
228
|
+
details: 'not available',
|
|
229
|
+
});
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
sections.push({ title: 'ark status:', lines: arkStatusLines });
|
|
235
|
+
return sections;
|
|
236
|
+
}
|
|
237
|
+
export async function checkStatus(config) {
|
|
9
238
|
const spinner = ora('Checking system status').start();
|
|
10
239
|
try {
|
|
11
|
-
const configManager = new ConfigManager();
|
|
12
240
|
spinner.text = 'Checking system dependencies';
|
|
13
|
-
const
|
|
14
|
-
const arkClient = new ArkClient(apiBaseUrl);
|
|
15
|
-
const statusChecker = new StatusChecker(arkClient);
|
|
241
|
+
const statusChecker = new StatusChecker();
|
|
16
242
|
spinner.text = 'Testing cluster access';
|
|
17
243
|
spinner.text = 'Checking ARK services';
|
|
18
244
|
const statusData = await statusChecker.checkAll();
|
|
19
245
|
spinner.stop();
|
|
20
|
-
|
|
246
|
+
const sections = buildStatusSections(statusData, config);
|
|
247
|
+
StatusFormatter.printSections(sections);
|
|
21
248
|
process.exit(0);
|
|
22
249
|
}
|
|
23
250
|
catch (error) {
|
|
@@ -26,8 +253,10 @@ export async function checkStatus() {
|
|
|
26
253
|
process.exit(1);
|
|
27
254
|
}
|
|
28
255
|
}
|
|
29
|
-
export function createStatusCommand() {
|
|
256
|
+
export function createStatusCommand(config) {
|
|
30
257
|
const statusCommand = new Command('status');
|
|
31
|
-
statusCommand
|
|
258
|
+
statusCommand
|
|
259
|
+
.description('Check ARK system status')
|
|
260
|
+
.action(() => checkStatus(config));
|
|
32
261
|
return statusCommand;
|
|
33
262
|
}
|
|
@@ -43,7 +43,7 @@ async function listTargets(options) {
|
|
|
43
43
|
}
|
|
44
44
|
}
|
|
45
45
|
}
|
|
46
|
-
export function createTargetsCommand() {
|
|
46
|
+
export function createTargetsCommand(_) {
|
|
47
47
|
const targets = new Command('targets');
|
|
48
48
|
targets
|
|
49
49
|
.description('list available query targets (agents, teams, models, tools)')
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import { jest } from '@jest/globals';
|
|
2
|
+
import { Command } from 'commander';
|
|
3
|
+
const mockArkApiClient = {
|
|
4
|
+
getQueryTargets: jest.fn(),
|
|
5
|
+
};
|
|
6
|
+
const mockStart = jest.fn();
|
|
7
|
+
mockStart.mockResolvedValue(mockArkApiClient);
|
|
8
|
+
const mockArkApiProxy = jest.fn();
|
|
9
|
+
mockArkApiProxy.prototype = {
|
|
10
|
+
start: mockStart,
|
|
11
|
+
stop: jest.fn(),
|
|
12
|
+
};
|
|
13
|
+
jest.unstable_mockModule('../../lib/arkApiProxy.js', () => ({
|
|
14
|
+
ArkApiProxy: mockArkApiProxy,
|
|
15
|
+
}));
|
|
16
|
+
const mockOutput = {
|
|
17
|
+
warning: jest.fn(),
|
|
18
|
+
error: jest.fn(),
|
|
19
|
+
};
|
|
20
|
+
jest.unstable_mockModule('../../lib/output.js', () => ({
|
|
21
|
+
default: mockOutput,
|
|
22
|
+
}));
|
|
23
|
+
const mockExit = jest.spyOn(process, 'exit').mockImplementation((() => {
|
|
24
|
+
throw new Error('process.exit called');
|
|
25
|
+
}));
|
|
26
|
+
const mockConsoleLog = jest.spyOn(console, 'log').mockImplementation(() => { });
|
|
27
|
+
const { createTargetsCommand } = await import('./index.js');
|
|
28
|
+
describe('targets command', () => {
|
|
29
|
+
beforeEach(() => {
|
|
30
|
+
jest.clearAllMocks();
|
|
31
|
+
});
|
|
32
|
+
it('creates command with correct structure', () => {
|
|
33
|
+
const command = createTargetsCommand({});
|
|
34
|
+
expect(command).toBeInstanceOf(Command);
|
|
35
|
+
expect(command.name()).toBe('targets');
|
|
36
|
+
});
|
|
37
|
+
it('lists targets in text format', async () => {
|
|
38
|
+
const mockTargets = [
|
|
39
|
+
{ id: 'agent/gpt-assistant', type: 'agent', name: 'gpt-assistant' },
|
|
40
|
+
{ id: 'model/gpt-4', type: 'model', name: 'gpt-4' },
|
|
41
|
+
];
|
|
42
|
+
mockArkApiClient.getQueryTargets.mockResolvedValue(mockTargets);
|
|
43
|
+
const command = createTargetsCommand({});
|
|
44
|
+
await command.parseAsync(['node', 'test']);
|
|
45
|
+
expect(mockArkApiProxy.prototype.start).toHaveBeenCalled();
|
|
46
|
+
expect(mockArkApiClient.getQueryTargets).toHaveBeenCalled();
|
|
47
|
+
expect(mockConsoleLog).toHaveBeenCalledWith('agent/gpt-assistant');
|
|
48
|
+
expect(mockConsoleLog).toHaveBeenCalledWith('model/gpt-4');
|
|
49
|
+
expect(mockArkApiProxy.prototype.stop).toHaveBeenCalled();
|
|
50
|
+
});
|
|
51
|
+
it('lists targets in json format', async () => {
|
|
52
|
+
const mockTargets = [{ id: 'agent/gpt', type: 'agent', name: 'gpt' }];
|
|
53
|
+
mockArkApiClient.getQueryTargets.mockResolvedValue(mockTargets);
|
|
54
|
+
const command = createTargetsCommand({});
|
|
55
|
+
await command.parseAsync(['node', 'test', '-o', 'json']);
|
|
56
|
+
expect(mockConsoleLog).toHaveBeenCalledWith(JSON.stringify(mockTargets, null, 2));
|
|
57
|
+
});
|
|
58
|
+
it('filters targets by type', async () => {
|
|
59
|
+
const mockTargets = [
|
|
60
|
+
{ id: 'agent/gpt', type: 'agent', name: 'gpt' },
|
|
61
|
+
{ id: 'model/claude', type: 'model', name: 'claude' },
|
|
62
|
+
{ id: 'agent/helper', type: 'agent', name: 'helper' },
|
|
63
|
+
];
|
|
64
|
+
mockArkApiClient.getQueryTargets.mockResolvedValue(mockTargets);
|
|
65
|
+
const command = createTargetsCommand({});
|
|
66
|
+
await command.parseAsync(['node', 'test', '-t', 'agent']);
|
|
67
|
+
expect(mockConsoleLog).toHaveBeenCalledWith('agent/gpt');
|
|
68
|
+
expect(mockConsoleLog).toHaveBeenCalledWith('agent/helper');
|
|
69
|
+
expect(mockConsoleLog).not.toHaveBeenCalledWith('model/claude');
|
|
70
|
+
});
|
|
71
|
+
it('sorts targets by type then name', async () => {
|
|
72
|
+
const mockTargets = [
|
|
73
|
+
{ id: 'model/b', type: 'model', name: 'b' },
|
|
74
|
+
{ id: 'agent/z', type: 'agent', name: 'z' },
|
|
75
|
+
{ id: 'agent/a', type: 'agent', name: 'a' },
|
|
76
|
+
{ id: 'model/a', type: 'model', name: 'a' },
|
|
77
|
+
];
|
|
78
|
+
mockArkApiClient.getQueryTargets.mockResolvedValue(mockTargets);
|
|
79
|
+
const command = createTargetsCommand({});
|
|
80
|
+
await command.parseAsync(['node', 'test']);
|
|
81
|
+
// Check order of calls
|
|
82
|
+
const calls = mockConsoleLog.mock.calls.map((call) => call[0]);
|
|
83
|
+
expect(calls).toEqual(['agent/a', 'agent/z', 'model/a', 'model/b']);
|
|
84
|
+
});
|
|
85
|
+
it('shows warning when no targets', async () => {
|
|
86
|
+
mockArkApiClient.getQueryTargets.mockResolvedValue([]);
|
|
87
|
+
const command = createTargetsCommand({});
|
|
88
|
+
await command.parseAsync(['node', 'test']);
|
|
89
|
+
expect(mockOutput.warning).toHaveBeenCalledWith('no targets available');
|
|
90
|
+
});
|
|
91
|
+
it('handles errors and stops proxy', async () => {
|
|
92
|
+
mockArkApiClient.getQueryTargets.mockRejectedValue(new Error('API failed'));
|
|
93
|
+
const command = createTargetsCommand({});
|
|
94
|
+
await expect(command.parseAsync(['node', 'test'])).rejects.toThrow('process.exit called');
|
|
95
|
+
expect(mockOutput.error).toHaveBeenCalledWith('fetching targets:', 'API failed');
|
|
96
|
+
expect(mockExit).toHaveBeenCalledWith(1);
|
|
97
|
+
expect(mockArkApiProxy.prototype.stop).toHaveBeenCalled();
|
|
98
|
+
});
|
|
99
|
+
it('list subcommand works', async () => {
|
|
100
|
+
mockArkApiClient.getQueryTargets.mockResolvedValue([]);
|
|
101
|
+
const command = createTargetsCommand({});
|
|
102
|
+
await command.parseAsync(['node', 'test', 'list']);
|
|
103
|
+
expect(mockArkApiClient.getQueryTargets).toHaveBeenCalled();
|
|
104
|
+
});
|
|
105
|
+
});
|