@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.
Files changed (130) 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 +24 -2
  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/docs/index.d.ts +4 -0
  34. package/dist/commands/docs/index.js +18 -0
  35. package/dist/commands/generate/generators/project.js +22 -41
  36. package/dist/commands/generate/index.d.ts +2 -1
  37. package/dist/commands/generate/index.js +1 -1
  38. package/dist/commands/install/index.d.ts +4 -2
  39. package/dist/commands/install/index.js +225 -90
  40. package/dist/commands/install/index.spec.d.ts +1 -0
  41. package/dist/commands/install/index.spec.js +143 -0
  42. package/dist/commands/models/create.spec.d.ts +1 -0
  43. package/dist/commands/models/create.spec.js +125 -0
  44. package/dist/commands/models/index.d.ts +2 -1
  45. package/dist/commands/models/index.js +2 -7
  46. package/dist/commands/models/index.spec.d.ts +1 -0
  47. package/dist/commands/models/index.spec.js +76 -0
  48. package/dist/commands/query/index.d.ts +3 -0
  49. package/dist/commands/query/index.js +131 -0
  50. package/dist/commands/routes/index.d.ts +2 -1
  51. package/dist/commands/routes/index.js +1 -9
  52. package/dist/commands/status/index.d.ts +3 -2
  53. package/dist/commands/status/index.js +240 -11
  54. package/dist/commands/targets/index.d.ts +2 -1
  55. package/dist/commands/targets/index.js +1 -1
  56. package/dist/commands/targets/index.spec.d.ts +1 -0
  57. package/dist/commands/targets/index.spec.js +105 -0
  58. package/dist/commands/teams/index.d.ts +2 -1
  59. package/dist/commands/teams/index.js +2 -7
  60. package/dist/commands/teams/index.spec.d.ts +1 -0
  61. package/dist/commands/teams/index.spec.js +70 -0
  62. package/dist/commands/tools/index.d.ts +2 -1
  63. package/dist/commands/tools/index.js +2 -7
  64. package/dist/commands/tools/index.spec.d.ts +1 -0
  65. package/dist/commands/tools/index.spec.js +70 -0
  66. package/dist/commands/uninstall/index.d.ts +2 -1
  67. package/dist/commands/uninstall/index.js +66 -44
  68. package/dist/commands/uninstall/index.spec.d.ts +1 -0
  69. package/dist/commands/uninstall/index.spec.js +125 -0
  70. package/dist/components/ChatUI.js +4 -4
  71. package/dist/components/statusChecker.d.ts +5 -12
  72. package/dist/components/statusChecker.js +193 -90
  73. package/dist/config.d.ts +3 -22
  74. package/dist/config.js +7 -151
  75. package/dist/index.js +26 -19
  76. package/dist/lib/arkServiceProxy.js +4 -2
  77. package/dist/lib/arkStatus.d.ts +5 -0
  78. package/dist/lib/arkStatus.js +61 -2
  79. package/dist/lib/arkStatus.spec.d.ts +1 -0
  80. package/dist/lib/arkStatus.spec.js +49 -0
  81. package/dist/lib/chatClient.js +1 -3
  82. package/dist/lib/cluster.js +11 -14
  83. package/dist/lib/cluster.spec.d.ts +1 -0
  84. package/dist/lib/cluster.spec.js +338 -0
  85. package/dist/lib/commandUtils.js +7 -7
  86. package/dist/lib/commands.d.ts +16 -0
  87. package/dist/lib/commands.js +29 -0
  88. package/dist/lib/commands.spec.d.ts +1 -0
  89. package/dist/lib/commands.spec.js +146 -0
  90. package/dist/lib/config.d.ts +4 -0
  91. package/dist/lib/config.js +6 -4
  92. package/dist/lib/config.spec.d.ts +1 -0
  93. package/dist/lib/config.spec.js +99 -0
  94. package/dist/lib/consts.d.ts +0 -1
  95. package/dist/lib/consts.js +0 -2
  96. package/dist/lib/consts.spec.d.ts +1 -0
  97. package/dist/lib/consts.spec.js +15 -0
  98. package/dist/lib/errors.js +1 -1
  99. package/dist/lib/errors.spec.d.ts +1 -0
  100. package/dist/lib/errors.spec.js +221 -0
  101. package/dist/lib/exec.d.ts +0 -4
  102. package/dist/lib/exec.js +0 -11
  103. package/dist/lib/nextSteps.d.ts +4 -0
  104. package/dist/lib/nextSteps.js +20 -0
  105. package/dist/lib/nextSteps.spec.d.ts +1 -0
  106. package/dist/lib/nextSteps.spec.js +59 -0
  107. package/dist/lib/output.spec.d.ts +1 -0
  108. package/dist/lib/output.spec.js +123 -0
  109. package/dist/lib/portUtils.d.ts +8 -0
  110. package/dist/lib/portUtils.js +39 -0
  111. package/dist/lib/startup.d.ts +9 -0
  112. package/dist/lib/startup.js +93 -0
  113. package/dist/lib/startup.spec.d.ts +1 -0
  114. package/dist/lib/startup.spec.js +168 -0
  115. package/dist/lib/types.d.ts +9 -0
  116. package/dist/ui/AgentSelector.d.ts +8 -0
  117. package/dist/ui/AgentSelector.js +53 -0
  118. package/dist/ui/MainMenu.d.ts +5 -1
  119. package/dist/ui/MainMenu.js +117 -54
  120. package/dist/ui/ModelSelector.d.ts +8 -0
  121. package/dist/ui/ModelSelector.js +53 -0
  122. package/dist/ui/TeamSelector.d.ts +8 -0
  123. package/dist/ui/TeamSelector.js +55 -0
  124. package/dist/ui/ToolSelector.d.ts +8 -0
  125. package/dist/ui/ToolSelector.js +53 -0
  126. package/dist/ui/statusFormatter.d.ts +22 -10
  127. package/dist/ui/statusFormatter.js +37 -109
  128. package/dist/ui/statusFormatter.spec.d.ts +1 -0
  129. package/dist/ui/statusFormatter.spec.js +58 -0
  130. package/package.json +3 -3
@@ -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 {};
@@ -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 { createToolsCommand } = await import('./index.js');
19
+ describe('tools command', () => {
20
+ beforeEach(() => {
21
+ jest.clearAllMocks();
22
+ });
23
+ it('creates command with correct structure', () => {
24
+ const command = createToolsCommand({});
25
+ expect(command).toBeInstanceOf(Command);
26
+ expect(command.name()).toBe('tools');
27
+ });
28
+ it('lists tools in text format', async () => {
29
+ const mockTools = {
30
+ items: [
31
+ { metadata: { name: 'github-mcp' } },
32
+ { metadata: { name: 'slack-mcp' } },
33
+ ],
34
+ };
35
+ mockExeca.mockResolvedValue({ stdout: JSON.stringify(mockTools) });
36
+ const command = createToolsCommand({});
37
+ await command.parseAsync(['node', 'test']);
38
+ expect(mockExeca).toHaveBeenCalledWith('kubectl', ['get', 'mcpservers', '-o', 'json'], { stdio: 'pipe' });
39
+ expect(mockConsoleLog).toHaveBeenCalledWith('github-mcp');
40
+ expect(mockConsoleLog).toHaveBeenCalledWith('slack-mcp');
41
+ });
42
+ it('lists tools in json format', async () => {
43
+ const mockTools = {
44
+ items: [{ metadata: { name: 'github-mcp' } }],
45
+ };
46
+ mockExeca.mockResolvedValue({ stdout: JSON.stringify(mockTools) });
47
+ const command = createToolsCommand({});
48
+ await command.parseAsync(['node', 'test', '-o', 'json']);
49
+ expect(mockConsoleLog).toHaveBeenCalledWith(JSON.stringify(mockTools.items, null, 2));
50
+ });
51
+ it('shows info when no tools', async () => {
52
+ mockExeca.mockResolvedValue({ stdout: JSON.stringify({ items: [] }) });
53
+ const command = createToolsCommand({});
54
+ await command.parseAsync(['node', 'test']);
55
+ expect(mockOutput.info).toHaveBeenCalledWith('No tools found');
56
+ });
57
+ it('handles errors', async () => {
58
+ mockExeca.mockRejectedValue(new Error('kubectl failed'));
59
+ const command = createToolsCommand({});
60
+ await expect(command.parseAsync(['node', 'test'])).rejects.toThrow('process.exit called');
61
+ expect(mockOutput.error).toHaveBeenCalledWith('fetching tools:', 'kubectl failed');
62
+ expect(mockExit).toHaveBeenCalledWith(1);
63
+ });
64
+ it('list subcommand works', async () => {
65
+ mockExeca.mockResolvedValue({ stdout: JSON.stringify({ items: [] }) });
66
+ const command = createToolsCommand({});
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 createUninstallCommand(): Command;
2
+ import type { ArkConfig } from '../../lib/config.js';
3
+ export declare function createUninstallCommand(config: ArkConfig): Command;
@@ -1,33 +1,25 @@
1
1
  import { Command } from 'commander';
2
2
  import chalk from 'chalk';
3
- import { execa } from 'execa';
3
+ import { execute } from '../../lib/commands.js';
4
4
  import inquirer from 'inquirer';
5
- import { isCommandAvailable } from '../../lib/commandUtils.js';
6
- import { getClusterInfo } from '../../lib/cluster.js';
5
+ import { showNoClusterError } from '../../lib/startup.js';
7
6
  import output from '../../lib/output.js';
8
7
  import { getInstallableServices } from '../../arkServices.js';
9
- async function uninstallArk(options = {}) {
10
- // Check if helm is installed
11
- const helmInstalled = await isCommandAvailable('helm');
12
- if (!helmInstalled) {
13
- output.error('helm is not installed. please install helm first:');
14
- output.info('https://helm.sh/docs/intro/install/');
15
- process.exit(1);
16
- }
17
- // Check if kubectl is installed
18
- const kubectlInstalled = await isCommandAvailable('kubectl');
19
- if (!kubectlInstalled) {
20
- output.error('kubectl is not installed. please install kubectl first:');
21
- output.info('https://kubernetes.io/docs/tasks/tools/');
22
- process.exit(1);
8
+ async function uninstallService(service, verbose = false) {
9
+ const helmArgs = ['uninstall', service.helmReleaseName, '--ignore-not-found'];
10
+ // Only add namespace flag if service has explicit namespace
11
+ if (service.namespace) {
12
+ helmArgs.push('--namespace', service.namespace);
23
13
  }
24
- // Check cluster connectivity
25
- const clusterInfo = await getClusterInfo();
26
- if (clusterInfo.error) {
27
- output.error('no kubernetes cluster detected');
28
- output.info('please ensure you have a running cluster and kubectl is configured.');
14
+ await execute('helm', helmArgs, { stdio: 'inherit' }, { verbose });
15
+ }
16
+ async function uninstallArk(config, serviceName, options = {}) {
17
+ // Check cluster connectivity from config
18
+ if (!config.clusterInfo) {
19
+ showNoClusterError();
29
20
  process.exit(1);
30
21
  }
22
+ const clusterInfo = config.clusterInfo;
31
23
  // Show cluster info
32
24
  output.success(`connected to cluster: ${chalk.bold(clusterInfo.context)}`);
33
25
  output.info(`type: ${clusterInfo.type}`);
@@ -36,34 +28,62 @@ async function uninstallArk(options = {}) {
36
28
  output.info(`ip: ${clusterInfo.ip}`);
37
29
  }
38
30
  console.log(); // Add blank line after cluster info
31
+ // If a specific service is requested, uninstall only that service
32
+ if (serviceName) {
33
+ const services = getInstallableServices();
34
+ const service = Object.values(services).find((s) => s.name === serviceName);
35
+ if (!service) {
36
+ output.error(`service '${serviceName}' not found`);
37
+ output.info('available services:');
38
+ for (const s of Object.values(services)) {
39
+ output.info(` ${s.name}`);
40
+ }
41
+ process.exit(1);
42
+ }
43
+ output.info(`uninstalling ${service.name}...`);
44
+ try {
45
+ await uninstallService(service, options.verbose);
46
+ output.success(`${service.name} uninstalled successfully`);
47
+ }
48
+ catch (error) {
49
+ output.error(`failed to uninstall ${service.name}`);
50
+ console.error(error);
51
+ process.exit(1);
52
+ }
53
+ return;
54
+ }
39
55
  // Get installable services and iterate through them in reverse order for clean uninstall
40
56
  const services = getInstallableServices();
41
57
  const serviceEntries = Object.entries(services).reverse();
42
58
  for (const [, service] of serviceEntries) {
43
- // Ask for confirmation
44
- const shouldUninstall = options.yes || (await inquirer.prompt([
45
- {
46
- type: 'confirm',
47
- name: 'shouldUninstall',
48
- message: `uninstall ${chalk.bold(service.name)}? ${service.description ? chalk.gray(`(${service.description.toLowerCase()})`) : ''}`,
49
- default: true,
50
- },
51
- ])).shouldUninstall;
59
+ let shouldUninstall = false;
60
+ try {
61
+ // Ask for confirmation
62
+ shouldUninstall =
63
+ options.yes ||
64
+ (await inquirer.prompt([
65
+ {
66
+ type: 'confirm',
67
+ name: 'shouldUninstall',
68
+ message: `uninstall ${chalk.bold(service.name)}? ${service.description ? chalk.gray(`(${service.description.toLowerCase()})`) : ''}`,
69
+ default: true,
70
+ },
71
+ ])).shouldUninstall;
72
+ }
73
+ catch (error) {
74
+ // Handle Ctrl-C gracefully
75
+ if (error && error.name === 'ExitPromptError') {
76
+ console.log('\nUninstallation cancelled');
77
+ process.exit(130); // Standard exit code for SIGINT
78
+ }
79
+ throw error;
80
+ }
52
81
  if (!shouldUninstall) {
53
82
  output.warning(`skipping ${service.name}`);
54
83
  continue;
55
84
  }
56
85
  try {
57
- // Uninstall the release
58
- await execa('helm', [
59
- 'uninstall',
60
- service.helmReleaseName,
61
- '--namespace',
62
- service.namespace,
63
- '--ignore-not-found',
64
- ], {
65
- stdio: 'inherit',
66
- });
86
+ await uninstallService(service, options.verbose);
67
87
  console.log(); // Add blank line after command output
68
88
  }
69
89
  catch {
@@ -72,13 +92,15 @@ async function uninstallArk(options = {}) {
72
92
  }
73
93
  }
74
94
  }
75
- export function createUninstallCommand() {
95
+ export function createUninstallCommand(config) {
76
96
  const command = new Command('uninstall');
77
97
  command
78
98
  .description('Uninstall ARK components using Helm')
99
+ .argument('[service]', 'specific service to uninstall, or all if omitted')
79
100
  .option('-y, --yes', 'automatically confirm all uninstallations')
80
- .action(async (options) => {
81
- await uninstallArk(options);
101
+ .option('-v, --verbose', 'show commands being executed')
102
+ .action(async (service, options) => {
103
+ await uninstallArk(config, service, options);
82
104
  });
83
105
  return command;
84
106
  }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,125 @@
1
+ import { jest } from '@jest/globals';
2
+ import { Command } from 'commander';
3
+ const mockExeca = jest.fn(() => Promise.resolve());
4
+ jest.unstable_mockModule('execa', () => ({
5
+ execa: mockExeca,
6
+ }));
7
+ const mockGetClusterInfo = jest.fn();
8
+ jest.unstable_mockModule('../../lib/cluster.js', () => ({
9
+ getClusterInfo: mockGetClusterInfo,
10
+ }));
11
+ const mockGetInstallableServices = jest.fn();
12
+ const mockArkServices = {};
13
+ jest.unstable_mockModule('../../arkServices.js', () => ({
14
+ getInstallableServices: mockGetInstallableServices,
15
+ arkServices: mockArkServices,
16
+ }));
17
+ const mockOutput = {
18
+ error: jest.fn(),
19
+ info: jest.fn(),
20
+ success: jest.fn(),
21
+ warning: jest.fn(),
22
+ };
23
+ jest.unstable_mockModule('../../lib/output.js', () => ({
24
+ default: mockOutput,
25
+ }));
26
+ const mockExit = jest.spyOn(process, 'exit').mockImplementation((() => {
27
+ throw new Error('process.exit called');
28
+ }));
29
+ jest.spyOn(console, 'log').mockImplementation(() => { });
30
+ jest.spyOn(console, 'error').mockImplementation(() => { });
31
+ const { createUninstallCommand } = await import('./index.js');
32
+ describe('uninstall command', () => {
33
+ const mockConfig = {
34
+ clusterInfo: {
35
+ context: 'test-cluster',
36
+ type: 'minikube',
37
+ namespace: 'default',
38
+ },
39
+ };
40
+ beforeEach(() => {
41
+ jest.clearAllMocks();
42
+ mockGetClusterInfo.mockResolvedValue({
43
+ context: 'test-cluster',
44
+ type: 'minikube',
45
+ namespace: 'default',
46
+ });
47
+ });
48
+ it('creates command with correct structure', () => {
49
+ const command = createUninstallCommand(mockConfig);
50
+ expect(command).toBeInstanceOf(Command);
51
+ expect(command.name()).toBe('uninstall');
52
+ });
53
+ it('uninstalls single service with correct helm parameters', async () => {
54
+ const mockService = {
55
+ name: 'ark-api',
56
+ helmReleaseName: 'ark-api',
57
+ namespace: 'ark-system',
58
+ };
59
+ mockGetInstallableServices.mockReturnValue({
60
+ 'ark-api': mockService,
61
+ });
62
+ const command = createUninstallCommand(mockConfig);
63
+ await command.parseAsync(['node', 'test', 'ark-api']);
64
+ expect(mockExeca).toHaveBeenCalledWith('helm', [
65
+ 'uninstall',
66
+ 'ark-api',
67
+ '--ignore-not-found',
68
+ '--namespace',
69
+ 'ark-system',
70
+ ], {
71
+ stdio: 'inherit',
72
+ });
73
+ expect(mockOutput.success).toHaveBeenCalledWith('ark-api uninstalled successfully');
74
+ });
75
+ it('shows error when service not found', async () => {
76
+ mockGetInstallableServices.mockReturnValue({
77
+ 'ark-api': { name: 'ark-api' },
78
+ 'ark-controller': { name: 'ark-controller' },
79
+ });
80
+ const command = createUninstallCommand(mockConfig);
81
+ await expect(command.parseAsync(['node', 'test', 'invalid-service'])).rejects.toThrow('process.exit called');
82
+ expect(mockOutput.error).toHaveBeenCalledWith("service 'invalid-service' not found");
83
+ expect(mockOutput.info).toHaveBeenCalledWith('available services:');
84
+ expect(mockOutput.info).toHaveBeenCalledWith(' ark-api');
85
+ expect(mockOutput.info).toHaveBeenCalledWith(' ark-controller');
86
+ expect(mockExit).toHaveBeenCalledWith(1);
87
+ });
88
+ it('handles service without namespace (uses current context)', async () => {
89
+ const mockService = {
90
+ name: 'ark-dashboard',
91
+ helmReleaseName: 'ark-dashboard',
92
+ // namespace is undefined - should use current context
93
+ };
94
+ mockGetInstallableServices.mockReturnValue({
95
+ 'ark-dashboard': mockService,
96
+ });
97
+ const command = createUninstallCommand(mockConfig);
98
+ await command.parseAsync(['node', 'test', 'ark-dashboard']);
99
+ // Should NOT include --namespace flag
100
+ expect(mockExeca).toHaveBeenCalledWith('helm', ['uninstall', 'ark-dashboard', '--ignore-not-found'], {
101
+ stdio: 'inherit',
102
+ });
103
+ });
104
+ it('handles helm uninstall error gracefully', async () => {
105
+ const mockService = {
106
+ name: 'ark-api',
107
+ helmReleaseName: 'ark-api',
108
+ namespace: 'ark-system',
109
+ };
110
+ mockGetInstallableServices.mockReturnValue({
111
+ 'ark-api': mockService,
112
+ });
113
+ mockExeca.mockRejectedValue(new Error('helm failed'));
114
+ const command = createUninstallCommand(mockConfig);
115
+ await expect(command.parseAsync(['node', 'test', 'ark-api'])).rejects.toThrow('process.exit called');
116
+ expect(mockOutput.error).toHaveBeenCalledWith('failed to uninstall ark-api');
117
+ expect(mockExit).toHaveBeenCalledWith(1);
118
+ });
119
+ it('exits when cluster not connected', async () => {
120
+ mockGetClusterInfo.mockResolvedValue({ error: true });
121
+ const command = createUninstallCommand({});
122
+ await expect(command.parseAsync(['node', 'test', 'ark-api'])).rejects.toThrow('process.exit called');
123
+ expect(mockExit).toHaveBeenCalledWith(1);
124
+ });
125
+ });
@@ -8,10 +8,10 @@ import { marked } from 'marked';
8
8
  // @ts-ignore - no types available
9
9
  import TerminalRenderer from 'marked-terminal';
10
10
  import { ChatClient, } from '../lib/chatClient.js';
11
- import { AgentSelector } from '../commands/agents/selector.js';
12
- import { ModelSelector } from '../commands/models/selector.js';
13
- import { TeamSelector } from '../commands/teams/selector.js';
14
- import { ToolSelector } from '../commands/tools/selector.js';
11
+ import { AgentSelector } from '../ui/AgentSelector.js';
12
+ import { ModelSelector } from '../ui/ModelSelector.js';
13
+ import { TeamSelector } from '../ui/TeamSelector.js';
14
+ import { ToolSelector } from '../ui/ToolSelector.js';
15
15
  // Generate a unique ID for messages
16
16
  let messageIdCounter = 0;
17
17
  const generateMessageId = () => {
@@ -1,5 +1,4 @@
1
1
  import { StatusData, CommandVersionConfig } from '../lib/types.js';
2
- import { ArkClient } from '../lib/arkClient.js';
3
2
  export declare const getNodeVersion: () => CommandVersionConfig;
4
3
  export declare const getNpmVersion: () => CommandVersionConfig;
5
4
  export declare const getKubectlVersion: () => CommandVersionConfig;
@@ -8,13 +7,15 @@ export declare const getHelmVersion: () => CommandVersionConfig;
8
7
  export declare const getMinikubeVersion: () => CommandVersionConfig;
9
8
  export declare const getKindVersion: () => CommandVersionConfig;
10
9
  export declare class StatusChecker {
11
- private arkClient;
12
- private kubernetesManager;
13
- constructor(arkClient: ArkClient);
14
10
  /**
15
11
  * Get version of a command
16
12
  */
17
13
  private getCommandVersion;
14
+ /**
15
+ * Check combined deployment and helm status
16
+ * Gets health from deployment, version/revision from Helm
17
+ */
18
+ private checkCombinedStatus;
18
19
  /**
19
20
  * Check deployment status
20
21
  */
@@ -23,14 +24,6 @@ export declare class StatusChecker {
23
24
  * Check helm release status (fallback for services without deployments)
24
25
  */
25
26
  private checkHelmStatus;
26
- /**
27
- * Return a "not installed" status for a service
28
- */
29
- private createNotInstalledStatus;
30
- /**
31
- * Check Kubernetes service health via pods and endpoints
32
- */
33
- private checkKubernetesService;
34
27
  /**
35
28
  * Check system dependencies
36
29
  */