@agents-at-scale/ark 0.1.35 → 0.1.36

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 (167) hide show
  1. package/dist/arkServices.d.ts +42 -0
  2. package/dist/arkServices.js +138 -0
  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 +3 -0
  6. package/dist/commands/agents/index.js +65 -0
  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 +3 -0
  10. package/dist/commands/chat/index.js +29 -0
  11. package/dist/commands/cluster/get.d.ts +2 -0
  12. package/dist/commands/cluster/get.js +39 -0
  13. package/dist/commands/cluster/get.spec.d.ts +1 -0
  14. package/dist/commands/cluster/get.spec.js +92 -0
  15. package/dist/commands/cluster/index.d.ts +2 -1
  16. package/dist/commands/cluster/index.js +3 -5
  17. package/dist/commands/cluster/index.spec.d.ts +1 -0
  18. package/dist/commands/cluster/index.spec.js +24 -0
  19. package/dist/commands/completion/index.d.ts +3 -0
  20. package/dist/commands/completion/index.js +230 -0
  21. package/dist/commands/completion/index.spec.d.ts +1 -0
  22. package/dist/commands/completion/index.spec.js +34 -0
  23. package/dist/commands/config/index.d.ts +3 -0
  24. package/dist/commands/config/index.js +42 -0
  25. package/dist/commands/config/index.spec.d.ts +1 -0
  26. package/dist/commands/config/index.spec.js +78 -0
  27. package/dist/commands/dashboard/index.d.ts +4 -0
  28. package/dist/commands/dashboard/index.js +39 -0
  29. package/dist/commands/docs/index.d.ts +4 -0
  30. package/dist/commands/docs/index.js +18 -0
  31. package/dist/commands/generate/config.js +5 -24
  32. package/dist/commands/generate/generators/mcpserver.d.ts +2 -1
  33. package/dist/commands/generate/generators/mcpserver.js +26 -5
  34. package/dist/commands/generate/generators/project.js +22 -41
  35. package/dist/commands/generate/index.d.ts +2 -1
  36. package/dist/commands/generate/index.js +1 -1
  37. package/dist/commands/install/index.d.ts +8 -0
  38. package/dist/commands/install/index.js +295 -0
  39. package/dist/commands/install/index.spec.d.ts +1 -0
  40. package/dist/commands/install/index.spec.js +143 -0
  41. package/dist/commands/models/create.d.ts +1 -0
  42. package/dist/commands/models/create.js +213 -0
  43. package/dist/commands/models/create.spec.d.ts +1 -0
  44. package/dist/commands/models/create.spec.js +125 -0
  45. package/dist/commands/models/index.d.ts +3 -0
  46. package/dist/commands/models/index.js +75 -0
  47. package/dist/commands/models/index.spec.d.ts +1 -0
  48. package/dist/commands/models/index.spec.js +96 -0
  49. package/dist/commands/query/index.d.ts +3 -0
  50. package/dist/commands/query/index.js +24 -0
  51. package/dist/commands/query/index.spec.d.ts +1 -0
  52. package/dist/commands/query/index.spec.js +53 -0
  53. package/dist/commands/routes/index.d.ts +3 -0
  54. package/dist/commands/routes/index.js +93 -0
  55. package/dist/commands/status/index.d.ts +3 -0
  56. package/dist/commands/status/index.js +281 -0
  57. package/dist/commands/targets/index.d.ts +3 -0
  58. package/dist/commands/targets/index.js +72 -0
  59. package/dist/commands/targets/index.spec.d.ts +1 -0
  60. package/dist/commands/targets/index.spec.js +154 -0
  61. package/dist/commands/teams/index.d.ts +3 -0
  62. package/dist/commands/teams/index.js +64 -0
  63. package/dist/commands/teams/index.spec.d.ts +1 -0
  64. package/dist/commands/teams/index.spec.js +70 -0
  65. package/dist/commands/tools/index.d.ts +3 -0
  66. package/dist/commands/tools/index.js +49 -0
  67. package/dist/commands/tools/index.spec.d.ts +1 -0
  68. package/dist/commands/tools/index.spec.js +70 -0
  69. package/dist/commands/uninstall/index.d.ts +3 -0
  70. package/dist/commands/uninstall/index.js +101 -0
  71. package/dist/commands/uninstall/index.spec.d.ts +1 -0
  72. package/dist/commands/uninstall/index.spec.js +125 -0
  73. package/dist/components/ChatUI.d.ts +16 -0
  74. package/dist/components/ChatUI.js +801 -0
  75. package/dist/components/statusChecker.d.ts +14 -24
  76. package/dist/components/statusChecker.js +295 -129
  77. package/dist/index.d.ts +1 -1
  78. package/dist/index.js +42 -42
  79. package/dist/lib/arkApiClient.d.ts +53 -0
  80. package/dist/lib/arkApiClient.js +102 -0
  81. package/dist/lib/arkApiProxy.d.ts +9 -0
  82. package/dist/lib/arkApiProxy.js +22 -0
  83. package/dist/lib/arkServiceProxy.d.ts +14 -0
  84. package/dist/lib/arkServiceProxy.js +95 -0
  85. package/dist/lib/arkStatus.d.ts +10 -0
  86. package/dist/lib/arkStatus.js +79 -0
  87. package/dist/lib/arkStatus.spec.d.ts +1 -0
  88. package/dist/lib/arkStatus.spec.js +49 -0
  89. package/dist/lib/chatClient.d.ts +33 -0
  90. package/dist/lib/chatClient.js +93 -0
  91. package/dist/lib/cluster.d.ts +2 -1
  92. package/dist/lib/cluster.js +37 -16
  93. package/dist/lib/cluster.spec.d.ts +1 -0
  94. package/dist/lib/cluster.spec.js +338 -0
  95. package/dist/lib/commands.d.ts +16 -0
  96. package/dist/lib/commands.js +29 -0
  97. package/dist/lib/commands.spec.d.ts +1 -0
  98. package/dist/lib/commands.spec.js +146 -0
  99. package/dist/lib/config.d.ts +26 -80
  100. package/dist/lib/config.js +70 -205
  101. package/dist/lib/config.spec.d.ts +1 -0
  102. package/dist/lib/config.spec.js +99 -0
  103. package/dist/lib/errors.js +1 -1
  104. package/dist/lib/errors.spec.d.ts +1 -0
  105. package/dist/lib/errors.spec.js +221 -0
  106. package/dist/lib/executeQuery.d.ts +20 -0
  107. package/dist/lib/executeQuery.js +135 -0
  108. package/dist/lib/executeQuery.spec.d.ts +1 -0
  109. package/dist/lib/executeQuery.spec.js +170 -0
  110. package/dist/lib/nextSteps.d.ts +4 -0
  111. package/dist/lib/nextSteps.js +20 -0
  112. package/dist/lib/nextSteps.spec.d.ts +1 -0
  113. package/dist/lib/nextSteps.spec.js +59 -0
  114. package/dist/lib/output.d.ts +36 -0
  115. package/dist/lib/output.js +89 -0
  116. package/dist/lib/output.spec.d.ts +1 -0
  117. package/dist/lib/output.spec.js +123 -0
  118. package/dist/lib/startup.d.ts +9 -0
  119. package/dist/lib/startup.js +87 -0
  120. package/dist/lib/startup.spec.d.ts +1 -0
  121. package/dist/lib/startup.spec.js +152 -0
  122. package/dist/lib/types.d.ts +87 -3
  123. package/dist/lib/versions.d.ts +23 -0
  124. package/dist/lib/versions.js +51 -0
  125. package/dist/types/types.d.ts +40 -0
  126. package/dist/types/types.js +1 -0
  127. package/dist/ui/AgentSelector.d.ts +8 -0
  128. package/dist/ui/AgentSelector.js +53 -0
  129. package/dist/ui/MainMenu.d.ts +5 -1
  130. package/dist/ui/MainMenu.js +226 -91
  131. package/dist/ui/ModelSelector.d.ts +8 -0
  132. package/dist/ui/ModelSelector.js +53 -0
  133. package/dist/ui/TeamSelector.d.ts +8 -0
  134. package/dist/ui/TeamSelector.js +55 -0
  135. package/dist/ui/ToolSelector.d.ts +8 -0
  136. package/dist/ui/ToolSelector.js +53 -0
  137. package/dist/ui/statusFormatter.d.ts +22 -7
  138. package/dist/ui/statusFormatter.js +39 -39
  139. package/dist/ui/statusFormatter.spec.d.ts +1 -0
  140. package/dist/ui/statusFormatter.spec.js +58 -0
  141. package/package.json +16 -5
  142. package/dist/commands/cluster/get-ip.d.ts +0 -2
  143. package/dist/commands/cluster/get-ip.js +0 -32
  144. package/dist/commands/cluster/get-type.d.ts +0 -2
  145. package/dist/commands/cluster/get-type.js +0 -26
  146. package/dist/commands/completion.d.ts +0 -2
  147. package/dist/commands/completion.js +0 -108
  148. package/dist/commands/config.d.ts +0 -5
  149. package/dist/commands/config.js +0 -327
  150. package/dist/components/DashboardCLI.d.ts +0 -3
  151. package/dist/components/DashboardCLI.js +0 -149
  152. package/dist/config.d.ts +0 -42
  153. package/dist/config.js +0 -243
  154. package/dist/lib/arkClient.d.ts +0 -32
  155. package/dist/lib/arkClient.js +0 -43
  156. package/dist/lib/consts.d.ts +0 -10
  157. package/dist/lib/consts.js +0 -15
  158. package/dist/lib/exec.d.ts +0 -5
  159. package/dist/lib/exec.js +0 -20
  160. package/dist/lib/gatewayManager.d.ts +0 -24
  161. package/dist/lib/gatewayManager.js +0 -85
  162. package/dist/lib/kubernetes.d.ts +0 -28
  163. package/dist/lib/kubernetes.js +0 -122
  164. package/dist/lib/progress.d.ts +0 -128
  165. package/dist/lib/progress.js +0 -273
  166. package/dist/lib/wrappers/git.d.ts +0 -2
  167. package/dist/lib/wrappers/git.js +0 -43
@@ -0,0 +1,93 @@
1
+ export class ChatClient {
2
+ constructor(arkApiClient) {
3
+ this.arkApiClient = arkApiClient;
4
+ }
5
+ async getQueryTargets() {
6
+ return await this.arkApiClient.getQueryTargets();
7
+ }
8
+ /**
9
+ * Send a chat completion request
10
+ */
11
+ async sendMessage(targetId, messages, config, onChunk, signal) {
12
+ const shouldStream = config.streamingEnabled && !!onChunk;
13
+ const params = {
14
+ model: targetId,
15
+ messages: messages,
16
+ signal: signal,
17
+ };
18
+ if (shouldStream) {
19
+ let fullResponse = '';
20
+ const toolCallsById = new Map();
21
+ const stream = this.arkApiClient.createChatCompletionStream(params);
22
+ for await (const chunk of stream) {
23
+ if (signal?.aborted) {
24
+ break;
25
+ }
26
+ const delta = chunk.choices[0]?.delta;
27
+ // Extract ARK metadata if present
28
+ const arkMetadata = chunk.ark;
29
+ // Handle regular content
30
+ const content = delta?.content || '';
31
+ if (content) {
32
+ fullResponse += content;
33
+ if (onChunk) {
34
+ onChunk(content, undefined, arkMetadata);
35
+ }
36
+ }
37
+ // Handle tool calls
38
+ if (delta?.tool_calls) {
39
+ for (const toolCallDelta of delta.tool_calls) {
40
+ const index = toolCallDelta.index;
41
+ // Initialize tool call if this is the first chunk for this index
42
+ if (!toolCallsById.has(index)) {
43
+ toolCallsById.set(index, {
44
+ id: toolCallDelta.id || '',
45
+ type: toolCallDelta.type || 'function',
46
+ function: {
47
+ name: toolCallDelta.function?.name || '',
48
+ arguments: '',
49
+ },
50
+ });
51
+ }
52
+ // Accumulate function arguments
53
+ const toolCall = toolCallsById.get(index);
54
+ if (toolCallDelta.function?.arguments) {
55
+ toolCall.function.arguments += toolCallDelta.function.arguments;
56
+ }
57
+ // Send the current state of all tool calls
58
+ if (onChunk) {
59
+ const toolCallsArray = Array.from(toolCallsById.values());
60
+ onChunk('', toolCallsArray, arkMetadata);
61
+ }
62
+ }
63
+ }
64
+ }
65
+ return fullResponse;
66
+ }
67
+ else {
68
+ const response = await this.arkApiClient.createChatCompletion(params);
69
+ const message = response.choices[0]?.message;
70
+ const content = message?.content || '';
71
+ // Handle tool calls in non-streaming mode
72
+ if (message?.tool_calls && message.tool_calls.length > 0) {
73
+ const toolCalls = message.tool_calls.map((tc) => ({
74
+ id: tc.id,
75
+ type: tc.type || 'function',
76
+ function: {
77
+ name: tc.function?.name || '',
78
+ arguments: tc.function?.arguments || '',
79
+ },
80
+ }));
81
+ // Send tool calls first
82
+ if (onChunk) {
83
+ onChunk('', toolCalls);
84
+ }
85
+ }
86
+ // Send content after tool calls
87
+ if (content && onChunk) {
88
+ onChunk(content);
89
+ }
90
+ return content;
91
+ }
92
+ }
93
+ }
@@ -2,7 +2,8 @@ export interface ClusterInfo {
2
2
  type: 'minikube' | 'kind' | 'k3s' | 'docker-desktop' | 'cloud' | 'unknown';
3
3
  ip?: string;
4
4
  context?: string;
5
+ namespace?: string;
5
6
  error?: string;
6
7
  }
7
8
  export declare function detectClusterType(): Promise<ClusterInfo>;
8
- export declare function getClusterIp(_context?: string): Promise<ClusterInfo>;
9
+ export declare function getClusterInfo(context?: string): Promise<ClusterInfo>;
@@ -1,10 +1,7 @@
1
- import { executeCommand } from './exec.js';
1
+ import { execa } from 'execa';
2
2
  export async function detectClusterType() {
3
3
  try {
4
- const { stdout } = await executeCommand('kubectl', [
5
- 'config',
6
- 'current-context',
7
- ]);
4
+ const { stdout } = await execa('kubectl', ['config', 'current-context']);
8
5
  const context = stdout.trim();
9
6
  if (context.includes('minikube')) {
10
7
  return { type: 'minikube', context };
@@ -28,12 +25,33 @@ export async function detectClusterType() {
28
25
  }
29
26
  }
30
27
  catch (error) {
31
- return { type: 'unknown', error: error.message };
28
+ return {
29
+ type: 'unknown',
30
+ error: error instanceof Error ? error.message : 'Unknown error',
31
+ };
32
32
  }
33
33
  }
34
- export async function getClusterIp(_context) {
34
+ export async function getClusterInfo(context) {
35
35
  try {
36
+ // If context is provided, use it
37
+ const contextArgs = context ? ['--context', context] : [];
38
+ // Get all config info in one command
39
+ const { stdout: configJson } = await execa('kubectl', [
40
+ 'config',
41
+ 'view',
42
+ '--minify',
43
+ '-o',
44
+ 'json',
45
+ ...contextArgs,
46
+ ]);
47
+ const config = JSON.parse(configJson);
48
+ const currentContext = config['current-context'] || '';
49
+ const contextData = config.contexts?.find((c) => c.name === currentContext);
50
+ const namespace = contextData?.context?.namespace || 'default';
51
+ // Detect cluster type from context name
36
52
  const clusterInfo = await detectClusterType();
53
+ clusterInfo.context = currentContext;
54
+ clusterInfo.namespace = namespace;
37
55
  if (clusterInfo.error) {
38
56
  return clusterInfo;
39
57
  }
@@ -41,12 +59,12 @@ export async function getClusterIp(_context) {
41
59
  switch (clusterInfo.type) {
42
60
  case 'minikube':
43
61
  try {
44
- const { stdout } = await executeCommand('minikube', ['ip']);
62
+ const { stdout } = await execa('minikube', ['ip']);
45
63
  ip = stdout.trim();
46
64
  }
47
65
  catch {
48
66
  // Fallback to kubectl if minikube command fails
49
- const { stdout } = await executeCommand('kubectl', [
67
+ const { stdout } = await execa('kubectl', [
50
68
  'get',
51
69
  'nodes',
52
70
  '-o',
@@ -56,7 +74,7 @@ export async function getClusterIp(_context) {
56
74
  }
57
75
  break;
58
76
  case 'kind': {
59
- const { stdout: kindOutput } = await executeCommand('kubectl', [
77
+ const { stdout: kindOutput } = await execa('kubectl', [
60
78
  'get',
61
79
  'nodes',
62
80
  '-o',
@@ -69,7 +87,7 @@ export async function getClusterIp(_context) {
69
87
  ip = 'localhost';
70
88
  break;
71
89
  case 'k3s': {
72
- const { stdout: k3sOutput } = await executeCommand('kubectl', [
90
+ const { stdout: k3sOutput } = await execa('kubectl', [
73
91
  'get',
74
92
  'nodes',
75
93
  '-o',
@@ -81,7 +99,7 @@ export async function getClusterIp(_context) {
81
99
  case 'cloud':
82
100
  // For cloud clusters, try to get the external IP or load balancer IP
83
101
  try {
84
- const { stdout: lbOutput } = await executeCommand('kubectl', [
102
+ const { stdout: lbOutput } = await execa('kubectl', [
85
103
  'get',
86
104
  'svc',
87
105
  '-n',
@@ -92,7 +110,7 @@ export async function getClusterIp(_context) {
92
110
  ]);
93
111
  ip = lbOutput.trim();
94
112
  if (!ip) {
95
- const { stdout: hostnameOutput } = await executeCommand('kubectl', [
113
+ const { stdout: hostnameOutput } = await execa('kubectl', [
96
114
  'get',
97
115
  'svc',
98
116
  '-n',
@@ -106,7 +124,7 @@ export async function getClusterIp(_context) {
106
124
  }
107
125
  catch {
108
126
  // Fallback to node IP
109
- const { stdout: nodeOutput } = await executeCommand('kubectl', [
127
+ const { stdout: nodeOutput } = await execa('kubectl', [
110
128
  'get',
111
129
  'nodes',
112
130
  '-o',
@@ -116,7 +134,7 @@ export async function getClusterIp(_context) {
116
134
  }
117
135
  break;
118
136
  default: {
119
- const { stdout: defaultOutput } = await executeCommand('kubectl', [
137
+ const { stdout: defaultOutput } = await execa('kubectl', [
120
138
  'get',
121
139
  'nodes',
122
140
  '-o',
@@ -129,6 +147,9 @@ export async function getClusterIp(_context) {
129
147
  return { ...clusterInfo, ip };
130
148
  }
131
149
  catch (error) {
132
- return { type: 'unknown', error: error.message };
150
+ return {
151
+ type: 'unknown',
152
+ error: error instanceof Error ? error.message : 'Unknown error',
153
+ };
133
154
  }
134
155
  }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,338 @@
1
+ import { jest } from '@jest/globals';
2
+ const mockExeca = jest.fn();
3
+ jest.unstable_mockModule('execa', () => ({
4
+ execa: mockExeca,
5
+ }));
6
+ const { getClusterInfo, detectClusterType } = await import('./cluster.js');
7
+ describe('cluster', () => {
8
+ beforeEach(() => {
9
+ jest.clearAllMocks();
10
+ });
11
+ describe('detectClusterType', () => {
12
+ it('detects minikube cluster', async () => {
13
+ mockExeca.mockResolvedValue({ stdout: 'minikube' });
14
+ const result = await detectClusterType();
15
+ expect(result).toEqual({ type: 'minikube', context: 'minikube' });
16
+ expect(mockExeca).toHaveBeenCalledWith('kubectl', [
17
+ 'config',
18
+ 'current-context',
19
+ ]);
20
+ });
21
+ it('detects kind cluster', async () => {
22
+ mockExeca.mockResolvedValue({ stdout: 'kind-kind' });
23
+ const result = await detectClusterType();
24
+ expect(result).toEqual({ type: 'kind', context: 'kind-kind' });
25
+ });
26
+ it('detects k3s cluster', async () => {
27
+ mockExeca.mockResolvedValue({ stdout: 'k3s-default' });
28
+ const result = await detectClusterType();
29
+ expect(result).toEqual({ type: 'k3s', context: 'k3s-default' });
30
+ });
31
+ it('detects docker-desktop cluster', async () => {
32
+ mockExeca.mockResolvedValue({ stdout: 'docker-desktop' });
33
+ const result = await detectClusterType();
34
+ expect(result).toEqual({
35
+ type: 'docker-desktop',
36
+ context: 'docker-desktop',
37
+ });
38
+ });
39
+ it('detects gke cloud cluster', async () => {
40
+ mockExeca.mockResolvedValue({ stdout: 'gke_project_zone_cluster' });
41
+ const result = await detectClusterType();
42
+ expect(result).toEqual({
43
+ type: 'cloud',
44
+ context: 'gke_project_zone_cluster',
45
+ });
46
+ });
47
+ it('detects eks cloud cluster', async () => {
48
+ mockExeca.mockResolvedValue({
49
+ stdout: 'arn:aws:eks:region:account:cluster/name',
50
+ });
51
+ const result = await detectClusterType();
52
+ expect(result).toEqual({
53
+ type: 'cloud',
54
+ context: 'arn:aws:eks:region:account:cluster/name',
55
+ });
56
+ });
57
+ it('detects aks cloud cluster', async () => {
58
+ mockExeca.mockResolvedValue({ stdout: 'aks-cluster-name' });
59
+ const result = await detectClusterType();
60
+ expect(result).toEqual({ type: 'cloud', context: 'aks-cluster-name' });
61
+ });
62
+ it('returns unknown for unrecognized cluster', async () => {
63
+ mockExeca.mockResolvedValue({ stdout: 'some-other-cluster' });
64
+ const result = await detectClusterType();
65
+ expect(result).toEqual({ type: 'unknown', context: 'some-other-cluster' });
66
+ });
67
+ it('handles kubectl error', async () => {
68
+ mockExeca.mockRejectedValue(new Error('kubectl not found'));
69
+ const result = await detectClusterType();
70
+ expect(result).toEqual({ type: 'unknown', error: 'kubectl not found' });
71
+ });
72
+ });
73
+ describe('getClusterInfo', () => {
74
+ const mockConfig = {
75
+ 'current-context': 'minikube',
76
+ contexts: [
77
+ {
78
+ name: 'minikube',
79
+ context: {
80
+ namespace: 'default',
81
+ },
82
+ },
83
+ ],
84
+ };
85
+ it('gets minikube cluster info with IP', async () => {
86
+ mockExeca
87
+ .mockResolvedValueOnce({ stdout: JSON.stringify(mockConfig) })
88
+ .mockResolvedValueOnce({ stdout: 'minikube' })
89
+ .mockResolvedValueOnce({ stdout: '192.168.49.2' });
90
+ const result = await getClusterInfo();
91
+ expect(result).toEqual({
92
+ type: 'minikube',
93
+ context: 'minikube',
94
+ namespace: 'default',
95
+ ip: '192.168.49.2',
96
+ });
97
+ expect(mockExeca).toHaveBeenCalledWith('kubectl', [
98
+ 'config',
99
+ 'view',
100
+ '--minify',
101
+ '-o',
102
+ 'json',
103
+ ]);
104
+ expect(mockExeca).toHaveBeenCalledWith('kubectl', [
105
+ 'config',
106
+ 'current-context',
107
+ ]);
108
+ expect(mockExeca).toHaveBeenCalledWith('minikube', ['ip']);
109
+ });
110
+ it('falls back to kubectl for minikube IP if minikube command fails', async () => {
111
+ mockExeca
112
+ .mockResolvedValueOnce({ stdout: JSON.stringify(mockConfig) })
113
+ .mockResolvedValueOnce({ stdout: 'minikube' })
114
+ .mockRejectedValueOnce(new Error('minikube not found'))
115
+ .mockResolvedValueOnce({ stdout: '192.168.49.2' });
116
+ const result = await getClusterInfo();
117
+ expect(result.ip).toBe('192.168.49.2');
118
+ expect(mockExeca).toHaveBeenCalledWith('kubectl', [
119
+ 'get',
120
+ 'nodes',
121
+ '-o',
122
+ 'jsonpath={.items[0].status.addresses[?(@.type=="InternalIP")].address}',
123
+ ]);
124
+ });
125
+ it('gets kind cluster info with IP', async () => {
126
+ const kindConfig = {
127
+ 'current-context': 'kind-kind',
128
+ contexts: [
129
+ {
130
+ name: 'kind-kind',
131
+ context: {
132
+ namespace: 'kube-system',
133
+ },
134
+ },
135
+ ],
136
+ };
137
+ mockExeca
138
+ .mockResolvedValueOnce({ stdout: JSON.stringify(kindConfig) })
139
+ .mockResolvedValueOnce({ stdout: 'kind-kind' })
140
+ .mockResolvedValueOnce({ stdout: '172.18.0.2' });
141
+ const result = await getClusterInfo();
142
+ expect(result).toEqual({
143
+ type: 'kind',
144
+ context: 'kind-kind',
145
+ namespace: 'kube-system',
146
+ ip: '172.18.0.2',
147
+ });
148
+ });
149
+ it('gets docker-desktop cluster info', async () => {
150
+ const dockerConfig = {
151
+ 'current-context': 'docker-desktop',
152
+ contexts: [
153
+ {
154
+ name: 'docker-desktop',
155
+ context: {},
156
+ },
157
+ ],
158
+ };
159
+ mockExeca
160
+ .mockResolvedValueOnce({ stdout: JSON.stringify(dockerConfig) })
161
+ .mockResolvedValueOnce({ stdout: 'docker-desktop' });
162
+ const result = await getClusterInfo();
163
+ expect(result).toEqual({
164
+ type: 'docker-desktop',
165
+ context: 'docker-desktop',
166
+ namespace: 'default',
167
+ ip: 'localhost',
168
+ });
169
+ });
170
+ it('gets cloud cluster info with load balancer IP', async () => {
171
+ const cloudConfig = {
172
+ 'current-context': 'gke_project_zone_cluster',
173
+ contexts: [
174
+ {
175
+ name: 'gke_project_zone_cluster',
176
+ context: {
177
+ namespace: 'production',
178
+ },
179
+ },
180
+ ],
181
+ };
182
+ mockExeca
183
+ .mockResolvedValueOnce({ stdout: JSON.stringify(cloudConfig) })
184
+ .mockResolvedValueOnce({ stdout: 'gke_project_zone_cluster' })
185
+ .mockResolvedValueOnce({ stdout: '35.201.125.17' });
186
+ const result = await getClusterInfo();
187
+ expect(result).toEqual({
188
+ type: 'cloud',
189
+ context: 'gke_project_zone_cluster',
190
+ namespace: 'production',
191
+ ip: '35.201.125.17',
192
+ });
193
+ expect(mockExeca).toHaveBeenCalledWith('kubectl', [
194
+ 'get',
195
+ 'svc',
196
+ '-n',
197
+ 'istio-system',
198
+ 'istio-ingressgateway',
199
+ '-o',
200
+ 'jsonpath={.status.loadBalancer.ingress[0].ip}',
201
+ ]);
202
+ });
203
+ it('falls back to hostname for cloud cluster if no IP', async () => {
204
+ const cloudConfig = {
205
+ 'current-context': 'eks-cluster',
206
+ contexts: [
207
+ {
208
+ name: 'eks-cluster',
209
+ context: {},
210
+ },
211
+ ],
212
+ };
213
+ mockExeca
214
+ .mockResolvedValueOnce({ stdout: JSON.stringify(cloudConfig) })
215
+ .mockResolvedValueOnce({ stdout: 'eks-cluster' })
216
+ .mockResolvedValueOnce({ stdout: '' })
217
+ .mockResolvedValueOnce({ stdout: 'a1234.elb.amazonaws.com' });
218
+ const result = await getClusterInfo();
219
+ expect(result.ip).toBe('a1234.elb.amazonaws.com');
220
+ });
221
+ it('falls back to external node IP for cloud cluster', async () => {
222
+ const cloudConfig = {
223
+ 'current-context': 'gke-cluster',
224
+ contexts: [
225
+ {
226
+ name: 'gke-cluster',
227
+ context: {},
228
+ },
229
+ ],
230
+ };
231
+ mockExeca
232
+ .mockResolvedValueOnce({ stdout: JSON.stringify(cloudConfig) })
233
+ .mockResolvedValueOnce({ stdout: 'gke-cluster' })
234
+ .mockRejectedValueOnce(new Error('service not found'))
235
+ .mockResolvedValueOnce({ stdout: '35.201.125.18' });
236
+ const result = await getClusterInfo();
237
+ expect(result.ip).toBe('35.201.125.18');
238
+ expect(mockExeca).toHaveBeenCalledWith('kubectl', [
239
+ 'get',
240
+ 'nodes',
241
+ '-o',
242
+ 'jsonpath={.items[0].status.addresses[?(@.type=="ExternalIP")].address}',
243
+ ]);
244
+ });
245
+ it('gets k3s cluster info', async () => {
246
+ const k3sConfig = {
247
+ 'current-context': 'k3s-default',
248
+ contexts: [
249
+ {
250
+ name: 'k3s-default',
251
+ context: {},
252
+ },
253
+ ],
254
+ };
255
+ mockExeca
256
+ .mockResolvedValueOnce({ stdout: JSON.stringify(k3sConfig) })
257
+ .mockResolvedValueOnce({ stdout: 'k3s-default' })
258
+ .mockResolvedValueOnce({ stdout: '10.0.0.5' });
259
+ const result = await getClusterInfo();
260
+ expect(result).toEqual({
261
+ type: 'k3s',
262
+ context: 'k3s-default',
263
+ namespace: 'default',
264
+ ip: '10.0.0.5',
265
+ });
266
+ });
267
+ it('uses provided context parameter', async () => {
268
+ const multiConfig = {
269
+ 'current-context': 'kind-staging',
270
+ contexts: [
271
+ {
272
+ name: 'kind-staging',
273
+ context: {
274
+ namespace: 'staging-ns',
275
+ },
276
+ },
277
+ ],
278
+ };
279
+ mockExeca
280
+ .mockResolvedValueOnce({ stdout: JSON.stringify(multiConfig) })
281
+ .mockResolvedValueOnce({ stdout: 'kind-staging' })
282
+ .mockResolvedValueOnce({ stdout: '172.18.0.3' });
283
+ const result = await getClusterInfo('kind-staging');
284
+ expect(result.context).toBe('kind-staging');
285
+ expect(mockExeca).toHaveBeenCalledWith('kubectl', [
286
+ 'config',
287
+ 'view',
288
+ '--minify',
289
+ '-o',
290
+ 'json',
291
+ '--context',
292
+ 'kind-staging',
293
+ ]);
294
+ });
295
+ it('handles unknown cluster type', async () => {
296
+ const unknownConfig = {
297
+ 'current-context': 'custom-cluster',
298
+ contexts: [
299
+ {
300
+ name: 'custom-cluster',
301
+ context: {},
302
+ },
303
+ ],
304
+ };
305
+ mockExeca
306
+ .mockResolvedValueOnce({ stdout: JSON.stringify(unknownConfig) })
307
+ .mockResolvedValueOnce({ stdout: 'custom-cluster' })
308
+ .mockResolvedValueOnce({ stdout: '10.0.0.1' });
309
+ const result = await getClusterInfo();
310
+ expect(result).toEqual({
311
+ type: 'unknown',
312
+ context: 'custom-cluster',
313
+ namespace: 'default',
314
+ ip: '10.0.0.1',
315
+ });
316
+ });
317
+ it('handles kubectl config error', async () => {
318
+ mockExeca.mockRejectedValue(new Error('kubectl not configured'));
319
+ const result = await getClusterInfo();
320
+ expect(result).toEqual({
321
+ type: 'unknown',
322
+ error: 'kubectl not configured',
323
+ });
324
+ });
325
+ it('handles missing context in config', async () => {
326
+ const emptyConfig = {
327
+ contexts: [],
328
+ };
329
+ mockExeca
330
+ .mockResolvedValueOnce({ stdout: JSON.stringify(emptyConfig) })
331
+ .mockResolvedValueOnce({ stdout: '' })
332
+ .mockResolvedValueOnce({ stdout: '10.0.0.1' });
333
+ const result = await getClusterInfo();
334
+ expect(result.context).toBe('');
335
+ expect(result.namespace).toBe('default');
336
+ });
337
+ });
338
+ });
@@ -0,0 +1,16 @@
1
+ import { type Options } from 'execa';
2
+ /**
3
+ * Check if a command exists and is executable by running it with specified args
4
+ */
5
+ export declare function checkCommandExists(command: string, args?: string[]): Promise<boolean>;
6
+ export { checkCommandExists as isCommandAvailable };
7
+ /**
8
+ * Execute a command with optional verbose output
9
+ * @param command The command to execute
10
+ * @param args Array of arguments
11
+ * @param execaOptions Standard execa options
12
+ * @param additionalOptions Additional options for execute (e.g., verbose)
13
+ */
14
+ export declare function execute(command: string, args?: string[], execaOptions?: Options, additionalOptions?: {
15
+ verbose?: boolean;
16
+ }): Promise<import("execa").Result<Options>>;
@@ -0,0 +1,29 @@
1
+ import { execa } from 'execa';
2
+ import chalk from 'chalk';
3
+ /**
4
+ * Check if a command exists and is executable by running it with specified args
5
+ */
6
+ export async function checkCommandExists(command, args = ['--version']) {
7
+ try {
8
+ await execa(command, args);
9
+ return true;
10
+ }
11
+ catch {
12
+ return false;
13
+ }
14
+ }
15
+ export { checkCommandExists as isCommandAvailable };
16
+ /**
17
+ * Execute a command with optional verbose output
18
+ * @param command The command to execute
19
+ * @param args Array of arguments
20
+ * @param execaOptions Standard execa options
21
+ * @param additionalOptions Additional options for execute (e.g., verbose)
22
+ */
23
+ export async function execute(command, args = [], execaOptions = {}, additionalOptions = {}) {
24
+ const { verbose = false } = additionalOptions;
25
+ if (verbose) {
26
+ console.log(chalk.gray(`$ ${command} ${args.join(' ')}`));
27
+ }
28
+ return await execa(command, args, execaOptions);
29
+ }
@@ -0,0 +1 @@
1
+ export {};