@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.
- package/dist/arkServices.js +0 -9
- package/dist/commands/completion/index.js +38 -3
- package/dist/commands/evaluation/index.spec.js +1 -6
- package/dist/commands/generate/generators/team.js +4 -1
- package/dist/commands/install/index.js +27 -0
- package/dist/commands/marketplace/index.d.ts +4 -0
- package/dist/commands/marketplace/index.js +50 -0
- package/dist/commands/models/create.js +1 -1
- package/dist/commands/models/create.spec.js +6 -2
- package/dist/commands/models/providers/azure.spec.js +3 -1
- package/dist/commands/queries/delete.d.ts +7 -0
- package/dist/commands/queries/delete.js +24 -0
- package/dist/commands/queries/delete.spec.d.ts +1 -0
- package/dist/commands/queries/delete.spec.js +74 -0
- package/dist/commands/queries/index.js +42 -4
- package/dist/commands/queries/list.d.ts +6 -0
- package/dist/commands/queries/list.js +66 -0
- package/dist/commands/queries/list.spec.d.ts +1 -0
- package/dist/commands/queries/list.spec.js +170 -0
- package/dist/commands/queries/validation.d.ts +2 -0
- package/dist/commands/queries/validation.js +10 -0
- package/dist/commands/queries/validation.spec.d.ts +1 -0
- package/dist/commands/queries/validation.spec.js +27 -0
- package/dist/commands/uninstall/index.js +27 -0
- package/dist/components/ChatUI.js +2 -2
- package/dist/index.js +2 -0
- package/dist/lib/arkApiClient.js +2 -0
- package/dist/lib/executeQuery.d.ts +0 -4
- package/dist/lib/executeQuery.js +98 -104
- package/dist/lib/executeQuery.spec.js +176 -99
- package/dist/lib/kubectl.d.ts +7 -0
- package/dist/lib/kubectl.js +27 -0
- package/dist/lib/kubectl.spec.js +89 -1
- package/dist/marketplaceServices.d.ts +15 -0
- package/dist/marketplaceServices.js +51 -0
- package/package.json +1 -1
package/dist/arkServices.js
CHANGED
|
@@ -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
|
-
{
|
|
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,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({
|
|
35
|
+
.mockResolvedValueOnce({
|
|
36
|
+
secretAccessKey: 'wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY',
|
|
37
|
+
})
|
|
36
38
|
.mockResolvedValueOnce({ sessionToken: 'optional-session-token' })
|
|
37
|
-
.mockResolvedValueOnce({
|
|
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({
|
|
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-
|
|
13
|
+
// @ts-ignore - TerminalRenderer types are incomplete
|
|
11
14
|
renderer: new TerminalRenderer({
|
|
12
15
|
showSectionPrefix: false,
|
|
13
16
|
reflowText: true,
|
|
14
|
-
// @ts-
|
|
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
|
|
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,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 {};
|