@agents-at-scale/ark 0.1.42 → 0.1.44
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 +12 -18
- package/dist/commands/completion/index.js +38 -3
- package/dist/commands/evaluation/index.spec.js +1 -6
- package/dist/commands/generate/generators/project.js +3 -3
- package/dist/commands/generate/generators/team.js +4 -1
- package/dist/commands/generate/index.js +2 -2
- 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/query/index.js +2 -0
- package/dist/commands/query/index.spec.js +24 -0
- package/dist/commands/uninstall/index.js +27 -0
- package/dist/components/ChatUI.js +14 -4
- package/dist/index.js +2 -0
- package/dist/lib/arkApiClient.js +2 -0
- package/dist/lib/chatClient.d.ts +4 -0
- package/dist/lib/chatClient.js +23 -7
- package/dist/lib/chatClient.spec.d.ts +1 -0
- package/dist/lib/chatClient.spec.js +108 -0
- package/dist/lib/constants.d.ts +3 -0
- package/dist/lib/constants.js +8 -0
- package/dist/lib/executeQuery.d.ts +1 -4
- package/dist/lib/executeQuery.js +103 -104
- package/dist/lib/executeQuery.spec.js +218 -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/lib/types.d.ts +22 -7
- package/dist/marketplaceServices.d.ts +15 -0
- package/dist/marketplaceServices.js +51 -0
- package/package.json +1 -1
- package/templates/models/azure.yaml +1 -1
- package/templates/project/Makefile +1 -1
- package/templates/project/README.md +1 -1
- package/templates/project/scripts/setup.sh +2 -2
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',
|
|
@@ -128,6 +119,18 @@ const defaultArkServices = {
|
|
|
128
119
|
k8sDeploymentName: 'ark-mcp',
|
|
129
120
|
k8sDevDeploymentName: 'ark-mcp-devspace',
|
|
130
121
|
},
|
|
122
|
+
'ark-cluster-memory': {
|
|
123
|
+
name: 'ark-cluster-memory',
|
|
124
|
+
helmReleaseName: 'ark-cluster-memory',
|
|
125
|
+
description: 'In-memory storage service with streaming support for Ark queries',
|
|
126
|
+
enabled: true,
|
|
127
|
+
category: 'service',
|
|
128
|
+
// namespace: undefined - uses current context namespace
|
|
129
|
+
chartPath: `${REGISTRY_BASE}/ark-cluster-memory`,
|
|
130
|
+
installArgs: [],
|
|
131
|
+
k8sDeploymentName: 'ark-cluster-memory',
|
|
132
|
+
k8sDevDeploymentName: 'ark-cluster-memory-devspace',
|
|
133
|
+
},
|
|
131
134
|
'mcp-filesystem': {
|
|
132
135
|
name: 'mcp-filesystem',
|
|
133
136
|
helmReleaseName: 'mcp-filesystem',
|
|
@@ -140,15 +143,6 @@ const defaultArkServices = {
|
|
|
140
143
|
k8sDeploymentName: 'mcp-filesystem',
|
|
141
144
|
k8sDevDeploymentName: 'mcp-filesystem-devspace',
|
|
142
145
|
},
|
|
143
|
-
'agents-at-scale': {
|
|
144
|
-
name: 'agents-at-scale',
|
|
145
|
-
helmReleaseName: 'agents-at-scale',
|
|
146
|
-
description: 'Agents @ Scale Platform',
|
|
147
|
-
enabled: false,
|
|
148
|
-
category: 'service',
|
|
149
|
-
chartPath: 'oci://ghcr.io/mck-private/qb-fm-labs-legacyx/charts/legacyx',
|
|
150
|
-
installArgs: [],
|
|
151
|
-
},
|
|
152
146
|
'localhost-gateway': {
|
|
153
147
|
name: 'localhost-gateway',
|
|
154
148
|
helmReleaseName: 'localhost-gateway',
|
|
@@ -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',
|
|
@@ -727,16 +727,16 @@ Generated with ARK CLI generator`;
|
|
|
727
727
|
},
|
|
728
728
|
];
|
|
729
729
|
if (config.projectType === 'empty') {
|
|
730
|
-
steps.push({ desc: 'Add YAML files to agents/, teams/, queries/ directories' }, { desc: 'Copy model configurations from samples/models/' }, { desc: 'Edit .env file to set your API keys' }, { desc: 'Deploy your project', cmd: '
|
|
730
|
+
steps.push({ desc: 'Add YAML files to agents/, teams/, queries/ directories' }, { desc: 'Copy model configurations from samples/models/' }, { desc: 'Edit .env file to set your API keys' }, { desc: 'Deploy your project', cmd: 'devspace dev' });
|
|
731
731
|
}
|
|
732
732
|
else if (config.selectedModels && config.selectedModels !== 'none') {
|
|
733
|
-
steps.push({ desc: 'Edit .env file to set your API keys' }, { desc: 'Load environment variables', cmd: 'source .env' }, { desc: 'Deploy your project', cmd: '
|
|
733
|
+
steps.push({ desc: 'Edit .env file to set your API keys' }, { desc: 'Load environment variables', cmd: 'source .env' }, { desc: 'Deploy your project', cmd: 'devspace dev' }, {
|
|
734
734
|
desc: 'Test your deployment',
|
|
735
735
|
cmd: `kubectl get query sample-team-query -w --namespace ${config.namespace}`,
|
|
736
736
|
});
|
|
737
737
|
}
|
|
738
738
|
else {
|
|
739
|
-
steps.push({ desc: 'Copy model configurations from samples/models/' }, { desc: 'Edit .env file to set your API keys' }, { desc: 'Deploy your project', cmd: '
|
|
739
|
+
steps.push({ desc: 'Copy model configurations from samples/models/' }, { desc: 'Edit .env file to set your API keys' }, { desc: 'Deploy your project', cmd: 'devspace dev' });
|
|
740
740
|
}
|
|
741
741
|
console.log(chalk.magenta.bold('🚀 NEXT STEPS:\n'));
|
|
742
742
|
let stepNumber = 1;
|
|
@@ -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
|
},
|
|
@@ -56,7 +56,7 @@ ${chalk.cyan('Getting started:')}
|
|
|
56
56
|
1. ${chalk.yellow('ark generate project my-first-project')} # Create project
|
|
57
57
|
2. ${chalk.yellow('cd my-first-project')} # Enter directory
|
|
58
58
|
3. ${chalk.yellow('source .env')} # Set environment
|
|
59
|
-
4. ${chalk.yellow('
|
|
59
|
+
4. ${chalk.yellow('devspace dev')} # Deploy to cluster
|
|
60
60
|
`);
|
|
61
61
|
// Register generators
|
|
62
62
|
const generators = new Map();
|
|
@@ -345,7 +345,7 @@ ${chalk.cyan('Use Cases:')}
|
|
|
345
345
|
console.log(chalk.cyan('\n📖 Quick Start:'));
|
|
346
346
|
console.log(chalk.gray(' 1. ark generate project my-first-project'));
|
|
347
347
|
console.log(chalk.gray(' 2. cd my-first-project && source .env'));
|
|
348
|
-
console.log(chalk.gray(' 3.
|
|
348
|
+
console.log(chalk.gray(' 3. devspace dev'));
|
|
349
349
|
console.log(chalk.cyan('\n🔧 Usage:'));
|
|
350
350
|
console.log(chalk.gray(' ark generate <type> [name] [options]'));
|
|
351
351
|
console.log(chalk.gray(' ark g <type> [name] [options]'));
|
|
@@ -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 {};
|