@agents-at-scale/ark 0.1.35-rc2 → 0.1.36-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.js +7 -7
- package/dist/commands/agents/index.js +14 -0
- package/dist/commands/completion/index.js +1 -61
- package/dist/commands/dev/tool/shared.js +3 -1
- package/dist/commands/generate/generators/agent.js +2 -2
- package/dist/commands/generate/generators/team.js +2 -2
- package/dist/commands/install/index.js +1 -6
- package/dist/commands/models/index.js +15 -0
- package/dist/commands/models/index.spec.js +20 -0
- package/dist/commands/query/index.js +9 -116
- package/dist/commands/query/index.spec.d.ts +1 -0
- package/dist/commands/query/index.spec.js +53 -0
- package/dist/commands/status/index.d.ts +2 -3
- package/dist/commands/status/index.js +36 -17
- package/dist/commands/targets/index.js +26 -19
- package/dist/commands/targets/index.spec.js +95 -46
- package/dist/commands/teams/index.js +15 -0
- package/dist/commands/uninstall/index.js +0 -5
- package/dist/components/statusChecker.d.ts +2 -2
- package/dist/index.js +1 -3
- package/dist/lib/chatClient.js +70 -76
- package/dist/lib/config.d.ts +0 -2
- package/dist/lib/executeQuery.d.ts +20 -0
- package/dist/lib/executeQuery.js +135 -0
- package/dist/lib/executeQuery.spec.d.ts +1 -0
- package/dist/lib/executeQuery.spec.js +170 -0
- package/dist/lib/nextSteps.js +1 -1
- package/dist/lib/queryRunner.d.ts +22 -0
- package/dist/lib/queryRunner.js +142 -0
- package/dist/lib/startup.d.ts +1 -1
- package/dist/lib/startup.js +25 -31
- package/dist/lib/startup.spec.js +29 -45
- package/dist/lib/types.d.ts +70 -0
- package/dist/lib/versions.d.ts +23 -0
- package/dist/lib/versions.js +51 -0
- package/dist/ui/MainMenu.js +15 -11
- package/package.json +1 -2
package/dist/arkServices.js
CHANGED
|
@@ -59,7 +59,7 @@ export const arkServices = {
|
|
|
59
59
|
'ark-controller': {
|
|
60
60
|
name: 'ark-controller',
|
|
61
61
|
helmReleaseName: 'ark-controller',
|
|
62
|
-
description: 'Core
|
|
62
|
+
description: 'Core Ark controller for managing AI resources',
|
|
63
63
|
enabled: true,
|
|
64
64
|
namespace: 'ark-system',
|
|
65
65
|
chartPath: `${REGISTRY_BASE}/ark-controller`,
|
|
@@ -70,7 +70,7 @@ export const arkServices = {
|
|
|
70
70
|
'ark-api': {
|
|
71
71
|
name: 'ark-api',
|
|
72
72
|
helmReleaseName: 'ark-api',
|
|
73
|
-
description: '
|
|
73
|
+
description: 'API layer for interacting with Ark resources',
|
|
74
74
|
enabled: true,
|
|
75
75
|
// namespace: undefined - uses current context namespace
|
|
76
76
|
chartPath: `${REGISTRY_BASE}/ark-api`,
|
|
@@ -84,7 +84,7 @@ export const arkServices = {
|
|
|
84
84
|
'ark-dashboard': {
|
|
85
85
|
name: 'ark-dashboard',
|
|
86
86
|
helmReleaseName: 'ark-dashboard',
|
|
87
|
-
description: '
|
|
87
|
+
description: 'Ark Dashboard',
|
|
88
88
|
enabled: true,
|
|
89
89
|
// namespace: undefined - uses current context namespace
|
|
90
90
|
chartPath: `${REGISTRY_BASE}/ark-dashboard`,
|
|
@@ -98,7 +98,7 @@ export const arkServices = {
|
|
|
98
98
|
'ark-api-a2a': {
|
|
99
99
|
name: 'ark-api-a2a',
|
|
100
100
|
helmReleaseName: 'ark-api-a2a',
|
|
101
|
-
description: '
|
|
101
|
+
description: 'Ark API agent-to-agent communication service',
|
|
102
102
|
enabled: false, // Disabled - not currently used
|
|
103
103
|
// namespace: undefined - uses current context namespace
|
|
104
104
|
// Note: This service might be installed as part of ark-api or separately
|
|
@@ -106,7 +106,7 @@ export const arkServices = {
|
|
|
106
106
|
'ark-mcp': {
|
|
107
107
|
name: 'ark-mcp',
|
|
108
108
|
helmReleaseName: 'ark-mcp',
|
|
109
|
-
description: '
|
|
109
|
+
description: 'Ark Model Context Protocol server',
|
|
110
110
|
enabled: true,
|
|
111
111
|
// namespace: undefined - uses current context namespace
|
|
112
112
|
chartPath: `${REGISTRY_BASE}/ark-mcp`,
|
|
@@ -117,8 +117,8 @@ export const arkServices = {
|
|
|
117
117
|
'localhost-gateway': {
|
|
118
118
|
name: 'localhost-gateway',
|
|
119
119
|
helmReleaseName: 'localhost-gateway',
|
|
120
|
-
description: 'Gateway for local
|
|
121
|
-
enabled:
|
|
120
|
+
description: 'Gateway for local development clusters',
|
|
121
|
+
enabled: false, // Disabled - not needed for most users
|
|
122
122
|
namespace: 'ark-system',
|
|
123
123
|
chartPath: `${REGISTRY_BASE}/localhost-gateway`,
|
|
124
124
|
installArgs: [],
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { Command } from 'commander';
|
|
2
2
|
import { execa } from 'execa';
|
|
3
3
|
import output from '../../lib/output.js';
|
|
4
|
+
import { executeQuery } from '../../lib/executeQuery.js';
|
|
4
5
|
async function listAgents(options) {
|
|
5
6
|
try {
|
|
6
7
|
// Use kubectl to get agents
|
|
@@ -47,5 +48,18 @@ export function createAgentsCommand(_) {
|
|
|
47
48
|
.action(async (options) => {
|
|
48
49
|
await listAgents(options);
|
|
49
50
|
});
|
|
51
|
+
// Add query subcommand
|
|
52
|
+
agentsCommand
|
|
53
|
+
.command('query')
|
|
54
|
+
.description('Query an agent')
|
|
55
|
+
.argument('<name>', 'Agent name')
|
|
56
|
+
.argument('<message>', 'Message to send')
|
|
57
|
+
.action(async (name, message) => {
|
|
58
|
+
await executeQuery({
|
|
59
|
+
targetType: 'agent',
|
|
60
|
+
targetName: name,
|
|
61
|
+
message,
|
|
62
|
+
});
|
|
63
|
+
});
|
|
50
64
|
return agentsCommand;
|
|
51
65
|
}
|
|
@@ -28,7 +28,7 @@ _ark_completion() {
|
|
|
28
28
|
|
|
29
29
|
case \${COMP_CWORD} in
|
|
30
30
|
1)
|
|
31
|
-
opts="agents chat cluster completion config dashboard
|
|
31
|
+
opts="agents chat cluster completion config dashboard docs generate install models query routes status targets teams tools uninstall help"
|
|
32
32
|
COMPREPLY=( $(compgen -W "\${opts}" -- \${cur}) )
|
|
33
33
|
return 0
|
|
34
34
|
;;
|
|
@@ -74,11 +74,6 @@ _ark_completion() {
|
|
|
74
74
|
COMPREPLY=( $(compgen -W "\${opts}" -- \${cur}) )
|
|
75
75
|
return 0
|
|
76
76
|
;;
|
|
77
|
-
dev)
|
|
78
|
-
opts="tool"
|
|
79
|
-
COMPREPLY=( $(compgen -W "\${opts}" -- \${cur}) )
|
|
80
|
-
return 0
|
|
81
|
-
;;
|
|
82
77
|
generate)
|
|
83
78
|
opts="agent marketplace mcp-server project query team"
|
|
84
79
|
COMPREPLY=( $(compgen -W "\${opts}" -- \${cur}) )
|
|
@@ -108,35 +103,6 @@ _ark_completion() {
|
|
|
108
103
|
;;
|
|
109
104
|
esac
|
|
110
105
|
;;
|
|
111
|
-
3)
|
|
112
|
-
case \${COMP_WORDS[1]} in
|
|
113
|
-
dev)
|
|
114
|
-
case \${prev} in
|
|
115
|
-
tool)
|
|
116
|
-
opts="check init generate"
|
|
117
|
-
COMPREPLY=( $(compgen -W "\${opts}" -- \${cur}) )
|
|
118
|
-
return 0
|
|
119
|
-
;;
|
|
120
|
-
esac
|
|
121
|
-
;;
|
|
122
|
-
esac
|
|
123
|
-
;;
|
|
124
|
-
4)
|
|
125
|
-
# Handle path completion for dev tool commands
|
|
126
|
-
case \${COMP_WORDS[1]} in
|
|
127
|
-
dev)
|
|
128
|
-
if [[ \${COMP_WORDS[2]} == "tool" ]]; then
|
|
129
|
-
case \${COMP_WORDS[3]} in
|
|
130
|
-
check|init|generate)
|
|
131
|
-
# Complete with directories
|
|
132
|
-
COMPREPLY=( $(compgen -d -- \${cur}) )
|
|
133
|
-
return 0
|
|
134
|
-
;;
|
|
135
|
-
esac
|
|
136
|
-
fi
|
|
137
|
-
;;
|
|
138
|
-
esac
|
|
139
|
-
;;
|
|
140
106
|
esac
|
|
141
107
|
}
|
|
142
108
|
|
|
@@ -169,7 +135,6 @@ _ark() {
|
|
|
169
135
|
'completion[Generate shell completion scripts]' \\
|
|
170
136
|
'config[Configuration management]' \\
|
|
171
137
|
'dashboard[Open ARK dashboard]' \\
|
|
172
|
-
'dev[Development tools for ARK]' \\
|
|
173
138
|
'docs[Open ARK documentation]' \\
|
|
174
139
|
'generate[Generate ARK resources]' \\
|
|
175
140
|
'install[Install ARK services]' \\
|
|
@@ -225,10 +190,6 @@ _ark() {
|
|
|
225
190
|
'list[List all available tools]' \\
|
|
226
191
|
'ls[List all available tools]'
|
|
227
192
|
;;
|
|
228
|
-
dev)
|
|
229
|
-
_values 'dev commands' \\
|
|
230
|
-
'tool[MCP tool development utilities]'
|
|
231
|
-
;;
|
|
232
193
|
generate)
|
|
233
194
|
_values 'generate types' \\
|
|
234
195
|
'agent[Generate a new agent]' \\
|
|
@@ -258,27 +219,6 @@ _ark() {
|
|
|
258
219
|
;;
|
|
259
220
|
esac
|
|
260
221
|
;;
|
|
261
|
-
args)
|
|
262
|
-
case \$words[2] in
|
|
263
|
-
dev)
|
|
264
|
-
if [[ \$words[3] == "tool" ]]; then
|
|
265
|
-
case \$words[4] in
|
|
266
|
-
check|init|generate)
|
|
267
|
-
# Complete with directories
|
|
268
|
-
_files -/
|
|
269
|
-
;;
|
|
270
|
-
*)
|
|
271
|
-
_values 'tool commands' \\
|
|
272
|
-
'check[Check the status of an MCP tool project]' \\
|
|
273
|
-
'init[Initialize an MCP tool project]' \\
|
|
274
|
-
'generate[Generate project files from templates]' \\
|
|
275
|
-
'clean[Remove template-generated files]'
|
|
276
|
-
;;
|
|
277
|
-
esac
|
|
278
|
-
fi
|
|
279
|
-
;;
|
|
280
|
-
esac
|
|
281
|
-
;;
|
|
282
222
|
esac
|
|
283
223
|
}
|
|
284
224
|
|
|
@@ -242,7 +242,9 @@ function processTemplateFile(templatePath, targetFileName, arkConfig, options, r
|
|
|
242
242
|
}
|
|
243
243
|
}
|
|
244
244
|
catch (helmError) {
|
|
245
|
-
const errorMsg = helmError.stderr ||
|
|
245
|
+
const errorMsg = helmError.stderr ||
|
|
246
|
+
helmError.message ||
|
|
247
|
+
'Unknown error';
|
|
246
248
|
throw new Error(`Failed to template ${targetFileName}: ${errorMsg}`);
|
|
247
249
|
}
|
|
248
250
|
return content;
|
|
@@ -144,11 +144,11 @@ class AgentGenerator {
|
|
|
144
144
|
console.log(chalk.gray(` 1. Review and customise the agent configuration`));
|
|
145
145
|
if (config.createQuery) {
|
|
146
146
|
console.log(chalk.gray(` 2. Review and customise the query configuration`));
|
|
147
|
-
console.log(chalk.gray(` 3. Deploy with: helm upgrade --install ${config.projectName}
|
|
147
|
+
console.log(chalk.gray(` 3. Deploy with: helm upgrade --install ${config.projectName} . --namespace ${config.projectName}`));
|
|
148
148
|
console.log(chalk.gray(` 4. Test with: kubectl get agents,queries`));
|
|
149
149
|
}
|
|
150
150
|
else {
|
|
151
|
-
console.log(chalk.gray(` 2. Deploy with: helm upgrade --install ${config.projectName}
|
|
151
|
+
console.log(chalk.gray(` 2. Deploy with: helm upgrade --install ${config.projectName} . --namespace ${config.projectName}`));
|
|
152
152
|
console.log(chalk.gray(` 3. Test with: kubectl get agents`));
|
|
153
153
|
}
|
|
154
154
|
}, 'Generating agent');
|
|
@@ -391,11 +391,11 @@ class TeamGenerator {
|
|
|
391
391
|
console.log(chalk.gray(` 1. Review and customise the team configuration`));
|
|
392
392
|
if (config.createQuery) {
|
|
393
393
|
console.log(chalk.gray(` 2. Review and customise the query configuration`));
|
|
394
|
-
console.log(chalk.gray(` 3. Deploy with: helm upgrade --install ${config.projectName}
|
|
394
|
+
console.log(chalk.gray(` 3. Deploy with: helm upgrade --install ${config.projectName} . --namespace ${config.projectName}`));
|
|
395
395
|
console.log(chalk.gray(` 4. Test with: kubectl get teams,queries`));
|
|
396
396
|
}
|
|
397
397
|
else {
|
|
398
|
-
console.log(chalk.gray(` 2. Deploy with: helm upgrade --install ${config.projectName}
|
|
398
|
+
console.log(chalk.gray(` 2. Deploy with: helm upgrade --install ${config.projectName} . --namespace ${config.projectName}`));
|
|
399
399
|
console.log(chalk.gray(` 3. Test with: kubectl get teams`));
|
|
400
400
|
}
|
|
401
401
|
}
|
|
@@ -4,7 +4,7 @@ import { execute } from '../../lib/commands.js';
|
|
|
4
4
|
import inquirer from 'inquirer';
|
|
5
5
|
import { showNoClusterError } from '../../lib/startup.js';
|
|
6
6
|
import output from '../../lib/output.js';
|
|
7
|
-
import { getInstallableServices, arkDependencies } from '../../arkServices.js';
|
|
7
|
+
import { getInstallableServices, arkDependencies, } from '../../arkServices.js';
|
|
8
8
|
import { isArkReady } from '../../lib/arkStatus.js';
|
|
9
9
|
import { printNextSteps } from '../../lib/nextSteps.js';
|
|
10
10
|
import ora from 'ora';
|
|
@@ -37,11 +37,6 @@ export async function installArk(config, serviceName, options = {}) {
|
|
|
37
37
|
const clusterInfo = config.clusterInfo;
|
|
38
38
|
// Show cluster info
|
|
39
39
|
output.success(`connected to cluster: ${chalk.bold(clusterInfo.context)}`);
|
|
40
|
-
output.info(`type: ${clusterInfo.type}`);
|
|
41
|
-
output.info(`namespace: ${clusterInfo.namespace}`);
|
|
42
|
-
if (clusterInfo.ip) {
|
|
43
|
-
output.info(`ip: ${clusterInfo.ip}`);
|
|
44
|
-
}
|
|
45
40
|
console.log(); // Add blank line after cluster info
|
|
46
41
|
// If a specific service is requested, install only that service
|
|
47
42
|
if (serviceName) {
|
|
@@ -2,6 +2,7 @@ import { Command } from 'commander';
|
|
|
2
2
|
import { execa } from 'execa';
|
|
3
3
|
import output from '../../lib/output.js';
|
|
4
4
|
import { createModel } from './create.js';
|
|
5
|
+
import { executeQuery } from '../../lib/executeQuery.js';
|
|
5
6
|
async function listModels(options) {
|
|
6
7
|
try {
|
|
7
8
|
// Use kubectl to get models
|
|
@@ -56,5 +57,19 @@ export function createModelsCommand(_) {
|
|
|
56
57
|
await createModel(name);
|
|
57
58
|
});
|
|
58
59
|
modelsCommand.addCommand(createCommand);
|
|
60
|
+
// Add query command
|
|
61
|
+
const queryCommand = new Command('query');
|
|
62
|
+
queryCommand
|
|
63
|
+
.description('Query a model')
|
|
64
|
+
.argument('<name>', 'Model name (e.g., default)')
|
|
65
|
+
.argument('<message>', 'Message to send')
|
|
66
|
+
.action(async (name, message) => {
|
|
67
|
+
await executeQuery({
|
|
68
|
+
targetType: 'model',
|
|
69
|
+
targetName: name,
|
|
70
|
+
message,
|
|
71
|
+
});
|
|
72
|
+
});
|
|
73
|
+
modelsCommand.addCommand(queryCommand);
|
|
59
74
|
return modelsCommand;
|
|
60
75
|
}
|
|
@@ -15,6 +15,11 @@ const mockCreateModel = jest.fn();
|
|
|
15
15
|
jest.unstable_mockModule('./create.js', () => ({
|
|
16
16
|
createModel: mockCreateModel,
|
|
17
17
|
}));
|
|
18
|
+
const mockExecuteQuery = jest.fn();
|
|
19
|
+
jest.unstable_mockModule('../../lib/executeQuery.js', () => ({
|
|
20
|
+
executeQuery: mockExecuteQuery,
|
|
21
|
+
parseTarget: jest.fn(),
|
|
22
|
+
}));
|
|
18
23
|
const mockExit = jest.spyOn(process, 'exit').mockImplementation((() => {
|
|
19
24
|
throw new Error('process.exit called');
|
|
20
25
|
}));
|
|
@@ -73,4 +78,19 @@ describe('models command', () => {
|
|
|
73
78
|
await command.parseAsync(['node', 'test', 'create', 'my-model']);
|
|
74
79
|
expect(mockCreateModel).toHaveBeenCalledWith('my-model');
|
|
75
80
|
});
|
|
81
|
+
it('query subcommand works', async () => {
|
|
82
|
+
const command = createModelsCommand({});
|
|
83
|
+
await command.parseAsync([
|
|
84
|
+
'node',
|
|
85
|
+
'test',
|
|
86
|
+
'query',
|
|
87
|
+
'default',
|
|
88
|
+
'Hello world',
|
|
89
|
+
]);
|
|
90
|
+
expect(mockExecuteQuery).toHaveBeenCalledWith({
|
|
91
|
+
targetType: 'model',
|
|
92
|
+
targetName: 'default',
|
|
93
|
+
message: 'Hello world',
|
|
94
|
+
});
|
|
95
|
+
});
|
|
76
96
|
});
|
|
@@ -1,118 +1,6 @@
|
|
|
1
1
|
import { Command } from 'commander';
|
|
2
|
-
import { execa } from 'execa';
|
|
3
|
-
import ora from 'ora';
|
|
4
2
|
import output from '../../lib/output.js';
|
|
5
|
-
|
|
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
|
-
}
|
|
3
|
+
import { executeQuery, parseTarget } from '../../lib/executeQuery.js';
|
|
116
4
|
export function createQueryCommand(_) {
|
|
117
5
|
const queryCommand = new Command('query');
|
|
118
6
|
queryCommand
|
|
@@ -120,12 +8,17 @@ export function createQueryCommand(_) {
|
|
|
120
8
|
.argument('<target>', 'Query target (e.g., model/default, agent/my-agent)')
|
|
121
9
|
.argument('<message>', 'Message to send')
|
|
122
10
|
.action(async (target, message) => {
|
|
123
|
-
//
|
|
124
|
-
|
|
11
|
+
// Parse and validate target format
|
|
12
|
+
const parsed = parseTarget(target);
|
|
13
|
+
if (!parsed) {
|
|
125
14
|
output.error('Invalid target format. Use: model/name or agent/name etc');
|
|
126
15
|
process.exit(1);
|
|
127
16
|
}
|
|
128
|
-
await
|
|
17
|
+
await executeQuery({
|
|
18
|
+
targetType: parsed.type,
|
|
19
|
+
targetName: parsed.name,
|
|
20
|
+
message,
|
|
21
|
+
});
|
|
129
22
|
});
|
|
130
23
|
return queryCommand;
|
|
131
24
|
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { jest } from '@jest/globals';
|
|
2
|
+
import { Command } from 'commander';
|
|
3
|
+
const mockExecuteQuery = jest.fn();
|
|
4
|
+
const mockParseTarget = jest.fn();
|
|
5
|
+
jest.unstable_mockModule('../../lib/executeQuery.js', () => ({
|
|
6
|
+
executeQuery: mockExecuteQuery,
|
|
7
|
+
parseTarget: mockParseTarget,
|
|
8
|
+
}));
|
|
9
|
+
const mockOutput = {
|
|
10
|
+
error: jest.fn(),
|
|
11
|
+
};
|
|
12
|
+
jest.unstable_mockModule('../../lib/output.js', () => ({
|
|
13
|
+
default: mockOutput,
|
|
14
|
+
}));
|
|
15
|
+
const mockExit = jest.spyOn(process, 'exit').mockImplementation((() => {
|
|
16
|
+
throw new Error('process.exit called');
|
|
17
|
+
}));
|
|
18
|
+
const { createQueryCommand } = await import('./index.js');
|
|
19
|
+
describe('createQueryCommand', () => {
|
|
20
|
+
beforeEach(() => {
|
|
21
|
+
jest.clearAllMocks();
|
|
22
|
+
});
|
|
23
|
+
it('should create a query command', () => {
|
|
24
|
+
const command = createQueryCommand({});
|
|
25
|
+
expect(command).toBeInstanceOf(Command);
|
|
26
|
+
expect(command.name()).toBe('query');
|
|
27
|
+
expect(command.description()).toBe('Execute a single query against a model or agent');
|
|
28
|
+
});
|
|
29
|
+
it('should parse and execute query with valid target', async () => {
|
|
30
|
+
mockParseTarget.mockReturnValue({
|
|
31
|
+
type: 'model',
|
|
32
|
+
name: 'default',
|
|
33
|
+
});
|
|
34
|
+
mockExecuteQuery.mockResolvedValue(undefined);
|
|
35
|
+
const command = createQueryCommand({});
|
|
36
|
+
await command.parseAsync(['node', 'test', 'model/default', 'Hello world']);
|
|
37
|
+
expect(mockParseTarget).toHaveBeenCalledWith('model/default');
|
|
38
|
+
expect(mockExecuteQuery).toHaveBeenCalledWith({
|
|
39
|
+
targetType: 'model',
|
|
40
|
+
targetName: 'default',
|
|
41
|
+
message: 'Hello world',
|
|
42
|
+
});
|
|
43
|
+
});
|
|
44
|
+
it('should error on invalid target format', async () => {
|
|
45
|
+
mockParseTarget.mockReturnValue(null);
|
|
46
|
+
const command = createQueryCommand({});
|
|
47
|
+
await expect(command.parseAsync(['node', 'test', 'invalid-target', 'Hello'])).rejects.toThrow('process.exit called');
|
|
48
|
+
expect(mockParseTarget).toHaveBeenCalledWith('invalid-target');
|
|
49
|
+
expect(mockExecuteQuery).not.toHaveBeenCalled();
|
|
50
|
+
expect(mockOutput.error).toHaveBeenCalledWith('Invalid target format. Use: model/name or agent/name etc');
|
|
51
|
+
expect(mockExit).toHaveBeenCalledWith(1);
|
|
52
|
+
});
|
|
53
|
+
});
|
|
@@ -1,4 +1,3 @@
|
|
|
1
1
|
import { Command } from 'commander';
|
|
2
|
-
|
|
3
|
-
export declare function
|
|
4
|
-
export declare function createStatusCommand(config: ArkConfig): Command;
|
|
2
|
+
export declare function checkStatus(): Promise<void>;
|
|
3
|
+
export declare function createStatusCommand(): Command;
|
|
@@ -3,10 +3,11 @@ import chalk from 'chalk';
|
|
|
3
3
|
import ora from 'ora';
|
|
4
4
|
import { StatusChecker } from '../../components/statusChecker.js';
|
|
5
5
|
import { StatusFormatter, } from '../../ui/statusFormatter.js';
|
|
6
|
+
import { fetchVersionInfo } from '../../lib/versions.js';
|
|
6
7
|
/**
|
|
7
8
|
* Enrich service with formatted details including version/revision
|
|
8
9
|
*/
|
|
9
|
-
function enrichServiceDetails(service
|
|
10
|
+
function enrichServiceDetails(service) {
|
|
10
11
|
const statusMap = {
|
|
11
12
|
healthy: { icon: '✓', text: 'healthy', color: 'green' },
|
|
12
13
|
unhealthy: { icon: '✗', text: 'unhealthy', color: 'red' },
|
|
@@ -43,7 +44,7 @@ function enrichServiceDetails(service, _) {
|
|
|
43
44
|
details: details.join(', '),
|
|
44
45
|
};
|
|
45
46
|
}
|
|
46
|
-
function buildStatusSections(data,
|
|
47
|
+
function buildStatusSections(data, versionInfo) {
|
|
47
48
|
const sections = [];
|
|
48
49
|
// Dependencies section
|
|
49
50
|
sections.push({
|
|
@@ -97,7 +98,7 @@ function buildStatusSections(data, config) {
|
|
|
97
98
|
const serviceLines = data.services
|
|
98
99
|
.filter((s) => s.name !== 'ark-controller')
|
|
99
100
|
.map((service) => {
|
|
100
|
-
const { statusInfo, displayName, details } = enrichServiceDetails(service
|
|
101
|
+
const { statusInfo, displayName, details } = enrichServiceDetails(service);
|
|
101
102
|
return {
|
|
102
103
|
icon: statusInfo.icon,
|
|
103
104
|
iconColor: statusInfo.color,
|
|
@@ -144,7 +145,7 @@ function buildStatusSections(data, config) {
|
|
|
144
145
|
});
|
|
145
146
|
}
|
|
146
147
|
else {
|
|
147
|
-
const { statusInfo, displayName, details } = enrichServiceDetails(controller
|
|
148
|
+
const { statusInfo, displayName, details } = enrichServiceDetails(controller);
|
|
148
149
|
// Map service status to ark status display
|
|
149
150
|
const statusText = controller.status === 'healthy'
|
|
150
151
|
? 'ready'
|
|
@@ -160,28 +161,42 @@ function buildStatusSections(data, config) {
|
|
|
160
161
|
details: details,
|
|
161
162
|
});
|
|
162
163
|
// Add version update status as separate line
|
|
163
|
-
if (controller.status === 'healthy' &&
|
|
164
|
-
|
|
164
|
+
if (controller.status === 'healthy' && versionInfo) {
|
|
165
|
+
const currentVersion = versionInfo.current || controller.version;
|
|
166
|
+
if (!currentVersion) {
|
|
167
|
+
// Version is unknown
|
|
165
168
|
arkStatusLines.push({
|
|
166
169
|
icon: '?',
|
|
167
170
|
iconColor: 'yellow',
|
|
168
|
-
status: 'version
|
|
171
|
+
status: 'version unknown',
|
|
172
|
+
statusColor: 'yellow',
|
|
173
|
+
name: '',
|
|
174
|
+
details: versionInfo.latest
|
|
175
|
+
? `latest: ${versionInfo.latest}`
|
|
176
|
+
: 'unable to determine version',
|
|
177
|
+
});
|
|
178
|
+
}
|
|
179
|
+
else if (versionInfo.latest === undefined) {
|
|
180
|
+
// Have current version but couldn't check for updates
|
|
181
|
+
arkStatusLines.push({
|
|
182
|
+
icon: '?',
|
|
183
|
+
iconColor: 'yellow',
|
|
184
|
+
status: `version ${currentVersion}`,
|
|
169
185
|
statusColor: 'yellow',
|
|
170
186
|
name: '',
|
|
171
187
|
details: 'unable to check for updates',
|
|
172
188
|
});
|
|
173
189
|
}
|
|
174
190
|
else {
|
|
175
|
-
//
|
|
176
|
-
|
|
177
|
-
if (currentVersion === config.latestVersion) {
|
|
191
|
+
// Have both current and latest versions
|
|
192
|
+
if (currentVersion === versionInfo.latest) {
|
|
178
193
|
arkStatusLines.push({
|
|
179
194
|
icon: '✓',
|
|
180
195
|
iconColor: 'green',
|
|
181
196
|
status: 'up to date',
|
|
182
197
|
statusColor: 'green',
|
|
183
198
|
name: '',
|
|
184
|
-
details:
|
|
199
|
+
details: versionInfo.latest,
|
|
185
200
|
});
|
|
186
201
|
}
|
|
187
202
|
else {
|
|
@@ -191,7 +206,7 @@ function buildStatusSections(data, config) {
|
|
|
191
206
|
status: 'update available',
|
|
192
207
|
statusColor: 'yellow',
|
|
193
208
|
name: '',
|
|
194
|
-
details:
|
|
209
|
+
details: `${currentVersion} → ${versionInfo.latest}`,
|
|
195
210
|
});
|
|
196
211
|
}
|
|
197
212
|
}
|
|
@@ -234,16 +249,20 @@ function buildStatusSections(data, config) {
|
|
|
234
249
|
sections.push({ title: 'ark status:', lines: arkStatusLines });
|
|
235
250
|
return sections;
|
|
236
251
|
}
|
|
237
|
-
export async function checkStatus(
|
|
252
|
+
export async function checkStatus() {
|
|
238
253
|
const spinner = ora('Checking system status').start();
|
|
239
254
|
try {
|
|
240
255
|
spinner.text = 'Checking system dependencies';
|
|
241
256
|
const statusChecker = new StatusChecker();
|
|
242
257
|
spinner.text = 'Testing cluster access';
|
|
243
258
|
spinner.text = 'Checking ARK services';
|
|
244
|
-
|
|
259
|
+
// Run status check and version fetch in parallel
|
|
260
|
+
const [statusData, versionInfo] = await Promise.all([
|
|
261
|
+
statusChecker.checkAll(),
|
|
262
|
+
fetchVersionInfo(),
|
|
263
|
+
]);
|
|
245
264
|
spinner.stop();
|
|
246
|
-
const sections = buildStatusSections(statusData,
|
|
265
|
+
const sections = buildStatusSections(statusData, versionInfo);
|
|
247
266
|
StatusFormatter.printSections(sections);
|
|
248
267
|
process.exit(0);
|
|
249
268
|
}
|
|
@@ -253,10 +272,10 @@ export async function checkStatus(config) {
|
|
|
253
272
|
process.exit(1);
|
|
254
273
|
}
|
|
255
274
|
}
|
|
256
|
-
export function createStatusCommand(
|
|
275
|
+
export function createStatusCommand() {
|
|
257
276
|
const statusCommand = new Command('status');
|
|
258
277
|
statusCommand
|
|
259
278
|
.description('Check ARK system status')
|
|
260
|
-
.action(() => checkStatus(
|
|
279
|
+
.action(() => checkStatus());
|
|
261
280
|
return statusCommand;
|
|
262
281
|
}
|