@agents-at-scale/ark 0.1.42 → 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 (36) hide show
  1. package/dist/arkServices.js +0 -9
  2. package/dist/commands/completion/index.js +38 -3
  3. package/dist/commands/evaluation/index.spec.js +1 -6
  4. package/dist/commands/generate/generators/team.js +4 -1
  5. package/dist/commands/install/index.js +27 -0
  6. package/dist/commands/marketplace/index.d.ts +4 -0
  7. package/dist/commands/marketplace/index.js +50 -0
  8. package/dist/commands/models/create.js +1 -1
  9. package/dist/commands/models/create.spec.js +6 -2
  10. package/dist/commands/models/providers/azure.spec.js +3 -1
  11. package/dist/commands/queries/delete.d.ts +7 -0
  12. package/dist/commands/queries/delete.js +24 -0
  13. package/dist/commands/queries/delete.spec.d.ts +1 -0
  14. package/dist/commands/queries/delete.spec.js +74 -0
  15. package/dist/commands/queries/index.js +42 -4
  16. package/dist/commands/queries/list.d.ts +6 -0
  17. package/dist/commands/queries/list.js +66 -0
  18. package/dist/commands/queries/list.spec.d.ts +1 -0
  19. package/dist/commands/queries/list.spec.js +170 -0
  20. package/dist/commands/queries/validation.d.ts +2 -0
  21. package/dist/commands/queries/validation.js +10 -0
  22. package/dist/commands/queries/validation.spec.d.ts +1 -0
  23. package/dist/commands/queries/validation.spec.js +27 -0
  24. package/dist/commands/uninstall/index.js +27 -0
  25. package/dist/components/ChatUI.js +2 -2
  26. package/dist/index.js +2 -0
  27. package/dist/lib/arkApiClient.js +2 -0
  28. package/dist/lib/executeQuery.d.ts +0 -4
  29. package/dist/lib/executeQuery.js +98 -104
  30. package/dist/lib/executeQuery.spec.js +176 -99
  31. package/dist/lib/kubectl.d.ts +7 -0
  32. package/dist/lib/kubectl.js +27 -0
  33. package/dist/lib/kubectl.spec.js +89 -1
  34. package/dist/marketplaceServices.d.ts +15 -0
  35. package/dist/marketplaceServices.js +51 -0
  36. package/package.json +1 -1
@@ -107,15 +107,6 @@ const defaultArkServices = {
107
107
  k8sDevDeploymentName: 'ark-dashboard-devspace',
108
108
  k8sPortForwardLocalPort: 3274,
109
109
  },
110
- 'ark-api-a2a': {
111
- name: 'ark-api-a2a',
112
- helmReleaseName: 'ark-api-a2a',
113
- description: 'Ark API agent-to-agent communication service',
114
- enabled: false, // Disabled - not currently used
115
- category: 'service',
116
- // namespace: undefined - uses current context namespace
117
- // Note: This service might be installed as part of ark-api or separately
118
- },
119
110
  'ark-mcp': {
120
111
  name: 'ark-mcp',
121
112
  helmReleaseName: 'ark-mcp',
@@ -28,7 +28,7 @@ _ark_completion() {
28
28
 
29
29
  case \${COMP_CWORD} in
30
30
  1)
31
- opts="agents chat cluster completion config dashboard docs generate install models queries query routes status targets teams tools uninstall help"
31
+ opts="agents chat cluster completion config dashboard docs generate install marketplace models queries query routes status targets teams tools uninstall help"
32
32
  COMPREPLY=( $(compgen -W "\${opts}" -- \${cur}) )
33
33
  return 0
34
34
  ;;
@@ -79,6 +79,23 @@ _ark_completion() {
79
79
  COMPREPLY=( $(compgen -W "\${opts}" -- \${cur}) )
80
80
  return 0
81
81
  ;;
82
+ marketplace)
83
+ opts="list ls"
84
+ COMPREPLY=( $(compgen -W "\${opts}" -- \${cur}) )
85
+ return 0
86
+ ;;
87
+ install)
88
+ # Suggest marketplace services with marketplace/services/ prefix
89
+ opts="marketplace/services/phoenix marketplace/services/langfuse"
90
+ COMPREPLY=( $(compgen -W "\${opts}" -- \${cur}) )
91
+ return 0
92
+ ;;
93
+ uninstall)
94
+ # Suggest marketplace services with marketplace/services/ prefix
95
+ opts="marketplace/services/phoenix marketplace/services/langfuse"
96
+ COMPREPLY=( $(compgen -W "\${opts}" -- \${cur}) )
97
+ return 0
98
+ ;;
82
99
  chat)
83
100
  # Dynamically fetch available targets using ark targets list
84
101
  local targets
@@ -102,7 +119,7 @@ _ark_completion() {
102
119
  return 0
103
120
  ;;
104
121
  queries)
105
- opts="get"
122
+ opts="get delete resubmit"
106
123
  COMPREPLY=( $(compgen -W "\${opts}" -- \${cur}) )
107
124
  return 0
108
125
  ;;
@@ -142,6 +159,7 @@ _ark() {
142
159
  'docs[Open ARK documentation]' \\
143
160
  'generate[Generate ARK resources]' \\
144
161
  'install[Install ARK services]' \\
162
+ 'marketplace[Manage marketplace services]' \\
145
163
  'models[List available models]' \\
146
164
  'queries[Manage query resources]' \\
147
165
  'query[Execute a single query]' \\
@@ -204,6 +222,21 @@ _ark() {
204
222
  'query[Generate a query]' \\
205
223
  'team[Generate a team]'
206
224
  ;;
225
+ marketplace)
226
+ _values 'marketplace commands' \\
227
+ 'list[List available marketplace services]' \\
228
+ 'ls[List available marketplace services]'
229
+ ;;
230
+ install)
231
+ _values 'services to install' \\
232
+ 'marketplace/services/phoenix[Phoenix observability platform]' \\
233
+ 'marketplace/services/langfuse[Langfuse LLM analytics]'
234
+ ;;
235
+ uninstall)
236
+ _values 'services to uninstall' \\
237
+ 'marketplace/services/phoenix[Phoenix observability platform]' \\
238
+ 'marketplace/services/langfuse[Langfuse LLM analytics]'
239
+ ;;
207
240
  chat)
208
241
  # Get available targets dynamically
209
242
  local -a targets
@@ -224,7 +257,9 @@ _ark() {
224
257
  ;;
225
258
  queries)
226
259
  _values 'queries commands' \\
227
- 'get[Get a specific query]'
260
+ 'get[Get a specific query]' \\
261
+ 'delete[Delete a query]' \\
262
+ 'resubmit[Resubmit a query by clearing its status]'
228
263
  ;;
229
264
  esac
230
265
  ;;
@@ -67,12 +67,7 @@ describe('createEvaluationCommand', () => {
67
67
  it('should execute query evaluation with required arguments', async () => {
68
68
  mockExecuteQueryEvaluation.mockResolvedValue(undefined);
69
69
  const command = createEvaluationCommand({});
70
- await command.parseAsync([
71
- 'node',
72
- 'test',
73
- 'my-evaluator',
74
- 'test-query',
75
- ]);
70
+ await command.parseAsync(['node', 'test', 'my-evaluator', 'test-query']);
76
71
  expect(mockExecuteQueryEvaluation).toHaveBeenCalledWith({
77
72
  evaluatorName: 'my-evaluator',
78
73
  queryName: 'test-query',
@@ -90,7 +90,10 @@ class TeamGenerator {
90
90
  { name: 'Sequential - Agents execute in order', value: 'sequential' },
91
91
  { name: 'Round Robin - Agents take turns', value: 'round-robin' },
92
92
  { name: 'Graph - Custom workflow with dependencies', value: 'graph' },
93
- { name: 'Selector - AI chooses the next agent', value: 'selector' },
93
+ {
94
+ name: 'Selector - AI chooses the next agent (can add graph constraints)',
95
+ value: 'selector',
96
+ },
94
97
  ],
95
98
  default: 'sequential',
96
99
  },
@@ -5,6 +5,7 @@ import inquirer from 'inquirer';
5
5
  import { showNoClusterError } from '../../lib/startup.js';
6
6
  import output from '../../lib/output.js';
7
7
  import { getInstallableServices, arkDependencies, arkServices, } from '../../arkServices.js';
8
+ import { isMarketplaceService, extractMarketplaceServiceName, getMarketplaceService, getAllMarketplaceServices, } from '../../marketplaceServices.js';
8
9
  import { printNextSteps } from '../../lib/nextSteps.js';
9
10
  import ora from 'ora';
10
11
  import { waitForServicesReady, } from '../../lib/waitForReady.js';
@@ -41,6 +42,32 @@ export async function installArk(config, serviceName, options = {}) {
41
42
  console.log(); // Add blank line after cluster info
42
43
  // If a specific service is requested, install only that service
43
44
  if (serviceName) {
45
+ // Check if it's a marketplace service
46
+ if (isMarketplaceService(serviceName)) {
47
+ const marketplaceServiceName = extractMarketplaceServiceName(serviceName);
48
+ const service = getMarketplaceService(marketplaceServiceName);
49
+ if (!service) {
50
+ output.error(`marketplace service '${marketplaceServiceName}' not found`);
51
+ output.info('available marketplace services:');
52
+ const marketplaceServices = getAllMarketplaceServices();
53
+ for (const serviceName of Object.keys(marketplaceServices)) {
54
+ output.info(` marketplace/services/${serviceName}`);
55
+ }
56
+ process.exit(1);
57
+ }
58
+ output.info(`installing marketplace service ${service.name}...`);
59
+ try {
60
+ await installService(service, options.verbose);
61
+ output.success(`${service.name} installed successfully`);
62
+ }
63
+ catch (error) {
64
+ output.error(`failed to install ${service.name}`);
65
+ console.error(error);
66
+ process.exit(1);
67
+ }
68
+ return;
69
+ }
70
+ // Core ARK service
44
71
  const services = getInstallableServices();
45
72
  const service = Object.values(services).find((s) => s.name === serviceName);
46
73
  if (!service) {
@@ -0,0 +1,4 @@
1
+ import { Command } from 'commander';
2
+ import type { ArkConfig } from '../../lib/config.js';
3
+ declare function createMarketplaceCommand(_config: ArkConfig): Command;
4
+ export { createMarketplaceCommand };
@@ -0,0 +1,50 @@
1
+ import { Command } from 'commander';
2
+ import chalk from 'chalk';
3
+ import { getAllMarketplaceServices } from '../../marketplaceServices.js';
4
+ function createMarketplaceCommand(_config) {
5
+ const marketplace = new Command('marketplace');
6
+ marketplace
7
+ .description('Manage marketplace services')
8
+ .addHelpText('before', `
9
+ ${chalk.blue('🏪 ARK Marketplace')}
10
+ Install community-contributed services from the ARK Marketplace.
11
+
12
+ Repository: ${chalk.cyan('https://github.com/mckinsey/agents-at-scale-marketplace')}
13
+ Registry: ${chalk.cyan('ghcr.io/mckinsey/agents-at-scale-marketplace/charts')}
14
+ `)
15
+ .addHelpText('after', `
16
+ ${chalk.cyan('Examples:')}
17
+ ${chalk.yellow('ark marketplace list')} # List available services
18
+ ${chalk.yellow('ark install marketplace/services/phoenix')} # Install Phoenix
19
+ ${chalk.yellow('ark uninstall marketplace/services/phoenix')} # Uninstall Phoenix
20
+
21
+ ${chalk.cyan('Available Services:')}
22
+ • phoenix - AI/ML observability and evaluation platform
23
+ • langfuse - Open-source LLM observability and analytics
24
+ `);
25
+ // List command
26
+ const list = new Command('list');
27
+ list
28
+ .alias('ls')
29
+ .description('List available marketplace services')
30
+ .action(() => {
31
+ const services = getAllMarketplaceServices();
32
+ console.log(chalk.blue('\n🏪 ARK Marketplace Services\n'));
33
+ console.log(chalk.gray('Install with: ark install marketplace/services/<service-name>\n'));
34
+ for (const [key, service] of Object.entries(services)) {
35
+ const icon = '📦';
36
+ const serviceName = `marketplace/services/${key.padEnd(12)}`;
37
+ const serviceDesc = service.description;
38
+ console.log(`${icon} ${chalk.green(serviceName)} ${chalk.gray(serviceDesc)}`);
39
+ const namespaceInfo = `namespace: ${service.namespace || 'default'}`;
40
+ console.log(` ${chalk.dim(namespaceInfo)}`);
41
+ console.log();
42
+ }
43
+ console.log(chalk.cyan('Repository: https://github.com/mckinsey/agents-at-scale-marketplace'));
44
+ console.log(chalk.cyan('Registry: oci://ghcr.io/mckinsey/agents-at-scale-marketplace/charts'));
45
+ console.log();
46
+ });
47
+ marketplace.addCommand(list);
48
+ return marketplace;
49
+ }
50
+ export { createMarketplaceCommand };
@@ -1,7 +1,7 @@
1
1
  import { execa } from 'execa';
2
2
  import inquirer from 'inquirer';
3
3
  import output from '../../lib/output.js';
4
- import { ProviderConfigCollectorFactory } from './providers/index.js';
4
+ import { ProviderConfigCollectorFactory, } from './providers/index.js';
5
5
  import { KubernetesSecretManager } from './kubernetes/secret-manager.js';
6
6
  import { KubernetesModelManifestBuilder } from './kubernetes/manifest-builder.js';
7
7
  export async function createModel(modelName, options = {}) {
@@ -32,9 +32,13 @@ describe('createModel', () => {
32
32
  .mockResolvedValueOnce({ model: 'anthropic.claude-3-sonnet-20240229-v1:0' })
33
33
  .mockResolvedValueOnce({ region: 'us-east-1' })
34
34
  .mockResolvedValueOnce({ accessKeyId: 'AKIAIOSFODNN7EXAMPLE' })
35
- .mockResolvedValueOnce({ secretAccessKey: 'wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY' })
35
+ .mockResolvedValueOnce({
36
+ secretAccessKey: 'wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY',
37
+ })
36
38
  .mockResolvedValueOnce({ sessionToken: 'optional-session-token' })
37
- .mockResolvedValueOnce({ modelArn: 'arn:aws:bedrock:us-east-1:123456789012:model/anthropic.claude-3-sonnet-20240229-v1:0' });
39
+ .mockResolvedValueOnce({
40
+ modelArn: 'arn:aws:bedrock:us-east-1:123456789012:model/anthropic.claude-3-sonnet-20240229-v1:0',
41
+ });
38
42
  mockExeca.mockResolvedValue({}); // kubectl ops succeed
39
43
  await createModel('test-model');
40
44
  // Verify that model type prompt was called
@@ -93,7 +93,9 @@ describe('AzureConfigCollector', () => {
93
93
  expect(validate('https://test.openai.azure.com')).toBe(true);
94
94
  return { baseUrl: 'https://test.openai.azure.com' };
95
95
  });
96
- mockInquirer.prompt.mockResolvedValueOnce({ apiVersion: '2024-12-01-preview' });
96
+ mockInquirer.prompt.mockResolvedValueOnce({
97
+ apiVersion: '2024-12-01-preview',
98
+ });
97
99
  mockInquirer.prompt.mockResolvedValueOnce({ apiKey: 'azure-key' });
98
100
  await collector.collectConfig(options);
99
101
  });
@@ -0,0 +1,7 @@
1
+ interface DeleteQueryOptions {
2
+ all?: boolean;
3
+ }
4
+ export declare const MISSING_NAME_OR_ALL_ERROR = "either provide a query name or use --all flag";
5
+ export declare const BOTH_NAME_AND_ALL_ERROR = "cannot use query name and --all flag together";
6
+ export declare function deleteQuery(name: string | undefined, options: DeleteQueryOptions): Promise<void>;
7
+ export {};
@@ -0,0 +1,24 @@
1
+ import output from '../../lib/output.js';
2
+ import { ExitCodes } from '../../lib/errors.js';
3
+ import { deleteResource } from '../../lib/kubectl.js';
4
+ import { InvalidArgumentError } from 'commander';
5
+ export const MISSING_NAME_OR_ALL_ERROR = 'either provide a query name or use --all flag';
6
+ export const BOTH_NAME_AND_ALL_ERROR = 'cannot use query name and --all flag together';
7
+ function assertDeleteOptionsValid(name, options) {
8
+ if (!name && !options.all) {
9
+ throw new InvalidArgumentError(MISSING_NAME_OR_ALL_ERROR);
10
+ }
11
+ if (name && options.all) {
12
+ throw new InvalidArgumentError(BOTH_NAME_AND_ALL_ERROR);
13
+ }
14
+ }
15
+ export async function deleteQuery(name, options) {
16
+ try {
17
+ assertDeleteOptionsValid(name, options);
18
+ await deleteResource('queries', name, options);
19
+ }
20
+ catch (error) {
21
+ output.error('deleting query:', error instanceof Error ? error.message : error);
22
+ process.exit(ExitCodes.CliError);
23
+ }
24
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,74 @@
1
+ import { jest } from '@jest/globals';
2
+ import output from '../../lib/output.js';
3
+ const mockExeca = jest.fn();
4
+ jest.unstable_mockModule('execa', () => ({
5
+ execa: mockExeca,
6
+ }));
7
+ const { createQueriesCommand } = await import('./index.js');
8
+ const { deleteQuery, BOTH_NAME_AND_ALL_ERROR, MISSING_NAME_OR_ALL_ERROR } = await import('./delete.js');
9
+ describe('queries delete command', () => {
10
+ beforeEach(() => {
11
+ jest.clearAllMocks();
12
+ console.log = jest.fn();
13
+ jest.spyOn(output, 'warning').mockImplementation(() => { });
14
+ jest.spyOn(output, 'error').mockImplementation(() => { });
15
+ jest.spyOn(process, 'exit').mockImplementation(() => undefined);
16
+ });
17
+ it('should delete a query by name', async () => {
18
+ mockExeca.mockResolvedValue({
19
+ stdout: '',
20
+ });
21
+ const command = createQueriesCommand({});
22
+ await command.parseAsync(['node', 'test', 'delete', 'query-1']);
23
+ expect(mockExeca).toHaveBeenCalledWith('kubectl', ['delete', 'queries', 'query-1'], { stdio: 'pipe' });
24
+ expect(output.warning).not.toHaveBeenCalled();
25
+ expect(process.exit).not.toHaveBeenCalled();
26
+ });
27
+ it('should delete all queries with --all flag', async () => {
28
+ mockExeca.mockResolvedValue({
29
+ stdout: '',
30
+ });
31
+ const command = createQueriesCommand({});
32
+ await command.parseAsync(['node', 'test', 'delete', '--all']);
33
+ expect(mockExeca).toHaveBeenCalledWith('kubectl', ['delete', 'queries', '--all'], { stdio: 'pipe' });
34
+ expect(output.warning).not.toHaveBeenCalled();
35
+ expect(process.exit).not.toHaveBeenCalled();
36
+ });
37
+ });
38
+ describe('deleteQuery function', () => {
39
+ beforeEach(() => {
40
+ jest.clearAllMocks();
41
+ jest.spyOn(output, 'error').mockImplementation(() => { });
42
+ jest.spyOn(process, 'exit').mockImplementation(() => undefined);
43
+ });
44
+ it('should throw error when neither name nor --all flag is provided', async () => {
45
+ await deleteQuery(undefined, {});
46
+ expect(output.error).toHaveBeenCalledWith(expect.anything(), expect.stringMatching(MISSING_NAME_OR_ALL_ERROR));
47
+ expect(process.exit).toHaveBeenCalledWith(1);
48
+ });
49
+ it('should throw error when both name and --all flag are provided', async () => {
50
+ await deleteQuery('my-query', { all: true });
51
+ expect(output.error).toHaveBeenCalledWith(expect.anything(), expect.stringMatching(BOTH_NAME_AND_ALL_ERROR));
52
+ expect(process.exit).toHaveBeenCalledWith(1);
53
+ });
54
+ it('should handle deletion errors gracefully', async () => {
55
+ mockExeca.mockRejectedValue(new Error('query not found'));
56
+ await deleteQuery('nonexistent-query', {});
57
+ expect(output.error).toHaveBeenCalledWith('deleting query:', 'query not found');
58
+ expect(process.exit).toHaveBeenCalledWith(1);
59
+ });
60
+ it('should call deleteResource with query name', async () => {
61
+ mockExeca.mockResolvedValue({
62
+ stdout: '',
63
+ });
64
+ await deleteQuery('my-query', {});
65
+ expect(mockExeca).toHaveBeenCalledWith('kubectl', ['delete', 'queries', 'my-query'], { stdio: 'pipe' });
66
+ });
67
+ it('should delete all queries when all option is true', async () => {
68
+ mockExeca.mockResolvedValue({
69
+ stdout: '',
70
+ });
71
+ await deleteQuery(undefined, { all: true });
72
+ expect(mockExeca).toHaveBeenCalledWith('kubectl', ['delete', 'queries', '--all'], { stdio: 'pipe' });
73
+ });
74
+ });
@@ -1,17 +1,20 @@
1
1
  import { Command } from 'commander';
2
2
  import { marked } from 'marked';
3
+ // @ts-ignore - no types available
3
4
  import TerminalRenderer from 'marked-terminal';
4
5
  import output from '../../lib/output.js';
5
6
  import { ExitCodes } from '../../lib/errors.js';
6
- import { getResource } from '../../lib/kubectl.js';
7
+ import { getResource, replaceResource } from '../../lib/kubectl.js';
8
+ import { listQueries } from './list.js';
9
+ import { deleteQuery } from './delete.js';
7
10
  function renderMarkdown(content) {
8
11
  if (process.stdout.isTTY) {
9
12
  marked.setOptions({
10
- // @ts-expect-error - TerminalRenderer types are incomplete
13
+ // @ts-ignore - TerminalRenderer types are incomplete
11
14
  renderer: new TerminalRenderer({
12
15
  showSectionPrefix: false,
13
16
  reflowText: true,
14
- // @ts-expect-error - preserveNewlines exists but not in types
17
+ // @ts-ignore - preserveNewlines exists but not in types
15
18
  preserveNewlines: true,
16
19
  }),
17
20
  });
@@ -55,7 +58,13 @@ async function getQuery(name, options) {
55
58
  }
56
59
  export function createQueriesCommand(_) {
57
60
  const queriesCommand = new Command('queries');
58
- queriesCommand.description('Manage query resources');
61
+ queriesCommand
62
+ .description('List all queries')
63
+ .option('-o, --output <format>', 'output format (json or text)', 'text')
64
+ .option('--sort-by <field>', 'sort by kubernetes field (e.g., .metadata.name)')
65
+ .action(async (options) => {
66
+ await listQueries(options);
67
+ });
59
68
  const getCommand = new Command('get');
60
69
  getCommand
61
70
  .description('Get a specific query (@latest for most recent)')
@@ -66,5 +75,34 @@ export function createQueriesCommand(_) {
66
75
  await getQuery(name, options);
67
76
  });
68
77
  queriesCommand.addCommand(getCommand);
78
+ const deleteCommand = new Command('delete');
79
+ deleteCommand
80
+ .description('Delete a query')
81
+ .argument('[name]', 'Query name')
82
+ .option('--all', 'delete all queries', false)
83
+ .action(async (name, options) => {
84
+ await deleteQuery(name, options);
85
+ });
86
+ queriesCommand.addCommand(deleteCommand);
87
+ const resubmitCommand = new Command('resubmit');
88
+ resubmitCommand
89
+ .description('Resubmit a query by clearing its status (@latest for most recent)')
90
+ .argument('<name>', 'Query name or @latest')
91
+ .action(async (name) => {
92
+ try {
93
+ const query = await getResource('queries', name);
94
+ const queryWithoutStatus = {
95
+ ...query,
96
+ status: undefined,
97
+ };
98
+ await replaceResource(queryWithoutStatus);
99
+ output.success(`Query '${query.metadata.name}' resubmitted`);
100
+ }
101
+ catch (error) {
102
+ output.error('resubmitting query:', error instanceof Error ? error.message : error);
103
+ process.exit(ExitCodes.CliError);
104
+ }
105
+ });
106
+ queriesCommand.addCommand(resubmitCommand);
69
107
  return queriesCommand;
70
108
  }
@@ -0,0 +1,6 @@
1
+ interface ListQueriesOptions {
2
+ output?: string;
3
+ sortBy?: string;
4
+ }
5
+ export declare function listQueries(options: ListQueriesOptions): Promise<void>;
6
+ export {};
@@ -0,0 +1,66 @@
1
+ import chalk from 'chalk';
2
+ import output from '../../lib/output.js';
3
+ import { ExitCodes } from '../../lib/errors.js';
4
+ import { listResources } from '../../lib/kubectl.js';
5
+ import { assertSupportedOutputFormat } from './validation.js';
6
+ // Output format constants
7
+ const OUTPUT_FORMAT_JSON = 'json';
8
+ // Query phase constants
9
+ const PHASE_DONE = 'done';
10
+ const PHASE_RUNNING = 'running';
11
+ const PHASE_ERROR = 'error';
12
+ const PHASE_UNKNOWN = 'unknown';
13
+ // Column padding
14
+ const COLUMN_PADDING = 2;
15
+ const MIN_NAME_LENGTH = 4;
16
+ function getStatusColor(status) {
17
+ switch (status) {
18
+ case PHASE_DONE:
19
+ return chalk.green;
20
+ case PHASE_RUNNING:
21
+ return chalk.blue;
22
+ case PHASE_ERROR:
23
+ return chalk.red;
24
+ default:
25
+ return chalk.yellow;
26
+ }
27
+ }
28
+ function printTableHeader(maxNameLength) {
29
+ const paddedHeaderLength = maxNameLength + COLUMN_PADDING;
30
+ const header = `${'NAME'.padEnd(paddedHeaderLength)}${'STATUS'}`;
31
+ console.log(header);
32
+ }
33
+ function printTableRow(query, maxNameLength) {
34
+ const status = query.status?.phase || PHASE_UNKNOWN;
35
+ const statusColor = getStatusColor(status);
36
+ const paddedNameLength = maxNameLength + COLUMN_PADDING;
37
+ console.log(`${query.metadata.name.padEnd(paddedNameLength)}${statusColor(status)}`);
38
+ }
39
+ function printResult(queries, options) {
40
+ if (options.output === OUTPUT_FORMAT_JSON) {
41
+ console.log(JSON.stringify(queries, null, 2));
42
+ return;
43
+ }
44
+ if (queries.length === 0) {
45
+ output.warning('no queries available');
46
+ return;
47
+ }
48
+ const maxNameLength = Math.max(MIN_NAME_LENGTH, ...queries.map((q) => q.metadata.name.length));
49
+ printTableHeader(maxNameLength);
50
+ queries.forEach((query) => {
51
+ printTableRow(query, maxNameLength);
52
+ });
53
+ }
54
+ export async function listQueries(options) {
55
+ try {
56
+ assertSupportedOutputFormat(options.output);
57
+ const queries = await listResources('queries', {
58
+ sortBy: options.sortBy,
59
+ });
60
+ printResult(queries, options);
61
+ }
62
+ catch (error) {
63
+ output.error('fetching queries:', error instanceof Error ? error.message : error);
64
+ process.exit(ExitCodes.CliError);
65
+ }
66
+ }
@@ -0,0 +1 @@
1
+ export {};