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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (122) hide show
  1. package/dist/arkServices.d.ts +4 -12
  2. package/dist/arkServices.js +19 -34
  3. package/dist/arkServices.spec.d.ts +1 -0
  4. package/dist/arkServices.spec.js +24 -0
  5. package/dist/commands/agents/index.d.ts +2 -1
  6. package/dist/commands/agents/index.js +2 -7
  7. package/dist/commands/agents/index.spec.d.ts +1 -0
  8. package/dist/commands/agents/index.spec.js +67 -0
  9. package/dist/commands/chat/index.d.ts +2 -1
  10. package/dist/commands/chat/index.js +5 -21
  11. package/dist/commands/cluster/get.spec.d.ts +1 -0
  12. package/dist/commands/cluster/get.spec.js +92 -0
  13. package/dist/commands/cluster/index.d.ts +2 -1
  14. package/dist/commands/cluster/index.js +1 -1
  15. package/dist/commands/cluster/index.spec.d.ts +1 -0
  16. package/dist/commands/cluster/index.spec.js +24 -0
  17. package/dist/commands/completion/index.d.ts +2 -1
  18. package/dist/commands/completion/index.js +1 -1
  19. package/dist/commands/completion/index.spec.d.ts +1 -0
  20. package/dist/commands/completion/index.spec.js +34 -0
  21. package/dist/commands/config/index.d.ts +2 -1
  22. package/dist/commands/config/index.js +2 -2
  23. package/dist/commands/config/index.spec.d.ts +1 -0
  24. package/dist/commands/config/index.spec.js +78 -0
  25. package/dist/commands/dashboard/index.d.ts +2 -1
  26. package/dist/commands/dashboard/index.js +1 -1
  27. package/dist/commands/dev/index.d.ts +2 -1
  28. package/dist/commands/dev/index.js +1 -1
  29. package/dist/commands/dev/tool-generate.spec.d.ts +1 -0
  30. package/dist/commands/dev/tool-generate.spec.js +163 -0
  31. package/dist/commands/dev/tool.spec.d.ts +1 -0
  32. package/dist/commands/dev/tool.spec.js +48 -0
  33. package/dist/commands/generate/generators/project.js +22 -41
  34. package/dist/commands/generate/index.d.ts +2 -1
  35. package/dist/commands/generate/index.js +1 -1
  36. package/dist/commands/install/index.d.ts +4 -2
  37. package/dist/commands/install/index.js +215 -78
  38. package/dist/commands/install/index.spec.d.ts +1 -0
  39. package/dist/commands/install/index.spec.js +135 -0
  40. package/dist/commands/models/create.spec.d.ts +1 -0
  41. package/dist/commands/models/create.spec.js +125 -0
  42. package/dist/commands/models/index.d.ts +2 -1
  43. package/dist/commands/models/index.js +2 -7
  44. package/dist/commands/models/index.spec.d.ts +1 -0
  45. package/dist/commands/models/index.spec.js +76 -0
  46. package/dist/commands/routes/index.d.ts +2 -1
  47. package/dist/commands/routes/index.js +1 -9
  48. package/dist/commands/status/index.d.ts +3 -2
  49. package/dist/commands/status/index.js +210 -11
  50. package/dist/commands/targets/index.d.ts +2 -1
  51. package/dist/commands/targets/index.js +1 -1
  52. package/dist/commands/targets/index.spec.d.ts +1 -0
  53. package/dist/commands/targets/index.spec.js +105 -0
  54. package/dist/commands/teams/index.d.ts +2 -1
  55. package/dist/commands/teams/index.js +2 -7
  56. package/dist/commands/teams/index.spec.d.ts +1 -0
  57. package/dist/commands/teams/index.spec.js +70 -0
  58. package/dist/commands/tools/index.d.ts +2 -1
  59. package/dist/commands/tools/index.js +2 -7
  60. package/dist/commands/tools/index.spec.d.ts +1 -0
  61. package/dist/commands/tools/index.spec.js +70 -0
  62. package/dist/commands/uninstall/index.d.ts +2 -1
  63. package/dist/commands/uninstall/index.js +61 -38
  64. package/dist/commands/uninstall/index.spec.d.ts +1 -0
  65. package/dist/commands/uninstall/index.spec.js +117 -0
  66. package/dist/components/ChatUI.js +4 -4
  67. package/dist/components/statusChecker.d.ts +5 -12
  68. package/dist/components/statusChecker.js +172 -89
  69. package/dist/config.d.ts +3 -22
  70. package/dist/config.js +7 -151
  71. package/dist/index.js +22 -19
  72. package/dist/lib/arkServiceProxy.js +4 -2
  73. package/dist/lib/arkStatus.d.ts +5 -0
  74. package/dist/lib/arkStatus.js +61 -2
  75. package/dist/lib/arkStatus.spec.d.ts +1 -0
  76. package/dist/lib/arkStatus.spec.js +49 -0
  77. package/dist/lib/chatClient.js +1 -3
  78. package/dist/lib/cluster.js +11 -14
  79. package/dist/lib/cluster.spec.d.ts +1 -0
  80. package/dist/lib/cluster.spec.js +338 -0
  81. package/dist/lib/commandUtils.js +7 -7
  82. package/dist/lib/commands.d.ts +16 -0
  83. package/dist/lib/commands.js +29 -0
  84. package/dist/lib/commands.spec.d.ts +1 -0
  85. package/dist/lib/commands.spec.js +146 -0
  86. package/dist/lib/config.d.ts +2 -0
  87. package/dist/lib/config.js +6 -4
  88. package/dist/lib/config.spec.d.ts +1 -0
  89. package/dist/lib/config.spec.js +99 -0
  90. package/dist/lib/consts.d.ts +0 -1
  91. package/dist/lib/consts.js +0 -2
  92. package/dist/lib/consts.spec.d.ts +1 -0
  93. package/dist/lib/consts.spec.js +15 -0
  94. package/dist/lib/errors.js +1 -1
  95. package/dist/lib/errors.spec.d.ts +1 -0
  96. package/dist/lib/errors.spec.js +221 -0
  97. package/dist/lib/exec.d.ts +0 -4
  98. package/dist/lib/exec.js +0 -11
  99. package/dist/lib/output.spec.d.ts +1 -0
  100. package/dist/lib/output.spec.js +123 -0
  101. package/dist/lib/portUtils.d.ts +8 -0
  102. package/dist/lib/portUtils.js +39 -0
  103. package/dist/lib/startup.d.ts +5 -0
  104. package/dist/lib/startup.js +73 -0
  105. package/dist/lib/startup.spec.d.ts +1 -0
  106. package/dist/lib/startup.spec.js +168 -0
  107. package/dist/lib/types.d.ts +2 -0
  108. package/dist/ui/AgentSelector.d.ts +8 -0
  109. package/dist/ui/AgentSelector.js +53 -0
  110. package/dist/ui/MainMenu.d.ts +5 -1
  111. package/dist/ui/MainMenu.js +117 -54
  112. package/dist/ui/ModelSelector.d.ts +8 -0
  113. package/dist/ui/ModelSelector.js +53 -0
  114. package/dist/ui/TeamSelector.d.ts +8 -0
  115. package/dist/ui/TeamSelector.js +55 -0
  116. package/dist/ui/ToolSelector.d.ts +8 -0
  117. package/dist/ui/ToolSelector.js +53 -0
  118. package/dist/ui/statusFormatter.d.ts +22 -10
  119. package/dist/ui/statusFormatter.js +37 -109
  120. package/dist/ui/statusFormatter.spec.d.ts +1 -0
  121. package/dist/ui/statusFormatter.spec.js +58 -0
  122. package/package.json +3 -3
@@ -0,0 +1,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
+ });
@@ -1,2 +1,3 @@
1
1
  import { Command } from 'commander';
2
- export declare function createRoutesCommand(): Command;
2
+ import type { ArkConfig } from '../../lib/config.js';
3
+ export declare function createRoutesCommand(_: ArkConfig): Command;
@@ -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
- export declare function checkStatus(): Promise<void>;
3
- export declare function createStatusCommand(): Command;
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,219 @@ 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 { ConfigManager } from '../../config.js';
6
- import { ArkClient } from '../../lib/arkClient.js';
7
- import { StatusFormatter } from '../../ui/statusFormatter.js';
8
- export async function checkStatus() {
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
+ subtext: controller.status === 'healthy' && !data.defaultModelExists
162
+ ? '(no default model configured)'
163
+ : undefined,
164
+ });
165
+ // Add version update status as separate line
166
+ if (controller.status === 'healthy' && controller.version && config) {
167
+ if (config.latestVersion === undefined) {
168
+ arkStatusLines.push({
169
+ icon: '?',
170
+ iconColor: 'yellow',
171
+ status: 'version check',
172
+ statusColor: 'yellow',
173
+ name: '',
174
+ details: 'unable to check for updates',
175
+ });
176
+ }
177
+ else {
178
+ // Use currentVersion from config if available, otherwise use controller.version
179
+ const currentVersion = config.currentVersion || controller.version;
180
+ if (currentVersion === config.latestVersion) {
181
+ arkStatusLines.push({
182
+ icon: '✓',
183
+ iconColor: 'green',
184
+ status: 'up to date',
185
+ statusColor: 'green',
186
+ name: '',
187
+ details: config.latestVersion,
188
+ });
189
+ }
190
+ else {
191
+ arkStatusLines.push({
192
+ icon: '↑',
193
+ iconColor: 'yellow',
194
+ status: 'update available',
195
+ statusColor: 'yellow',
196
+ name: '',
197
+ details: config.latestVersion,
198
+ });
199
+ }
200
+ }
201
+ }
202
+ }
203
+ }
204
+ sections.push({ title: 'ark status:', lines: arkStatusLines });
205
+ return sections;
206
+ }
207
+ export async function checkStatus(config) {
9
208
  const spinner = ora('Checking system status').start();
10
209
  try {
11
- const configManager = new ConfigManager();
12
210
  spinner.text = 'Checking system dependencies';
13
- const apiBaseUrl = await configManager.getApiBaseUrl();
14
- const arkClient = new ArkClient(apiBaseUrl);
15
- const statusChecker = new StatusChecker(arkClient);
211
+ const statusChecker = new StatusChecker();
16
212
  spinner.text = 'Testing cluster access';
17
213
  spinner.text = 'Checking ARK services';
18
214
  const statusData = await statusChecker.checkAll();
19
215
  spinner.stop();
20
- StatusFormatter.printStatus(statusData);
216
+ const sections = buildStatusSections(statusData, config);
217
+ StatusFormatter.printSections(sections);
21
218
  process.exit(0);
22
219
  }
23
220
  catch (error) {
@@ -26,8 +223,10 @@ export async function checkStatus() {
26
223
  process.exit(1);
27
224
  }
28
225
  }
29
- export function createStatusCommand() {
226
+ export function createStatusCommand(config) {
30
227
  const statusCommand = new Command('status');
31
- statusCommand.description('Check ARK system status').action(checkStatus);
228
+ statusCommand
229
+ .description('Check ARK system status')
230
+ .action(() => checkStatus(config));
32
231
  return statusCommand;
33
232
  }
@@ -1,2 +1,3 @@
1
1
  import { Command } from 'commander';
2
- export declare function createTargetsCommand(): Command;
2
+ import type { ArkConfig } from '../../lib/config.js';
3
+ export declare function createTargetsCommand(_: ArkConfig): Command;
@@ -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
+ });
@@ -1,2 +1,3 @@
1
1
  import { Command } from 'commander';
2
- export declare function createTeamsCommand(): Command;
2
+ import type { ArkConfig } from '../../lib/config.js';
3
+ export declare function createTeamsCommand(_: ArkConfig): Command;
@@ -24,16 +24,11 @@ async function listTeams(options) {
24
24
  }
25
25
  }
26
26
  catch (error) {
27
- if (error instanceof Error && error.message.includes('the server doesn\'t have a resource type')) {
28
- output.error('Team CRDs not installed. Is the ARK controller running?');
29
- }
30
- else {
31
- output.error(`Failed to list teams: ${error instanceof Error ? error.message : 'Unknown error'}`);
32
- }
27
+ output.error('fetching teams:', error instanceof Error ? error.message : error);
33
28
  process.exit(1);
34
29
  }
35
30
  }
36
- export function createTeamsCommand() {
31
+ export function createTeamsCommand(_) {
37
32
  const teamsCommand = new Command('teams');
38
33
  teamsCommand
39
34
  .description('List available teams')
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,70 @@
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 mockExit = jest.spyOn(process, 'exit').mockImplementation((() => {
15
+ throw new Error('process.exit called');
16
+ }));
17
+ const mockConsoleLog = jest.spyOn(console, 'log').mockImplementation(() => { });
18
+ const { createTeamsCommand } = await import('./index.js');
19
+ describe('teams command', () => {
20
+ beforeEach(() => {
21
+ jest.clearAllMocks();
22
+ });
23
+ it('creates command with correct structure', () => {
24
+ const command = createTeamsCommand({});
25
+ expect(command).toBeInstanceOf(Command);
26
+ expect(command.name()).toBe('teams');
27
+ });
28
+ it('lists teams in text format', async () => {
29
+ const mockTeams = {
30
+ items: [
31
+ { metadata: { name: 'engineering' } },
32
+ { metadata: { name: 'data-science' } },
33
+ ],
34
+ };
35
+ mockExeca.mockResolvedValue({ stdout: JSON.stringify(mockTeams) });
36
+ const command = createTeamsCommand({});
37
+ await command.parseAsync(['node', 'test']);
38
+ expect(mockExeca).toHaveBeenCalledWith('kubectl', ['get', 'teams', '-o', 'json'], { stdio: 'pipe' });
39
+ expect(mockConsoleLog).toHaveBeenCalledWith('engineering');
40
+ expect(mockConsoleLog).toHaveBeenCalledWith('data-science');
41
+ });
42
+ it('lists teams in json format', async () => {
43
+ const mockTeams = {
44
+ items: [{ metadata: { name: 'engineering' } }],
45
+ };
46
+ mockExeca.mockResolvedValue({ stdout: JSON.stringify(mockTeams) });
47
+ const command = createTeamsCommand({});
48
+ await command.parseAsync(['node', 'test', '-o', 'json']);
49
+ expect(mockConsoleLog).toHaveBeenCalledWith(JSON.stringify(mockTeams.items, null, 2));
50
+ });
51
+ it('shows info when no teams', async () => {
52
+ mockExeca.mockResolvedValue({ stdout: JSON.stringify({ items: [] }) });
53
+ const command = createTeamsCommand({});
54
+ await command.parseAsync(['node', 'test']);
55
+ expect(mockOutput.info).toHaveBeenCalledWith('No teams found');
56
+ });
57
+ it('handles errors', async () => {
58
+ mockExeca.mockRejectedValue(new Error('kubectl failed'));
59
+ const command = createTeamsCommand({});
60
+ await expect(command.parseAsync(['node', 'test'])).rejects.toThrow('process.exit called');
61
+ expect(mockOutput.error).toHaveBeenCalledWith('fetching teams:', 'kubectl failed');
62
+ expect(mockExit).toHaveBeenCalledWith(1);
63
+ });
64
+ it('list subcommand works', async () => {
65
+ mockExeca.mockResolvedValue({ stdout: JSON.stringify({ items: [] }) });
66
+ const command = createTeamsCommand({});
67
+ await command.parseAsync(['node', 'test', 'list']);
68
+ expect(mockExeca).toHaveBeenCalled();
69
+ });
70
+ });
@@ -1,2 +1,3 @@
1
1
  import { Command } from 'commander';
2
- export declare function createToolsCommand(): Command;
2
+ import type { ArkConfig } from '../../lib/config.js';
3
+ export declare function createToolsCommand(_: ArkConfig): Command;
@@ -24,16 +24,11 @@ async function listTools(options) {
24
24
  }
25
25
  }
26
26
  catch (error) {
27
- if (error instanceof Error && error.message.includes('the server doesn\'t have a resource type')) {
28
- output.error('MCPServer CRDs not installed. Is the ARK controller running?');
29
- }
30
- else {
31
- output.error(`Failed to list tools: ${error instanceof Error ? error.message : 'Unknown error'}`);
32
- }
27
+ output.error('fetching tools:', error instanceof Error ? error.message : error);
33
28
  process.exit(1);
34
29
  }
35
30
  }
36
- export function createToolsCommand() {
31
+ export function createToolsCommand(_) {
37
32
  const toolsCommand = new Command('tools');
38
33
  toolsCommand
39
34
  .description('List available tools')
@@ -0,0 +1 @@
1
+ export {};