@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
@@ -5,15 +5,15 @@ export interface ArkService {
5
5
  name: string;
6
6
  helmReleaseName: string;
7
7
  description: string;
8
- namespace: string;
9
- healthPath?: string;
10
- gatewayUrl?: string;
8
+ enabled: boolean;
9
+ namespace?: string;
11
10
  chartPath?: string;
12
11
  installArgs?: string[];
13
12
  k8sServiceName?: string;
14
13
  k8sServicePort?: number;
15
14
  k8sPortForwardLocalPort?: number;
16
15
  k8sDeploymentName?: string;
16
+ k8sDevDeploymentName?: string;
17
17
  }
18
18
  export interface ServiceCollection {
19
19
  [key: string]: ArkService;
@@ -37,14 +37,6 @@ export declare const arkDependencies: DependencyCollection;
37
37
  */
38
38
  export declare const arkServices: ServiceCollection;
39
39
  /**
40
- * Get services that can be installed via Helm charts
40
+ * Get services that can be installed via Helm charts (only enabled services)
41
41
  */
42
42
  export declare function getInstallableServices(): ServiceCollection;
43
- /**
44
- * Get services that can be checked for status
45
- */
46
- export declare function getStatusCheckableServices(): Record<string, string>;
47
- /**
48
- * Get health check path for a specific service
49
- */
50
- export declare function getHealthPath(serviceName: string): string;
@@ -1,7 +1,6 @@
1
1
  /**
2
2
  * Centralized ARK service definitions used by both install and status commands
3
3
  */
4
- const LOCALHOST_GATEWAY_PORT = 8080;
5
4
  const REGISTRY_BASE = 'oci://ghcr.io/mckinsey/agents-at-scale-ark/charts';
6
5
  /**
7
6
  * Dependencies that should be installed before ARK services
@@ -61,93 +60,79 @@ export const arkServices = {
61
60
  name: 'ark-controller',
62
61
  helmReleaseName: 'ark-controller',
63
62
  description: 'Core ARK controller for managing AI resources',
63
+ enabled: true,
64
64
  namespace: 'ark-system',
65
65
  chartPath: `${REGISTRY_BASE}/ark-controller`,
66
66
  installArgs: ['--create-namespace', '--set', 'rbac.enable=true'],
67
67
  k8sDeploymentName: 'ark-controller',
68
+ k8sDevDeploymentName: 'ark-controller-devspace',
68
69
  },
69
70
  'ark-api': {
70
71
  name: 'ark-api',
71
72
  helmReleaseName: 'ark-api',
72
73
  description: 'ARK API service for interacting with ARK resources',
73
- namespace: 'default',
74
- healthPath: '/health',
75
- gatewayUrl: `http://ark-api.127.0.0.1.nip.io:${LOCALHOST_GATEWAY_PORT}`,
74
+ enabled: true,
75
+ // namespace: undefined - uses current context namespace
76
76
  chartPath: `${REGISTRY_BASE}/ark-api`,
77
77
  installArgs: [],
78
78
  k8sServiceName: 'ark-api',
79
79
  k8sServicePort: 80,
80
- k8sPortForwardLocalPort: 34780,
81
80
  k8sDeploymentName: 'ark-api',
81
+ k8sDevDeploymentName: 'ark-api-devspace',
82
+ k8sPortForwardLocalPort: 34780,
82
83
  },
83
84
  'ark-dashboard': {
84
85
  name: 'ark-dashboard',
85
86
  helmReleaseName: 'ark-dashboard',
86
87
  description: 'Web-based dashboard for ARK',
87
- namespace: 'default',
88
- healthPath: '',
89
- gatewayUrl: `http://dashboard.127.0.0.1.nip.io:${LOCALHOST_GATEWAY_PORT}`,
88
+ enabled: true,
89
+ // namespace: undefined - uses current context namespace
90
90
  chartPath: `${REGISTRY_BASE}/ark-dashboard`,
91
91
  installArgs: [],
92
92
  k8sServiceName: 'ark-dashboard',
93
93
  k8sServicePort: 3000,
94
- k8sPortForwardLocalPort: 3274,
95
94
  k8sDeploymentName: 'ark-dashboard',
95
+ k8sDevDeploymentName: 'ark-dashboard-devspace',
96
+ k8sPortForwardLocalPort: 3274,
96
97
  },
97
98
  'ark-api-a2a': {
98
99
  name: 'ark-api-a2a',
99
100
  helmReleaseName: 'ark-api-a2a',
100
101
  description: 'ARK API agent-to-agent communication service',
101
- namespace: 'default',
102
- healthPath: '/health',
103
- gatewayUrl: `http://ark-api-a2a.127.0.0.1.nip.io:${LOCALHOST_GATEWAY_PORT}`,
102
+ enabled: false, // Disabled - not currently used
103
+ // namespace: undefined - uses current context namespace
104
104
  // Note: This service might be installed as part of ark-api or separately
105
105
  },
106
106
  'ark-mcp': {
107
107
  name: 'ark-mcp',
108
108
  helmReleaseName: 'ark-mcp',
109
109
  description: 'MCP (Model Context Protocol) services for ARK',
110
- namespace: 'default',
110
+ enabled: true,
111
+ // namespace: undefined - uses current context namespace
111
112
  chartPath: `${REGISTRY_BASE}/ark-mcp`,
112
113
  installArgs: [],
114
+ k8sDeploymentName: 'ark-mcp',
115
+ k8sDevDeploymentName: 'ark-mcp-devspace',
113
116
  },
114
117
  'localhost-gateway': {
115
118
  name: 'localhost-gateway',
116
119
  helmReleaseName: 'localhost-gateway',
117
120
  description: 'Gateway for local cluster access',
121
+ enabled: true,
118
122
  namespace: 'ark-system',
119
123
  chartPath: `${REGISTRY_BASE}/localhost-gateway`,
120
124
  installArgs: [],
121
125
  },
122
126
  };
123
127
  /**
124
- * Get services that can be installed via Helm charts
128
+ * Get services that can be installed via Helm charts (only enabled services)
125
129
  */
126
130
  export function getInstallableServices() {
127
131
  const installable = {};
128
132
  for (const [key, service] of Object.entries(arkServices)) {
129
- if (service.chartPath) {
133
+ if (service.enabled && service.chartPath) {
130
134
  installable[key] = service;
131
135
  }
132
136
  }
133
137
  return installable;
134
138
  }
135
- /**
136
- * Get services that can be checked for status
137
- */
138
- export function getStatusCheckableServices() {
139
- const statusServices = {};
140
- for (const [key, service] of Object.entries(arkServices)) {
141
- if (service.gatewayUrl) {
142
- statusServices[key] = service.gatewayUrl;
143
- }
144
- }
145
- return statusServices;
146
- }
147
- /**
148
- * Get health check path for a specific service
149
- */
150
- export function getHealthPath(serviceName) {
151
- const service = arkServices[serviceName];
152
- return service?.healthPath || '';
153
- }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,24 @@
1
+ import { arkDependencies, arkServices, getInstallableServices, } from './arkServices.js';
2
+ describe('arkServices', () => {
3
+ it('exports arkDependencies with expected structure', () => {
4
+ expect(arkDependencies).toBeDefined();
5
+ expect(arkDependencies['cert-manager']).toBeDefined();
6
+ expect(arkDependencies['cert-manager'].command).toBe('helm');
7
+ });
8
+ it('exports arkServices with expected structure', () => {
9
+ expect(arkServices).toBeDefined();
10
+ expect(arkServices['ark-controller']).toBeDefined();
11
+ expect(arkServices['ark-controller'].namespace).toBe('ark-system');
12
+ // User services should have undefined namespace (use current context)
13
+ expect(arkServices['ark-api'].namespace).toBeUndefined();
14
+ expect(arkServices['ark-dashboard'].namespace).toBeUndefined();
15
+ // System services should have explicit namespace
16
+ expect(arkServices['localhost-gateway'].namespace).toBe('ark-system');
17
+ });
18
+ it('getInstallableServices returns services with chartPath', () => {
19
+ const installable = getInstallableServices();
20
+ expect(installable['ark-controller']).toBeDefined();
21
+ expect(installable['ark-api']).toBeDefined();
22
+ expect(installable['ark-api-a2a']).toBeUndefined(); // no chartPath
23
+ });
24
+ });
@@ -1,2 +1,3 @@
1
1
  import { Command } from 'commander';
2
- export declare function createAgentsCommand(): Command;
2
+ import type { ArkConfig } from '../../lib/config.js';
3
+ export declare function createAgentsCommand(_: ArkConfig): Command;
@@ -25,16 +25,11 @@ async function listAgents(options) {
25
25
  }
26
26
  }
27
27
  catch (error) {
28
- if (error instanceof Error && error.message.includes('the server doesn\'t have a resource type')) {
29
- output.error('Agent CRDs not installed. Is the ARK controller running?');
30
- }
31
- else {
32
- output.error('fetching agents:', error instanceof Error ? error.message : error);
33
- }
28
+ output.error('fetching agents:', error instanceof Error ? error.message : error);
34
29
  process.exit(1);
35
30
  }
36
31
  }
37
- export function createAgentsCommand() {
32
+ export function createAgentsCommand(_) {
38
33
  const agentsCommand = new Command('agents');
39
34
  agentsCommand
40
35
  .description('list available agents')
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,67 @@
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
+ warning: 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 { createAgentsCommand } = await import('./index.js');
19
+ describe('agents command', () => {
20
+ beforeEach(() => {
21
+ jest.clearAllMocks();
22
+ });
23
+ it('creates command with correct structure', () => {
24
+ const command = createAgentsCommand({});
25
+ expect(command).toBeInstanceOf(Command);
26
+ expect(command.name()).toBe('agents');
27
+ });
28
+ it('lists agents in text format', async () => {
29
+ const mockAgents = {
30
+ items: [{ metadata: { name: 'agent1' } }, { metadata: { name: 'agent2' } }],
31
+ };
32
+ mockExeca.mockResolvedValue({ stdout: JSON.stringify(mockAgents) });
33
+ const command = createAgentsCommand({});
34
+ await command.parseAsync(['node', 'test']);
35
+ expect(mockExeca).toHaveBeenCalledWith('kubectl', ['get', 'agents', '-o', 'json'], { stdio: 'pipe' });
36
+ expect(mockConsoleLog).toHaveBeenCalledWith('agent1');
37
+ expect(mockConsoleLog).toHaveBeenCalledWith('agent2');
38
+ });
39
+ it('lists agents in json format', async () => {
40
+ const mockAgents = {
41
+ items: [{ metadata: { name: 'agent1' } }],
42
+ };
43
+ mockExeca.mockResolvedValue({ stdout: JSON.stringify(mockAgents) });
44
+ const command = createAgentsCommand({});
45
+ await command.parseAsync(['node', 'test', '-o', 'json']);
46
+ expect(mockConsoleLog).toHaveBeenCalledWith(JSON.stringify(mockAgents.items, null, 2));
47
+ });
48
+ it('shows warning when no agents', async () => {
49
+ mockExeca.mockResolvedValue({ stdout: JSON.stringify({ items: [] }) });
50
+ const command = createAgentsCommand({});
51
+ await command.parseAsync(['node', 'test']);
52
+ expect(mockOutput.warning).toHaveBeenCalledWith('no agents available');
53
+ });
54
+ it('handles errors', async () => {
55
+ mockExeca.mockRejectedValue(new Error('kubectl failed'));
56
+ const command = createAgentsCommand({});
57
+ await expect(command.parseAsync(['node', 'test'])).rejects.toThrow('process.exit called');
58
+ expect(mockOutput.error).toHaveBeenCalledWith('fetching agents:', 'kubectl failed');
59
+ expect(mockExit).toHaveBeenCalledWith(1);
60
+ });
61
+ it('list subcommand works', async () => {
62
+ mockExeca.mockResolvedValue({ stdout: JSON.stringify({ items: [] }) });
63
+ const command = createAgentsCommand({});
64
+ await command.parseAsync(['node', 'test', 'list']);
65
+ expect(mockExeca).toHaveBeenCalled();
66
+ });
67
+ });
@@ -1,2 +1,3 @@
1
1
  import { Command } from 'commander';
2
- export declare function createChatCommand(): Command;
2
+ import type { ArkConfig } from '../../lib/config.js';
3
+ export declare function createChatCommand(config: ArkConfig): Command;
@@ -3,32 +3,16 @@ import { Command } from 'commander';
3
3
  import { render } from 'ink';
4
4
  import ChatUI from '../../components/ChatUI.js';
5
5
  import { ArkApiProxy } from '../../lib/arkApiProxy.js';
6
- import { loadConfig } from '../../lib/config.js';
7
6
  import output from '../../lib/output.js';
8
- export function createChatCommand() {
7
+ export function createChatCommand(config) {
9
8
  const chatCommand = new Command('chat');
10
9
  chatCommand
11
10
  .description('Start an interactive chat session with ARK agents or models')
12
11
  .argument('[target]', 'Target to connect to (e.g., agent/sample-agent, model/default)')
13
- .option('-a, --agent <name>', 'Connect directly to a specific agent')
14
- .option('-m, --model <name>', 'Connect directly to a specific model')
15
- .action(async (targetArg, options) => {
16
- // Determine initial target from argument or options
17
- let initialTargetId;
18
- if (targetArg) {
19
- // Direct target argument (e.g., "agent/sample-agent")
20
- initialTargetId = targetArg;
21
- }
22
- else if (options.agent) {
23
- // Agent option
24
- initialTargetId = `agent/${options.agent}`;
25
- }
26
- else if (options.model) {
27
- // Model option
28
- initialTargetId = `model/${options.model}`;
29
- }
30
- // Load config
31
- const config = loadConfig();
12
+ .action(async (targetArg) => {
13
+ // Direct target argument (e.g., "agent/sample-agent")
14
+ const initialTargetId = targetArg;
15
+ // Config is passed from main
32
16
  // Initialize proxy first - no spinner, just let ChatUI handle loading state
33
17
  try {
34
18
  const proxy = new ArkApiProxy();
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,92 @@
1
+ import { jest } from '@jest/globals';
2
+ import { Command } from 'commander';
3
+ const mockGetClusterInfo = jest.fn();
4
+ jest.unstable_mockModule('../../lib/cluster.js', () => ({
5
+ getClusterInfo: mockGetClusterInfo,
6
+ }));
7
+ const mockOutput = {
8
+ error: jest.fn(),
9
+ };
10
+ jest.unstable_mockModule('../../lib/output.js', () => ({
11
+ default: mockOutput,
12
+ }));
13
+ const mockExit = jest.spyOn(process, 'exit').mockImplementation((() => {
14
+ throw new Error('process.exit called');
15
+ }));
16
+ const mockConsoleLog = jest.spyOn(console, 'log').mockImplementation(() => { });
17
+ const { createGetCommand } = await import('./get.js');
18
+ describe('cluster get command', () => {
19
+ beforeEach(() => {
20
+ jest.clearAllMocks();
21
+ });
22
+ it('creates command with correct structure', () => {
23
+ const command = createGetCommand();
24
+ expect(command).toBeInstanceOf(Command);
25
+ expect(command.name()).toBe('get');
26
+ });
27
+ it('displays cluster info in text format by default', async () => {
28
+ mockGetClusterInfo.mockResolvedValue({
29
+ context: 'test-cluster',
30
+ namespace: 'default',
31
+ type: 'minikube',
32
+ ip: '192.168.1.1',
33
+ });
34
+ const command = createGetCommand();
35
+ await command.parseAsync(['node', 'test']);
36
+ expect(mockGetClusterInfo).toHaveBeenCalledWith(undefined);
37
+ expect(mockConsoleLog).toHaveBeenCalledWith('context: test-cluster');
38
+ expect(mockConsoleLog).toHaveBeenCalledWith('namespace: default');
39
+ expect(mockConsoleLog).toHaveBeenCalledWith('type: minikube');
40
+ expect(mockConsoleLog).toHaveBeenCalledWith('ip: 192.168.1.1');
41
+ });
42
+ it('displays cluster info in json format when requested', async () => {
43
+ const clusterInfo = {
44
+ context: 'prod-cluster',
45
+ namespace: 'production',
46
+ type: 'eks',
47
+ ip: '10.0.0.1',
48
+ };
49
+ mockGetClusterInfo.mockResolvedValue(clusterInfo);
50
+ const command = createGetCommand();
51
+ await command.parseAsync(['node', 'test', '-o', 'json']);
52
+ expect(mockConsoleLog).toHaveBeenCalledWith(JSON.stringify(clusterInfo, null, 2));
53
+ });
54
+ it('uses specified context when provided', async () => {
55
+ mockGetClusterInfo.mockResolvedValue({
56
+ context: 'custom-context',
57
+ namespace: 'custom',
58
+ type: 'kind',
59
+ ip: '127.0.0.1',
60
+ });
61
+ const command = createGetCommand();
62
+ await command.parseAsync(['node', 'test', '-c', 'custom-context']);
63
+ expect(mockGetClusterInfo).toHaveBeenCalledWith('custom-context');
64
+ });
65
+ it('handles missing ip gracefully', async () => {
66
+ mockGetClusterInfo.mockResolvedValue({
67
+ context: 'test-cluster',
68
+ namespace: 'default',
69
+ type: 'unknown',
70
+ ip: undefined,
71
+ });
72
+ const command = createGetCommand();
73
+ await command.parseAsync(['node', 'test']);
74
+ expect(mockConsoleLog).toHaveBeenCalledWith('ip: unknown');
75
+ });
76
+ it('exits with error when cluster info has error', async () => {
77
+ mockGetClusterInfo.mockResolvedValue({
78
+ error: 'No cluster found',
79
+ });
80
+ const command = createGetCommand();
81
+ await expect(command.parseAsync(['node', 'test'])).rejects.toThrow('process.exit called');
82
+ expect(mockOutput.error).toHaveBeenCalledWith('getting cluster info:', 'No cluster found');
83
+ expect(mockExit).toHaveBeenCalledWith(1);
84
+ });
85
+ it('handles exceptions gracefully', async () => {
86
+ mockGetClusterInfo.mockRejectedValue(new Error('Connection failed'));
87
+ const command = createGetCommand();
88
+ await expect(command.parseAsync(['node', 'test'])).rejects.toThrow('process.exit called');
89
+ expect(mockOutput.error).toHaveBeenCalledWith('failed to get cluster info:', 'Connection failed');
90
+ expect(mockExit).toHaveBeenCalledWith(1);
91
+ });
92
+ });
@@ -1,2 +1,3 @@
1
1
  import { Command } from 'commander';
2
- export declare function createClusterCommand(): Command;
2
+ import type { ArkConfig } from '../../lib/config.js';
3
+ export declare function createClusterCommand(_: ArkConfig): Command;
@@ -1,6 +1,6 @@
1
1
  import { Command } from 'commander';
2
2
  import { createGetCommand } from './get.js';
3
- export function createClusterCommand() {
3
+ export function createClusterCommand(_) {
4
4
  const cluster = new Command('cluster');
5
5
  cluster.description('Cluster management commands');
6
6
  cluster.addCommand(createGetCommand());
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,24 @@
1
+ import { jest } from '@jest/globals';
2
+ import { Command } from 'commander';
3
+ const mockCreateGetCommand = jest.fn();
4
+ jest.unstable_mockModule('./get.js', () => ({
5
+ createGetCommand: mockCreateGetCommand,
6
+ }));
7
+ const { createClusterCommand } = await import('./index.js');
8
+ describe('cluster command', () => {
9
+ beforeEach(() => {
10
+ jest.clearAllMocks();
11
+ mockCreateGetCommand.mockReturnValue(new Command('get'));
12
+ });
13
+ it('creates command with correct structure', () => {
14
+ const command = createClusterCommand({});
15
+ expect(command).toBeInstanceOf(Command);
16
+ expect(command.name()).toBe('cluster');
17
+ });
18
+ it('adds get subcommand', () => {
19
+ const command = createClusterCommand({});
20
+ expect(mockCreateGetCommand).toHaveBeenCalled();
21
+ const getCommand = command.commands.find((cmd) => cmd.name() === 'get');
22
+ expect(getCommand).toBeDefined();
23
+ });
24
+ });
@@ -1,2 +1,3 @@
1
1
  import { Command } from 'commander';
2
- export declare function createCompletionCommand(): Command;
2
+ import type { ArkConfig } from '../../lib/config.js';
3
+ export declare function createCompletionCommand(_: ArkConfig): Command;
@@ -1,6 +1,6 @@
1
1
  import chalk from 'chalk';
2
2
  import { Command } from 'commander';
3
- export function createCompletionCommand() {
3
+ export function createCompletionCommand(_) {
4
4
  const completion = new Command('completion');
5
5
  completion.description('Generate shell completion scripts').action(() => {
6
6
  console.log(chalk.cyan('Shell completion for ARK CLI'));
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,34 @@
1
+ import { jest } from '@jest/globals';
2
+ import { Command } from 'commander';
3
+ const mockConsoleLog = jest.spyOn(console, 'log').mockImplementation(() => { });
4
+ const { createCompletionCommand } = await import('./index.js');
5
+ describe('completion command', () => {
6
+ beforeEach(() => {
7
+ jest.clearAllMocks();
8
+ });
9
+ it('creates command with correct structure', () => {
10
+ const command = createCompletionCommand({});
11
+ expect(command).toBeInstanceOf(Command);
12
+ expect(command.name()).toBe('completion');
13
+ });
14
+ it('shows help when called without subcommand', async () => {
15
+ const command = createCompletionCommand({});
16
+ await command.parseAsync(['node', 'test']);
17
+ // Check first call contains the title (strip ANSI color codes)
18
+ expect(mockConsoleLog.mock.calls[0][0]).toContain('Shell completion for ARK CLI');
19
+ // Check that bash completion instructions are shown
20
+ expect(mockConsoleLog).toHaveBeenCalledWith(expect.stringContaining('ark completion bash'));
21
+ });
22
+ it('outputs bash completion script', async () => {
23
+ const command = createCompletionCommand({});
24
+ await command.parseAsync(['node', 'test', 'bash']);
25
+ expect(mockConsoleLog).toHaveBeenCalledWith(expect.stringContaining('_ark_completion()'));
26
+ expect(mockConsoleLog).toHaveBeenCalledWith(expect.stringContaining('COMPREPLY'));
27
+ });
28
+ it('outputs zsh completion script', async () => {
29
+ const command = createCompletionCommand({});
30
+ await command.parseAsync(['node', 'test', 'zsh']);
31
+ expect(mockConsoleLog).toHaveBeenCalledWith(expect.stringContaining('#compdef ark'));
32
+ expect(mockConsoleLog).toHaveBeenCalledWith(expect.stringContaining('_ark()'));
33
+ });
34
+ });
@@ -1,2 +1,3 @@
1
1
  import { Command } from 'commander';
2
- export declare function createConfigCommand(): Command;
2
+ import { type ArkConfig } from '../../lib/config.js';
3
+ export declare function createConfigCommand(_: ArkConfig): Command;
@@ -1,8 +1,8 @@
1
1
  import { Command } from 'commander';
2
2
  import chalk from 'chalk';
3
- import { loadConfig, getConfigPaths, formatConfig } from '../../lib/config.js';
3
+ import { loadConfig, getConfigPaths, formatConfig, } from '../../lib/config.js';
4
4
  import fs from 'fs';
5
- export function createConfigCommand() {
5
+ export function createConfigCommand(_) {
6
6
  const configCommand = new Command('config');
7
7
  configCommand.description('Show current configuration').action(() => {
8
8
  const config = loadConfig();
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,78 @@
1
+ import { jest } from '@jest/globals';
2
+ import { Command } from 'commander';
3
+ const mockLoadConfig = jest.fn();
4
+ const mockGetConfigPaths = jest.fn();
5
+ const mockFormatConfig = jest.fn();
6
+ jest.unstable_mockModule('../../lib/config.js', () => ({
7
+ loadConfig: mockLoadConfig,
8
+ getConfigPaths: mockGetConfigPaths,
9
+ formatConfig: mockFormatConfig,
10
+ }));
11
+ const mockExistsSync = jest.fn();
12
+ jest.unstable_mockModule('fs', () => ({
13
+ default: {
14
+ existsSync: mockExistsSync,
15
+ },
16
+ existsSync: mockExistsSync,
17
+ }));
18
+ const mockConsoleLog = jest.spyOn(console, 'log').mockImplementation(() => { });
19
+ const { createConfigCommand } = await import('./index.js');
20
+ describe('config command', () => {
21
+ beforeEach(() => {
22
+ jest.clearAllMocks();
23
+ // Reset environment variables
24
+ delete process.env.ARK_CHAT_STREAMING;
25
+ delete process.env.ARK_CHAT_OUTPUT_FORMAT;
26
+ });
27
+ it('creates command with correct structure', () => {
28
+ const command = createConfigCommand({});
29
+ expect(command).toBeInstanceOf(Command);
30
+ expect(command.name()).toBe('config');
31
+ });
32
+ it('displays config paths and environment variables', async () => {
33
+ const mockConfig = { defaultModel: 'test-model' };
34
+ const mockPaths = {
35
+ user: '/home/user/.arkrc.yaml',
36
+ project: '/project/.arkrc.yaml',
37
+ };
38
+ mockLoadConfig.mockReturnValue(mockConfig);
39
+ mockGetConfigPaths.mockReturnValue(mockPaths);
40
+ mockFormatConfig.mockReturnValue('formatted config');
41
+ mockExistsSync.mockReturnValue(true);
42
+ const command = createConfigCommand({});
43
+ await command.parseAsync(['node', 'test']);
44
+ expect(mockLoadConfig).toHaveBeenCalled();
45
+ expect(mockGetConfigPaths).toHaveBeenCalled();
46
+ expect(mockFormatConfig).toHaveBeenCalledWith(mockConfig);
47
+ expect(mockExistsSync).toHaveBeenCalledWith(mockPaths.user);
48
+ expect(mockExistsSync).toHaveBeenCalledWith(mockPaths.project);
49
+ });
50
+ it('shows when config files do not exist', async () => {
51
+ const mockPaths = {
52
+ user: '/home/user/.arkrc.yaml',
53
+ project: '/project/.arkrc.yaml',
54
+ };
55
+ mockLoadConfig.mockReturnValue({});
56
+ mockGetConfigPaths.mockReturnValue(mockPaths);
57
+ mockFormatConfig.mockReturnValue('');
58
+ mockExistsSync.mockReturnValue(false);
59
+ const command = createConfigCommand({});
60
+ await command.parseAsync(['node', 'test']);
61
+ expect(mockExistsSync).toHaveBeenCalledWith(mockPaths.user);
62
+ expect(mockExistsSync).toHaveBeenCalledWith(mockPaths.project);
63
+ // Should show that files don't exist
64
+ expect(mockConsoleLog).toHaveBeenCalled();
65
+ });
66
+ it('displays environment variables when set', async () => {
67
+ process.env.ARK_CHAT_STREAMING = 'true';
68
+ process.env.ARK_CHAT_OUTPUT_FORMAT = 'json';
69
+ mockLoadConfig.mockReturnValue({});
70
+ mockGetConfigPaths.mockReturnValue({ user: '', project: '' });
71
+ mockFormatConfig.mockReturnValue('');
72
+ mockExistsSync.mockReturnValue(false);
73
+ const command = createConfigCommand({});
74
+ await command.parseAsync(['node', 'test']);
75
+ // Should display the environment variables
76
+ expect(mockConsoleLog).toHaveBeenCalled();
77
+ });
78
+ });
@@ -1,3 +1,4 @@
1
1
  import { Command } from 'commander';
2
+ import type { ArkConfig } from '../../lib/config.js';
2
3
  export declare function openDashboard(): Promise<void>;
3
- export declare function createDashboardCommand(): Command;
4
+ export declare function createDashboardCommand(_: ArkConfig): Command;
@@ -30,7 +30,7 @@ export async function openDashboard() {
30
30
  process.exit(1);
31
31
  }
32
32
  }
33
- export function createDashboardCommand() {
33
+ export function createDashboardCommand(_) {
34
34
  const dashboardCommand = new Command('dashboard');
35
35
  dashboardCommand
36
36
  .description('Open the ARK dashboard in your browser')
@@ -1,2 +1,3 @@
1
1
  import { Command } from 'commander';
2
- export declare function createDevCommand(): Command;
2
+ import type { ArkConfig } from '../../lib/config.js';
3
+ export declare function createDevCommand(_: ArkConfig): Command;
@@ -1,6 +1,6 @@
1
1
  import { Command } from 'commander';
2
2
  import { createToolCommand } from './tool/index.js';
3
- export function createDevCommand() {
3
+ export function createDevCommand(_) {
4
4
  const devCommand = new Command('dev');
5
5
  devCommand.description('Development tools for ARK');
6
6
  // Add subcommands