@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.
Files changed (37) hide show
  1. package/dist/arkServices.js +7 -7
  2. package/dist/commands/agents/index.js +14 -0
  3. package/dist/commands/completion/index.js +1 -61
  4. package/dist/commands/dev/tool/shared.js +3 -1
  5. package/dist/commands/generate/generators/agent.js +2 -2
  6. package/dist/commands/generate/generators/team.js +2 -2
  7. package/dist/commands/install/index.js +1 -6
  8. package/dist/commands/models/index.js +15 -0
  9. package/dist/commands/models/index.spec.js +20 -0
  10. package/dist/commands/query/index.js +9 -116
  11. package/dist/commands/query/index.spec.d.ts +1 -0
  12. package/dist/commands/query/index.spec.js +53 -0
  13. package/dist/commands/status/index.d.ts +2 -3
  14. package/dist/commands/status/index.js +36 -17
  15. package/dist/commands/targets/index.js +26 -19
  16. package/dist/commands/targets/index.spec.js +95 -46
  17. package/dist/commands/teams/index.js +15 -0
  18. package/dist/commands/uninstall/index.js +0 -5
  19. package/dist/components/statusChecker.d.ts +2 -2
  20. package/dist/index.js +1 -3
  21. package/dist/lib/chatClient.js +70 -76
  22. package/dist/lib/config.d.ts +0 -2
  23. package/dist/lib/executeQuery.d.ts +20 -0
  24. package/dist/lib/executeQuery.js +135 -0
  25. package/dist/lib/executeQuery.spec.d.ts +1 -0
  26. package/dist/lib/executeQuery.spec.js +170 -0
  27. package/dist/lib/nextSteps.js +1 -1
  28. package/dist/lib/queryRunner.d.ts +22 -0
  29. package/dist/lib/queryRunner.js +142 -0
  30. package/dist/lib/startup.d.ts +1 -1
  31. package/dist/lib/startup.js +25 -31
  32. package/dist/lib/startup.spec.js +29 -45
  33. package/dist/lib/types.d.ts +70 -0
  34. package/dist/lib/versions.d.ts +23 -0
  35. package/dist/lib/versions.js +51 -0
  36. package/dist/ui/MainMenu.js +15 -11
  37. package/package.json +1 -2
@@ -59,7 +59,7 @@ export const arkServices = {
59
59
  'ark-controller': {
60
60
  name: 'ark-controller',
61
61
  helmReleaseName: 'ark-controller',
62
- description: 'Core ARK controller for managing AI resources',
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: 'ARK API service for interacting with ARK resources',
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: 'Web-based dashboard for ARK',
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: 'ARK API agent-to-agent communication service',
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: 'MCP (Model Context Protocol) services for ARK',
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 cluster access',
121
- enabled: true,
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 dev docs generate install models query routes status targets teams tools uninstall help"
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 || helmError.message || 'Unknown error';
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
- 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
- }
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
- // Validate target format
124
- if (!target.includes('/')) {
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 runQuery(target, message);
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
- 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
+ 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, config) {
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, config);
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, config);
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' && controller.version && config) {
164
- if (config.latestVersion === undefined) {
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 check',
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
- // Use currentVersion from config if available, otherwise use controller.version
176
- const currentVersion = config.currentVersion || controller.version;
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: config.latestVersion,
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: config.latestVersion,
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(config) {
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
- const statusData = await statusChecker.checkAll();
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, config);
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(config) {
275
+ export function createStatusCommand() {
257
276
  const statusCommand = new Command('status');
258
277
  statusCommand
259
278
  .description('Check ARK system status')
260
- .action(() => checkStatus(config));
279
+ .action(() => checkStatus());
261
280
  return statusCommand;
262
281
  }