@agents-at-scale/ark 0.1.41 → 0.1.43

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 (79) hide show
  1. package/dist/arkServices.js +0 -9
  2. package/dist/commands/completion/index.js +46 -1
  3. package/dist/commands/evaluation/index.d.ts +3 -0
  4. package/dist/commands/evaluation/index.js +60 -0
  5. package/dist/commands/evaluation/index.spec.d.ts +1 -0
  6. package/dist/commands/evaluation/index.spec.js +161 -0
  7. package/dist/commands/generate/generators/team.js +4 -1
  8. package/dist/commands/install/index.js +27 -0
  9. package/dist/commands/marketplace/index.d.ts +4 -0
  10. package/dist/commands/marketplace/index.js +50 -0
  11. package/dist/commands/memory/index.d.ts +15 -0
  12. package/dist/commands/memory/index.js +130 -0
  13. package/dist/commands/memory/index.spec.d.ts +1 -0
  14. package/dist/commands/memory/index.spec.js +124 -0
  15. package/dist/commands/models/create.d.ts +5 -6
  16. package/dist/commands/models/create.js +14 -119
  17. package/dist/commands/models/create.spec.js +51 -0
  18. package/dist/commands/models/kubernetes/manifest-builder.d.ts +11 -0
  19. package/dist/commands/models/kubernetes/manifest-builder.js +109 -0
  20. package/dist/commands/models/kubernetes/secret-manager.d.ts +7 -0
  21. package/dist/commands/models/kubernetes/secret-manager.js +20 -0
  22. package/dist/commands/models/providers/azure.d.ts +31 -0
  23. package/dist/commands/models/providers/azure.js +82 -0
  24. package/dist/commands/models/providers/azure.spec.d.ts +1 -0
  25. package/dist/commands/models/providers/azure.spec.js +232 -0
  26. package/dist/commands/models/providers/bedrock.d.ts +37 -0
  27. package/dist/commands/models/providers/bedrock.js +105 -0
  28. package/dist/commands/models/providers/bedrock.spec.d.ts +1 -0
  29. package/dist/commands/models/providers/bedrock.spec.js +241 -0
  30. package/dist/commands/models/providers/factory.d.ts +18 -0
  31. package/dist/commands/models/providers/factory.js +31 -0
  32. package/dist/commands/models/providers/index.d.ts +17 -0
  33. package/dist/commands/models/providers/index.js +9 -0
  34. package/dist/commands/models/providers/openai.d.ts +28 -0
  35. package/dist/commands/models/providers/openai.js +68 -0
  36. package/dist/commands/models/providers/openai.spec.d.ts +1 -0
  37. package/dist/commands/models/providers/openai.spec.js +180 -0
  38. package/dist/commands/models/providers/types.d.ts +51 -0
  39. package/dist/commands/models/providers/types.js +1 -0
  40. package/dist/commands/queries/delete.d.ts +7 -0
  41. package/dist/commands/queries/delete.js +24 -0
  42. package/dist/commands/queries/delete.spec.d.ts +1 -0
  43. package/dist/commands/queries/delete.spec.js +74 -0
  44. package/dist/commands/queries/index.d.ts +3 -0
  45. package/dist/commands/queries/index.js +108 -0
  46. package/dist/commands/queries/list.d.ts +6 -0
  47. package/dist/commands/queries/list.js +66 -0
  48. package/dist/commands/queries/list.spec.d.ts +1 -0
  49. package/dist/commands/queries/list.spec.js +170 -0
  50. package/dist/commands/queries/validation.d.ts +2 -0
  51. package/dist/commands/queries/validation.js +10 -0
  52. package/dist/commands/queries/validation.spec.d.ts +1 -0
  53. package/dist/commands/queries/validation.spec.js +27 -0
  54. package/dist/commands/query/index.js +3 -1
  55. package/dist/commands/query/index.spec.js +24 -0
  56. package/dist/commands/uninstall/index.js +27 -0
  57. package/dist/components/ChatUI.js +2 -0
  58. package/dist/index.js +8 -0
  59. package/dist/lib/arkApiClient.d.ts +4 -0
  60. package/dist/lib/arkApiClient.js +57 -0
  61. package/dist/lib/errors.d.ts +1 -0
  62. package/dist/lib/errors.js +1 -0
  63. package/dist/lib/executeEvaluation.d.ts +16 -0
  64. package/dist/lib/executeEvaluation.js +155 -0
  65. package/dist/lib/executeQuery.d.ts +1 -4
  66. package/dist/lib/executeQuery.js +98 -68
  67. package/dist/lib/executeQuery.spec.js +176 -99
  68. package/dist/lib/kubectl.d.ts +15 -0
  69. package/dist/lib/kubectl.js +47 -0
  70. package/dist/lib/kubectl.spec.d.ts +1 -0
  71. package/dist/lib/kubectl.spec.js +176 -0
  72. package/dist/lib/stdin.d.ts +1 -0
  73. package/dist/lib/stdin.js +16 -0
  74. package/dist/lib/stdin.spec.d.ts +1 -0
  75. package/dist/lib/stdin.spec.js +82 -0
  76. package/dist/lib/types.d.ts +39 -0
  77. package/dist/marketplaceServices.d.ts +15 -0
  78. package/dist/marketplaceServices.js +51 -0
  79. package/package.json +2 -1
@@ -4,21 +4,91 @@
4
4
  import { execa } from 'execa';
5
5
  import ora from 'ora';
6
6
  import chalk from 'chalk';
7
- import output from './output.js';
8
7
  import { ExitCodes } from './errors.js';
9
- import { parseDuration } from './duration.js';
10
- /**
11
- * Execute a query against any ARK target (model, agent, team)
12
- * This is the shared implementation used by all query commands
13
- */
8
+ import { ArkApiProxy } from './arkApiProxy.js';
9
+ import { ChatClient } from './chatClient.js';
14
10
  export async function executeQuery(options) {
15
- const spinner = ora('Creating query...').start();
16
- const queryTimeoutMs = options.timeout
17
- ? parseDuration(options.timeout)
18
- : parseDuration('5m');
19
- const watchTimeoutMs = options.watchTimeout
20
- ? parseDuration(options.watchTimeout)
21
- : queryTimeoutMs + 60000;
11
+ if (options.outputFormat) {
12
+ return executeQueryWithFormat(options);
13
+ }
14
+ let arkApiProxy;
15
+ const spinner = ora('Connecting to Ark API...').start();
16
+ try {
17
+ arkApiProxy = new ArkApiProxy();
18
+ const arkApiClient = await arkApiProxy.start();
19
+ const chatClient = new ChatClient(arkApiClient);
20
+ spinner.text = 'Executing query...';
21
+ const targetId = `${options.targetType}/${options.targetName}`;
22
+ const messages = [{ role: 'user', content: options.message }];
23
+ const state = {
24
+ toolCalls: new Map(),
25
+ content: '',
26
+ };
27
+ let lastAgentName;
28
+ let headerShown = false;
29
+ let firstOutput = true;
30
+ await chatClient.sendMessage(targetId, messages, { streamingEnabled: true }, (chunk, toolCalls, arkMetadata) => {
31
+ if (firstOutput) {
32
+ spinner.stop();
33
+ firstOutput = false;
34
+ }
35
+ const agentName = arkMetadata?.agent || arkMetadata?.team;
36
+ if (agentName && agentName !== lastAgentName) {
37
+ if (lastAgentName) {
38
+ if (state.content) {
39
+ process.stdout.write('\n');
40
+ }
41
+ process.stdout.write('\n');
42
+ }
43
+ const prefix = arkMetadata?.team ? '◆' : '●';
44
+ const color = arkMetadata?.team ? 'green' : 'cyan';
45
+ process.stdout.write(chalk[color](`${prefix} ${agentName}\n`));
46
+ lastAgentName = agentName;
47
+ state.content = '';
48
+ state.toolCalls.clear();
49
+ headerShown = true;
50
+ }
51
+ if (toolCalls && toolCalls.length > 0) {
52
+ for (const toolCall of toolCalls) {
53
+ if (!state.toolCalls.has(toolCall.id)) {
54
+ state.toolCalls.set(toolCall.id, toolCall);
55
+ if (state.content) {
56
+ process.stdout.write('\n');
57
+ }
58
+ process.stdout.write(chalk.magenta(`🔧 ${toolCall.function.name}\n`));
59
+ }
60
+ }
61
+ }
62
+ if (chunk) {
63
+ if (state.toolCalls.size > 0 && !state.content) {
64
+ process.stdout.write('\n');
65
+ }
66
+ process.stdout.write(chunk);
67
+ state.content += chunk;
68
+ }
69
+ });
70
+ if (spinner.isSpinning) {
71
+ spinner.stop();
72
+ }
73
+ if ((state.content || state.toolCalls.size > 0) && headerShown) {
74
+ process.stdout.write('\n');
75
+ }
76
+ if (arkApiProxy) {
77
+ arkApiProxy.stop();
78
+ }
79
+ }
80
+ catch (error) {
81
+ if (spinner.isSpinning) {
82
+ spinner.stop();
83
+ }
84
+ if (arkApiProxy) {
85
+ arkApiProxy.stop();
86
+ }
87
+ console.error(chalk.red(error instanceof Error ? error.message : 'Unknown error'));
88
+ process.exit(ExitCodes.CliError);
89
+ }
90
+ }
91
+ async function executeQueryWithFormat(options) {
22
92
  const timestamp = Date.now();
23
93
  const queryName = `cli-query-${timestamp}`;
24
94
  const queryManifest = {
@@ -39,71 +109,31 @@ export async function executeQuery(options) {
39
109
  },
40
110
  };
41
111
  try {
42
- // Apply the query
43
- spinner.text = 'Submitting query...';
44
112
  await execa('kubectl', ['apply', '-f', '-'], {
45
113
  input: JSON.stringify(queryManifest),
46
114
  stdio: ['pipe', 'pipe', 'pipe'],
47
115
  });
48
- // Watch for query completion using kubectl wait
49
- spinner.text = 'Waiting for query completion...';
50
- try {
51
- await execa('kubectl', [
52
- 'wait',
53
- '--for=condition=Completed',
54
- `query/${queryName}`,
55
- `--timeout=${Math.floor(watchTimeoutMs / 1000)}s`,
56
- ], { timeout: watchTimeoutMs });
116
+ const timeoutSeconds = 300;
117
+ await execa('kubectl', [
118
+ 'wait',
119
+ '--for=condition=Completed',
120
+ `query/${queryName}`,
121
+ `--timeout=${timeoutSeconds}s`,
122
+ ], { timeout: timeoutSeconds * 1000 });
123
+ if (options.outputFormat === 'name') {
124
+ console.log(queryName);
57
125
  }
58
- catch (error) {
59
- spinner.stop();
60
- // Check if it's a timeout or other error
61
- if (error instanceof Error &&
62
- error.message.includes('timed out waiting')) {
63
- console.error(chalk.red(`Query did not complete within ${options.watchTimeout ?? `${Math.floor(watchTimeoutMs / 1000)}s`}`));
64
- process.exit(ExitCodes.Timeout);
65
- }
66
- // For other errors, fetch the query to check status
67
- }
68
- spinner.stop();
69
- // Fetch final query state
70
- try {
71
- const { stdout } = await execa('kubectl', ['get', 'query', queryName, '-o', 'json'], { stdio: 'pipe' });
72
- const query = JSON.parse(stdout);
73
- const phase = query.status?.phase;
74
- // Check if query completed successfully or with error
75
- if (phase === 'done') {
76
- // Extract and display the response from responses array
77
- if (query.status?.responses && query.status.responses.length > 0) {
78
- const response = query.status.responses[0];
79
- console.log(response.content || response);
80
- }
81
- else {
82
- output.warning('No response received');
83
- }
84
- }
85
- else if (phase === 'error') {
86
- const response = query.status?.responses?.[0];
87
- console.error(chalk.red(response?.content || 'Query failed with unknown error'));
88
- process.exit(ExitCodes.OperationError);
89
- }
90
- else if (phase === 'canceled') {
91
- spinner.warn('Query canceled');
92
- if (query.status?.message) {
93
- output.warning(query.status.message);
94
- }
95
- process.exit(ExitCodes.OperationError);
96
- }
126
+ else if (options.outputFormat === 'json' ||
127
+ options.outputFormat === 'yaml') {
128
+ const { stdout } = await execa('kubectl', ['get', 'query', queryName, '-o', options.outputFormat], { stdio: 'pipe' });
129
+ console.log(stdout);
97
130
  }
98
- catch (error) {
99
- console.error(chalk.red(error instanceof Error
100
- ? error.message
101
- : 'Failed to fetch query result'));
131
+ else {
132
+ console.error(chalk.red(`Invalid output format: ${options.outputFormat}. Use: yaml, json, or name`));
102
133
  process.exit(ExitCodes.CliError);
103
134
  }
104
135
  }
105
136
  catch (error) {
106
- spinner.stop();
107
137
  console.error(chalk.red(error instanceof Error ? error.message : 'Unknown error'));
108
138
  process.exit(ExitCodes.CliError);
109
139
  }
@@ -10,17 +10,26 @@ const mockSpinner = {
10
10
  warn: jest.fn(),
11
11
  stop: jest.fn(),
12
12
  text: '',
13
+ isSpinning: false,
13
14
  };
14
15
  const mockOra = jest.fn(() => mockSpinner);
15
16
  jest.unstable_mockModule('ora', () => ({
16
17
  default: mockOra,
17
18
  }));
18
- const mockOutput = {
19
- warning: jest.fn(),
20
- error: jest.fn(),
19
+ let mockSendMessage = jest.fn();
20
+ const mockChatClient = jest.fn(() => ({
21
+ sendMessage: mockSendMessage,
22
+ }));
23
+ let mockArkApiProxyInstance = {
24
+ start: jest.fn(),
25
+ stop: jest.fn(),
21
26
  };
22
- jest.unstable_mockModule('./output.js', () => ({
23
- default: mockOutput,
27
+ const mockArkApiProxy = jest.fn(() => mockArkApiProxyInstance);
28
+ jest.unstable_mockModule('./arkApiProxy.js', () => ({
29
+ ArkApiProxy: mockArkApiProxy,
30
+ }));
31
+ jest.unstable_mockModule('./chatClient.js', () => ({
32
+ ChatClient: mockChatClient,
24
33
  }));
25
34
  const mockExit = jest.spyOn(process, 'exit').mockImplementation((() => {
26
35
  throw new Error('process.exit called');
@@ -29,12 +38,25 @@ const mockConsoleLog = jest.spyOn(console, 'log').mockImplementation(() => { });
29
38
  const mockConsoleError = jest
30
39
  .spyOn(console, 'error')
31
40
  .mockImplementation(() => { });
41
+ const mockStdoutWrite = jest
42
+ .spyOn(process.stdout, 'write')
43
+ .mockImplementation(() => true);
32
44
  const { executeQuery, parseTarget } = await import('./executeQuery.js');
33
45
  const { ExitCodes } = await import('./errors.js');
34
46
  describe('executeQuery', () => {
35
47
  beforeEach(() => {
36
48
  jest.clearAllMocks();
37
49
  mockSpinner.start.mockReturnValue(mockSpinner);
50
+ mockSpinner.isSpinning = false;
51
+ mockSendMessage = jest.fn();
52
+ mockChatClient.mockReturnValue({ sendMessage: mockSendMessage });
53
+ const startMock = jest.fn();
54
+ startMock.mockResolvedValue({});
55
+ mockArkApiProxyInstance = {
56
+ start: startMock,
57
+ stop: jest.fn(),
58
+ };
59
+ mockArkApiProxy.mockReturnValue(mockArkApiProxyInstance);
38
60
  });
39
61
  describe('parseTarget', () => {
40
62
  it('should parse valid target strings', () => {
@@ -57,24 +79,107 @@ describe('executeQuery', () => {
57
79
  expect(parseTarget('model/default/extra')).toBeNull();
58
80
  });
59
81
  });
60
- describe('executeQuery', () => {
61
- it('should create and apply a query manifest', async () => {
62
- const mockQueryResponse = {
63
- status: {
64
- phase: 'done',
65
- responses: [{ content: 'Test response' }],
66
- },
67
- };
82
+ describe('executeQuery with streaming', () => {
83
+ it('should execute query with streaming and display chunks', async () => {
84
+ mockSendMessage.mockImplementation(async (targetId, messages, options, callback) => {
85
+ callback('Hello', undefined, { agent: 'test-agent' });
86
+ callback(' world', undefined, { agent: 'test-agent' });
87
+ });
88
+ await executeQuery({
89
+ targetType: 'model',
90
+ targetName: 'default',
91
+ message: 'Hello',
92
+ });
93
+ expect(mockArkApiProxy).toHaveBeenCalled();
94
+ expect(mockArkApiProxyInstance.start).toHaveBeenCalled();
95
+ expect(mockChatClient).toHaveBeenCalled();
96
+ expect(mockSendMessage).toHaveBeenCalledWith('model/default', [{ role: 'user', content: 'Hello' }], { streamingEnabled: true }, expect.any(Function));
97
+ expect(mockSpinner.stop).toHaveBeenCalled();
98
+ expect(mockArkApiProxyInstance.stop).toHaveBeenCalled();
99
+ expect(mockStdoutWrite).toHaveBeenCalled();
100
+ });
101
+ it('should display agent names with correct formatting', async () => {
102
+ mockSendMessage.mockImplementation(async (targetId, messages, options, callback) => {
103
+ callback('Response 1', undefined, { agent: 'agent-1' });
104
+ callback('Response 2', undefined, { agent: 'agent-2' });
105
+ });
106
+ await executeQuery({
107
+ targetType: 'agent',
108
+ targetName: 'test-agent',
109
+ message: 'Hello',
110
+ });
111
+ expect(mockStdoutWrite).toHaveBeenCalled();
112
+ const calls = mockStdoutWrite.mock.calls.map((call) => String(call[0]));
113
+ expect(calls.some((call) => call.includes('agent-1'))).toBe(true);
114
+ expect(calls.some((call) => call.includes('agent-2'))).toBe(true);
115
+ });
116
+ it('should display team names with diamond prefix', async () => {
117
+ mockSendMessage.mockImplementation(async (targetId, messages, options, callback) => {
118
+ callback('Team response', undefined, { team: 'my-team' });
119
+ });
120
+ await executeQuery({
121
+ targetType: 'team',
122
+ targetName: 'my-team',
123
+ message: 'Hello',
124
+ });
125
+ const calls = mockStdoutWrite.mock.calls.map((call) => String(call[0]));
126
+ expect(calls.some((call) => call.includes('◆'))).toBe(true);
127
+ expect(calls.some((call) => call.includes('my-team'))).toBe(true);
128
+ });
129
+ it('should display tool calls', async () => {
130
+ mockSendMessage.mockImplementation(async (targetId, messages, options, callback) => {
131
+ callback('', [{ id: 1, function: { name: 'get_weather' } }], {
132
+ agent: 'weather-agent',
133
+ });
134
+ callback('The weather is sunny', undefined, {
135
+ agent: 'weather-agent',
136
+ });
137
+ });
138
+ await executeQuery({
139
+ targetType: 'agent',
140
+ targetName: 'weather-agent',
141
+ message: 'What is the weather?',
142
+ });
143
+ const calls = mockStdoutWrite.mock.calls.map((call) => String(call[0]));
144
+ expect(calls.some((call) => call.includes('get_weather'))).toBe(true);
145
+ expect(calls.some((call) => call.includes('The weather is sunny'))).toBe(true);
146
+ });
147
+ it('should handle errors and exit with CliError', async () => {
148
+ mockSpinner.isSpinning = true;
149
+ const startMock = jest.fn();
150
+ startMock.mockRejectedValue(new Error('Connection failed'));
151
+ mockArkApiProxyInstance.start = startMock;
152
+ await expect(executeQuery({
153
+ targetType: 'model',
154
+ targetName: 'default',
155
+ message: 'Hello',
156
+ })).rejects.toThrow('process.exit called');
157
+ expect(mockSpinner.stop).toHaveBeenCalled();
158
+ expect(mockConsoleError).toHaveBeenCalledWith(expect.stringContaining('Connection failed'));
159
+ expect(mockExit).toHaveBeenCalledWith(ExitCodes.CliError);
160
+ expect(mockArkApiProxyInstance.stop).toHaveBeenCalled();
161
+ });
162
+ it('should stop spinner when first output arrives', async () => {
163
+ mockSpinner.isSpinning = true;
164
+ mockSendMessage.mockImplementation(async (targetId, messages, options, callback) => {
165
+ callback('First chunk', undefined, { agent: 'test-agent' });
166
+ });
167
+ await executeQuery({
168
+ targetType: 'model',
169
+ targetName: 'default',
170
+ message: 'Hello',
171
+ });
172
+ expect(mockSpinner.stop).toHaveBeenCalled();
173
+ });
174
+ });
175
+ describe('executeQuery with output format', () => {
176
+ it('should create query and output name format', async () => {
68
177
  mockExeca.mockImplementation(async (command, args) => {
69
178
  if (args.includes('apply')) {
70
179
  return { stdout: '', stderr: '', exitCode: 0 };
71
180
  }
72
- if (args.includes('get') && args.includes('query')) {
73
- return {
74
- stdout: JSON.stringify(mockQueryResponse),
75
- stderr: '',
76
- exitCode: 0,
77
- };
181
+ if (args.includes('wait')) {
182
+ return { stdout: '', stderr: '', exitCode: 0 };
78
183
  }
79
184
  return { stdout: '', stderr: '', exitCode: 0 };
80
185
  });
@@ -82,83 +187,67 @@ describe('executeQuery', () => {
82
187
  targetType: 'model',
83
188
  targetName: 'default',
84
189
  message: 'Hello',
190
+ outputFormat: 'name',
85
191
  });
86
- expect(mockSpinner.start).toHaveBeenCalled();
87
- expect(mockSpinner.stop).toHaveBeenCalled();
88
- expect(mockConsoleLog).toHaveBeenCalledWith('Test response');
192
+ expect(mockExeca).toHaveBeenCalledWith('kubectl', expect.arrayContaining(['apply', '-f', '-']), expect.any(Object));
193
+ expect(mockExeca).toHaveBeenCalledWith('kubectl', expect.arrayContaining(['wait', '--for=condition=Completed']), expect.any(Object));
194
+ expect(mockConsoleLog).toHaveBeenCalledWith(expect.stringMatching(/cli-query-\d+/));
89
195
  });
90
- it('should handle query error phase and exit with code 2', async () => {
91
- const mockQueryResponse = {
92
- status: {
93
- phase: 'error',
94
- responses: [{ content: 'Query failed with test error' }],
95
- },
196
+ it('should output json format', async () => {
197
+ const mockQuery = {
198
+ apiVersion: 'ark.mckinsey.com/v1alpha1',
199
+ kind: 'Query',
200
+ metadata: { name: 'test-query' },
96
201
  };
97
202
  mockExeca.mockImplementation(async (command, args) => {
98
203
  if (args.includes('apply')) {
99
204
  return { stdout: '', stderr: '', exitCode: 0 };
100
205
  }
101
- if (args.includes('get') && args.includes('query')) {
102
- return {
103
- stdout: JSON.stringify(mockQueryResponse),
104
- stderr: '',
105
- exitCode: 0,
106
- };
206
+ if (args.includes('wait')) {
207
+ return { stdout: '', stderr: '', exitCode: 0 };
208
+ }
209
+ if (args.includes('get') && args.includes('-o')) {
210
+ return { stdout: JSON.stringify(mockQuery), stderr: '', exitCode: 0 };
107
211
  }
108
212
  return { stdout: '', stderr: '', exitCode: 0 };
109
213
  });
110
- try {
111
- await executeQuery({
112
- targetType: 'model',
113
- targetName: 'default',
114
- message: 'Hello',
115
- });
116
- }
117
- catch (error) {
118
- expect(error.message).toBe('process.exit called');
119
- }
120
- expect(mockSpinner.stop).toHaveBeenCalled();
121
- expect(mockConsoleError).toHaveBeenCalledWith(expect.stringContaining('Query failed with test error'));
122
- expect(mockExit).toHaveBeenCalledWith(ExitCodes.OperationError);
214
+ await executeQuery({
215
+ targetType: 'model',
216
+ targetName: 'default',
217
+ message: 'Hello',
218
+ outputFormat: 'json',
219
+ });
220
+ expect(mockConsoleLog).toHaveBeenCalledWith(JSON.stringify(mockQuery));
123
221
  });
124
- it('should handle query canceled phase and exit with code 2', async () => {
125
- const mockQueryResponse = {
126
- status: {
127
- phase: 'canceled',
128
- message: 'Query was canceled',
129
- },
130
- };
222
+ it('should output yaml format', async () => {
223
+ const mockYaml = 'apiVersion: ark.mckinsey.com/v1alpha1\nkind: Query';
131
224
  mockExeca.mockImplementation(async (command, args) => {
132
225
  if (args.includes('apply')) {
133
226
  return { stdout: '', stderr: '', exitCode: 0 };
134
227
  }
135
- if (args.includes('get') && args.includes('query')) {
136
- return {
137
- stdout: JSON.stringify(mockQueryResponse),
138
- stderr: '',
139
- exitCode: 0,
140
- };
228
+ if (args.includes('wait')) {
229
+ return { stdout: '', stderr: '', exitCode: 0 };
230
+ }
231
+ if (args.includes('get') && args.includes('yaml')) {
232
+ return { stdout: mockYaml, stderr: '', exitCode: 0 };
141
233
  }
142
234
  return { stdout: '', stderr: '', exitCode: 0 };
143
235
  });
144
- try {
145
- await executeQuery({
146
- targetType: 'agent',
147
- targetName: 'test-agent',
148
- message: 'Hello',
149
- });
150
- }
151
- catch (error) {
152
- expect(error.message).toBe('process.exit called');
153
- }
154
- expect(mockSpinner.warn).toHaveBeenCalledWith('Query canceled');
155
- expect(mockOutput.warning).toHaveBeenCalledWith('Query was canceled');
156
- expect(mockExit).toHaveBeenCalledWith(ExitCodes.OperationError);
236
+ await executeQuery({
237
+ targetType: 'model',
238
+ targetName: 'default',
239
+ message: 'Hello',
240
+ outputFormat: 'yaml',
241
+ });
242
+ expect(mockConsoleLog).toHaveBeenCalledWith(mockYaml);
157
243
  });
158
- it('should handle kubectl apply failures with exit code 1', async () => {
244
+ it('should reject invalid output format', async () => {
159
245
  mockExeca.mockImplementation(async (command, args) => {
160
246
  if (args.includes('apply')) {
161
- throw new Error('Failed to apply');
247
+ return { stdout: '', stderr: '', exitCode: 0 };
248
+ }
249
+ if (args.includes('wait')) {
250
+ return { stdout: '', stderr: '', exitCode: 0 };
162
251
  }
163
252
  return { stdout: '', stderr: '', exitCode: 0 };
164
253
  });
@@ -166,38 +255,26 @@ describe('executeQuery', () => {
166
255
  targetType: 'model',
167
256
  targetName: 'default',
168
257
  message: 'Hello',
258
+ outputFormat: 'invalid',
169
259
  })).rejects.toThrow('process.exit called');
170
- expect(mockSpinner.stop).toHaveBeenCalled();
171
- expect(mockConsoleError).toHaveBeenCalledWith(expect.stringContaining('Failed to apply'));
260
+ expect(mockConsoleError).toHaveBeenCalledWith(expect.stringContaining('Invalid output format'));
172
261
  expect(mockExit).toHaveBeenCalledWith(ExitCodes.CliError);
173
262
  });
174
- it('should handle query timeout and exit with code 3', async () => {
263
+ it('should handle kubectl errors', async () => {
175
264
  mockExeca.mockImplementation(async (command, args) => {
176
265
  if (args.includes('apply')) {
177
- return { stdout: '', stderr: '', exitCode: 0 };
178
- }
179
- if (args.includes('wait')) {
180
- // Simulate kubectl wait timeout
181
- const error = new Error('timed out waiting for the condition');
182
- throw error;
266
+ throw new Error('kubectl apply failed');
183
267
  }
184
268
  return { stdout: '', stderr: '', exitCode: 0 };
185
269
  });
186
- try {
187
- await executeQuery({
188
- targetType: 'model',
189
- targetName: 'default',
190
- message: 'Hello',
191
- timeout: '100ms',
192
- watchTimeout: '200ms',
193
- });
194
- }
195
- catch (error) {
196
- expect(error.message).toBe('process.exit called');
197
- }
198
- expect(mockSpinner.stop).toHaveBeenCalled();
199
- expect(mockConsoleError).toHaveBeenCalledWith(expect.stringContaining('Query did not complete within 200ms'));
200
- expect(mockExit).toHaveBeenCalledWith(ExitCodes.Timeout);
270
+ await expect(executeQuery({
271
+ targetType: 'model',
272
+ targetName: 'default',
273
+ message: 'Hello',
274
+ outputFormat: 'name',
275
+ })).rejects.toThrow('process.exit called');
276
+ expect(mockConsoleError).toHaveBeenCalledWith(expect.stringContaining('kubectl apply failed'));
277
+ expect(mockExit).toHaveBeenCalledWith(ExitCodes.CliError);
201
278
  });
202
279
  });
203
280
  });
@@ -0,0 +1,15 @@
1
+ interface K8sResource {
2
+ metadata: {
3
+ name: string;
4
+ creationTimestamp?: string;
5
+ };
6
+ }
7
+ export declare function getResource<T extends K8sResource>(resourceType: string, name: string): Promise<T>;
8
+ export declare function listResources<T extends K8sResource>(resourceType: string, options?: {
9
+ sortBy?: string;
10
+ }): Promise<T[]>;
11
+ export declare function deleteResource(resourceType: string, name?: string, options?: {
12
+ all?: boolean;
13
+ }): Promise<void>;
14
+ export declare function replaceResource<T extends K8sResource>(resource: T): Promise<T>;
15
+ export {};
@@ -0,0 +1,47 @@
1
+ import { execa } from 'execa';
2
+ export async function getResource(resourceType, name) {
3
+ if (name === '@latest') {
4
+ const result = await execa('kubectl', [
5
+ 'get',
6
+ resourceType,
7
+ '--sort-by=.metadata.creationTimestamp',
8
+ '-o',
9
+ 'json',
10
+ ], { stdio: 'pipe' });
11
+ const data = JSON.parse(result.stdout);
12
+ const resources = data.items || [];
13
+ if (resources.length === 0) {
14
+ throw new Error(`No ${resourceType} found`);
15
+ }
16
+ return resources[resources.length - 1];
17
+ }
18
+ const result = await execa('kubectl', ['get', resourceType, name, '-o', 'json'], { stdio: 'pipe' });
19
+ return JSON.parse(result.stdout);
20
+ }
21
+ export async function listResources(resourceType, options) {
22
+ const args = ['get', resourceType];
23
+ if (options?.sortBy) {
24
+ args.push(`--sort-by=${options.sortBy}`);
25
+ }
26
+ args.push('-o', 'json');
27
+ const result = await execa('kubectl', args, { stdio: 'pipe' });
28
+ const data = JSON.parse(result.stdout);
29
+ return data.items || [];
30
+ }
31
+ export async function deleteResource(resourceType, name, options) {
32
+ const args = ['delete', resourceType];
33
+ if (options?.all) {
34
+ args.push('--all');
35
+ }
36
+ else if (name) {
37
+ args.push(name);
38
+ }
39
+ await execa('kubectl', args, { stdio: 'pipe' });
40
+ }
41
+ export async function replaceResource(resource) {
42
+ const result = await execa('kubectl', ['replace', '-f', '-', '-o', 'json'], {
43
+ input: JSON.stringify(resource),
44
+ stdio: ['pipe', 'pipe', 'pipe'],
45
+ });
46
+ return JSON.parse(result.stdout);
47
+ }
@@ -0,0 +1 @@
1
+ export {};