@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.
- package/dist/arkServices.js +0 -9
- package/dist/commands/completion/index.js +46 -1
- package/dist/commands/evaluation/index.d.ts +3 -0
- package/dist/commands/evaluation/index.js +60 -0
- package/dist/commands/evaluation/index.spec.d.ts +1 -0
- package/dist/commands/evaluation/index.spec.js +161 -0
- 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/memory/index.d.ts +15 -0
- package/dist/commands/memory/index.js +130 -0
- package/dist/commands/memory/index.spec.d.ts +1 -0
- package/dist/commands/memory/index.spec.js +124 -0
- package/dist/commands/models/create.d.ts +5 -6
- package/dist/commands/models/create.js +14 -119
- package/dist/commands/models/create.spec.js +51 -0
- package/dist/commands/models/kubernetes/manifest-builder.d.ts +11 -0
- package/dist/commands/models/kubernetes/manifest-builder.js +109 -0
- package/dist/commands/models/kubernetes/secret-manager.d.ts +7 -0
- package/dist/commands/models/kubernetes/secret-manager.js +20 -0
- package/dist/commands/models/providers/azure.d.ts +31 -0
- package/dist/commands/models/providers/azure.js +82 -0
- package/dist/commands/models/providers/azure.spec.d.ts +1 -0
- package/dist/commands/models/providers/azure.spec.js +232 -0
- package/dist/commands/models/providers/bedrock.d.ts +37 -0
- package/dist/commands/models/providers/bedrock.js +105 -0
- package/dist/commands/models/providers/bedrock.spec.d.ts +1 -0
- package/dist/commands/models/providers/bedrock.spec.js +241 -0
- package/dist/commands/models/providers/factory.d.ts +18 -0
- package/dist/commands/models/providers/factory.js +31 -0
- package/dist/commands/models/providers/index.d.ts +17 -0
- package/dist/commands/models/providers/index.js +9 -0
- package/dist/commands/models/providers/openai.d.ts +28 -0
- package/dist/commands/models/providers/openai.js +68 -0
- package/dist/commands/models/providers/openai.spec.d.ts +1 -0
- package/dist/commands/models/providers/openai.spec.js +180 -0
- package/dist/commands/models/providers/types.d.ts +51 -0
- package/dist/commands/models/providers/types.js +1 -0
- 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.d.ts +3 -0
- package/dist/commands/queries/index.js +108 -0
- 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 +3 -1
- package/dist/commands/query/index.spec.js +24 -0
- package/dist/commands/uninstall/index.js +27 -0
- package/dist/components/ChatUI.js +2 -0
- package/dist/index.js +8 -0
- package/dist/lib/arkApiClient.d.ts +4 -0
- package/dist/lib/arkApiClient.js +57 -0
- package/dist/lib/errors.d.ts +1 -0
- package/dist/lib/errors.js +1 -0
- package/dist/lib/executeEvaluation.d.ts +16 -0
- package/dist/lib/executeEvaluation.js +155 -0
- package/dist/lib/executeQuery.d.ts +1 -4
- package/dist/lib/executeQuery.js +98 -68
- package/dist/lib/executeQuery.spec.js +176 -99
- package/dist/lib/kubectl.d.ts +15 -0
- package/dist/lib/kubectl.js +47 -0
- package/dist/lib/kubectl.spec.d.ts +1 -0
- package/dist/lib/kubectl.spec.js +176 -0
- package/dist/lib/stdin.d.ts +1 -0
- package/dist/lib/stdin.js +16 -0
- package/dist/lib/stdin.spec.d.ts +1 -0
- package/dist/lib/stdin.spec.js +82 -0
- package/dist/lib/types.d.ts +39 -0
- package/dist/marketplaceServices.d.ts +15 -0
- package/dist/marketplaceServices.js +51 -0
- package/package.json +2 -1
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Provider configuration types and collectors.
|
|
3
|
+
*
|
|
4
|
+
* This module exports all provider-specific configurations and their collectors.
|
|
5
|
+
*/
|
|
6
|
+
export { BaseProviderConfig, BaseCollectorOptions, ProviderConfigCollector, } from './types.js';
|
|
7
|
+
export { OpenAIConfig, OpenAICollectorOptions, OpenAIConfigCollector, } from './openai.js';
|
|
8
|
+
export { AzureConfig, AzureCollectorOptions, AzureConfigCollector, } from './azure.js';
|
|
9
|
+
export { BedrockConfig, BedrockCollectorOptions, BedrockConfigCollector, } from './bedrock.js';
|
|
10
|
+
export { ProviderConfigCollectorFactory } from './factory.js';
|
|
11
|
+
import { OpenAIConfig } from './openai.js';
|
|
12
|
+
import { AzureConfig } from './azure.js';
|
|
13
|
+
import { BedrockConfig } from './bedrock.js';
|
|
14
|
+
/**
|
|
15
|
+
* Union type of all supported provider configurations.
|
|
16
|
+
*/
|
|
17
|
+
export type ProviderConfig = OpenAIConfig | AzureConfig | BedrockConfig;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Provider configuration types and collectors.
|
|
3
|
+
*
|
|
4
|
+
* This module exports all provider-specific configurations and their collectors.
|
|
5
|
+
*/
|
|
6
|
+
export { OpenAIConfigCollector, } from './openai.js';
|
|
7
|
+
export { AzureConfigCollector, } from './azure.js';
|
|
8
|
+
export { BedrockConfigCollector, } from './bedrock.js';
|
|
9
|
+
export { ProviderConfigCollectorFactory } from './factory.js';
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { BaseProviderConfig, BaseCollectorOptions, ProviderConfigCollector } from './types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Configuration for OpenAI models.
|
|
4
|
+
*/
|
|
5
|
+
export interface OpenAIConfig extends BaseProviderConfig {
|
|
6
|
+
type: 'openai';
|
|
7
|
+
baseUrl: string;
|
|
8
|
+
apiKey: string;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Options specific to OpenAI collector.
|
|
12
|
+
*/
|
|
13
|
+
export interface OpenAICollectorOptions extends BaseCollectorOptions {
|
|
14
|
+
baseUrl?: string;
|
|
15
|
+
apiKey?: string;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Configuration collector for OpenAI models.
|
|
19
|
+
*
|
|
20
|
+
* Collects the necessary configuration to connect to an OpenAI-compatible API:
|
|
21
|
+
* - baseUrl: The API endpoint URL (e.g., https://api.openai.com/v1)
|
|
22
|
+
* - apiKey: The authentication key for the API
|
|
23
|
+
*
|
|
24
|
+
* Values can be provided via command-line options or will be prompted interactively.
|
|
25
|
+
*/
|
|
26
|
+
export declare class OpenAIConfigCollector implements ProviderConfigCollector {
|
|
27
|
+
collectConfig(options: BaseCollectorOptions): Promise<OpenAIConfig>;
|
|
28
|
+
}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import inquirer from 'inquirer';
|
|
2
|
+
/**
|
|
3
|
+
* Configuration collector for OpenAI models.
|
|
4
|
+
*
|
|
5
|
+
* Collects the necessary configuration to connect to an OpenAI-compatible API:
|
|
6
|
+
* - baseUrl: The API endpoint URL (e.g., https://api.openai.com/v1)
|
|
7
|
+
* - apiKey: The authentication key for the API
|
|
8
|
+
*
|
|
9
|
+
* Values can be provided via command-line options or will be prompted interactively.
|
|
10
|
+
*/
|
|
11
|
+
export class OpenAIConfigCollector {
|
|
12
|
+
async collectConfig(options) {
|
|
13
|
+
const openaiOptions = options;
|
|
14
|
+
let baseUrl = openaiOptions.baseUrl;
|
|
15
|
+
if (!baseUrl) {
|
|
16
|
+
const answer = await inquirer.prompt([
|
|
17
|
+
{
|
|
18
|
+
type: 'input',
|
|
19
|
+
name: 'baseUrl',
|
|
20
|
+
message: 'base URL:',
|
|
21
|
+
validate: (input) => {
|
|
22
|
+
if (!input)
|
|
23
|
+
return 'base URL is required';
|
|
24
|
+
try {
|
|
25
|
+
new URL(input);
|
|
26
|
+
return true;
|
|
27
|
+
}
|
|
28
|
+
catch {
|
|
29
|
+
return 'please enter a valid URL';
|
|
30
|
+
}
|
|
31
|
+
},
|
|
32
|
+
},
|
|
33
|
+
]);
|
|
34
|
+
baseUrl = answer.baseUrl;
|
|
35
|
+
}
|
|
36
|
+
if (!baseUrl) {
|
|
37
|
+
throw new Error('base URL is required');
|
|
38
|
+
}
|
|
39
|
+
baseUrl = baseUrl.replace(/\/$/, '');
|
|
40
|
+
let apiKey = openaiOptions.apiKey;
|
|
41
|
+
if (!apiKey) {
|
|
42
|
+
const answer = await inquirer.prompt([
|
|
43
|
+
{
|
|
44
|
+
type: 'password',
|
|
45
|
+
name: 'apiKey',
|
|
46
|
+
message: 'API key:',
|
|
47
|
+
mask: '*',
|
|
48
|
+
validate: (input) => {
|
|
49
|
+
if (!input)
|
|
50
|
+
return 'API key is required';
|
|
51
|
+
return true;
|
|
52
|
+
},
|
|
53
|
+
},
|
|
54
|
+
]);
|
|
55
|
+
apiKey = answer.apiKey;
|
|
56
|
+
}
|
|
57
|
+
if (!apiKey) {
|
|
58
|
+
throw new Error('API key is required');
|
|
59
|
+
}
|
|
60
|
+
return {
|
|
61
|
+
type: 'openai',
|
|
62
|
+
modelValue: options.model,
|
|
63
|
+
secretName: '',
|
|
64
|
+
baseUrl,
|
|
65
|
+
apiKey,
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
import { jest } from '@jest/globals';
|
|
2
|
+
const mockInquirer = {
|
|
3
|
+
prompt: jest.fn(),
|
|
4
|
+
};
|
|
5
|
+
jest.unstable_mockModule('inquirer', () => ({
|
|
6
|
+
default: mockInquirer,
|
|
7
|
+
}));
|
|
8
|
+
const { OpenAIConfigCollector } = await import('./openai.js');
|
|
9
|
+
describe('OpenAIConfigCollector', () => {
|
|
10
|
+
let collector;
|
|
11
|
+
beforeEach(() => {
|
|
12
|
+
collector = new OpenAIConfigCollector();
|
|
13
|
+
jest.clearAllMocks();
|
|
14
|
+
});
|
|
15
|
+
describe('collectConfig', () => {
|
|
16
|
+
it('uses provided options without prompting', async () => {
|
|
17
|
+
const options = {
|
|
18
|
+
model: 'gpt-4o-mini',
|
|
19
|
+
baseUrl: 'https://api.openai.com/v1',
|
|
20
|
+
apiKey: 'sk-test-key-12345',
|
|
21
|
+
};
|
|
22
|
+
const config = await collector.collectConfig(options);
|
|
23
|
+
expect(mockInquirer.prompt).not.toHaveBeenCalled();
|
|
24
|
+
expect(config).toEqual({
|
|
25
|
+
type: 'openai',
|
|
26
|
+
modelValue: 'gpt-4o-mini',
|
|
27
|
+
secretName: '',
|
|
28
|
+
baseUrl: 'https://api.openai.com/v1',
|
|
29
|
+
apiKey: 'sk-test-key-12345',
|
|
30
|
+
});
|
|
31
|
+
});
|
|
32
|
+
it('prompts for missing baseUrl', async () => {
|
|
33
|
+
mockInquirer.prompt.mockResolvedValueOnce({
|
|
34
|
+
baseUrl: 'https://custom-openai.com',
|
|
35
|
+
});
|
|
36
|
+
mockInquirer.prompt.mockResolvedValueOnce({ apiKey: 'sk-custom-key' });
|
|
37
|
+
const options = {
|
|
38
|
+
model: 'gpt-4',
|
|
39
|
+
};
|
|
40
|
+
const config = await collector.collectConfig(options);
|
|
41
|
+
expect(mockInquirer.prompt).toHaveBeenCalledWith([
|
|
42
|
+
expect.objectContaining({
|
|
43
|
+
type: 'input',
|
|
44
|
+
name: 'baseUrl',
|
|
45
|
+
message: 'base URL:',
|
|
46
|
+
validate: expect.any(Function),
|
|
47
|
+
}),
|
|
48
|
+
]);
|
|
49
|
+
expect(config.baseUrl).toBe('https://custom-openai.com');
|
|
50
|
+
});
|
|
51
|
+
it('validates baseUrl is required', async () => {
|
|
52
|
+
mockInquirer.prompt.mockResolvedValueOnce({ baseUrl: '' });
|
|
53
|
+
const options = {
|
|
54
|
+
model: 'gpt-4',
|
|
55
|
+
};
|
|
56
|
+
// The validation happens in the prompt, but we test the final check
|
|
57
|
+
await expect(collector.collectConfig(options)).rejects.toThrow('base URL is required');
|
|
58
|
+
});
|
|
59
|
+
it('validates baseUrl is a valid URL', async () => {
|
|
60
|
+
const options = {
|
|
61
|
+
model: 'gpt-4',
|
|
62
|
+
};
|
|
63
|
+
// Get the validate function from the prompt call
|
|
64
|
+
mockInquirer.prompt.mockImplementationOnce(async (questions) => {
|
|
65
|
+
const validate = questions[0].validate;
|
|
66
|
+
// Test invalid URL
|
|
67
|
+
expect(validate('not-a-url')).toBe('please enter a valid URL');
|
|
68
|
+
// Test empty string
|
|
69
|
+
expect(validate('')).toBe('base URL is required');
|
|
70
|
+
// Test valid URL
|
|
71
|
+
expect(validate('https://api.openai.com')).toBe(true);
|
|
72
|
+
return { baseUrl: 'https://api.openai.com' };
|
|
73
|
+
});
|
|
74
|
+
mockInquirer.prompt.mockResolvedValueOnce({ apiKey: 'sk-key' });
|
|
75
|
+
await collector.collectConfig(options);
|
|
76
|
+
});
|
|
77
|
+
it('removes trailing slash from baseUrl', async () => {
|
|
78
|
+
const options = {
|
|
79
|
+
model: 'gpt-4',
|
|
80
|
+
baseUrl: 'https://api.openai.com/v1/',
|
|
81
|
+
apiKey: 'sk-test-key',
|
|
82
|
+
};
|
|
83
|
+
const config = await collector.collectConfig(options);
|
|
84
|
+
expect(config.baseUrl).toBe('https://api.openai.com/v1');
|
|
85
|
+
});
|
|
86
|
+
it('prompts for missing apiKey as password field', async () => {
|
|
87
|
+
mockInquirer.prompt.mockResolvedValueOnce({ apiKey: 'sk-secret-key' });
|
|
88
|
+
const options = {
|
|
89
|
+
model: 'gpt-4',
|
|
90
|
+
baseUrl: 'https://api.openai.com/v1',
|
|
91
|
+
};
|
|
92
|
+
const config = await collector.collectConfig(options);
|
|
93
|
+
expect(mockInquirer.prompt).toHaveBeenCalledWith([
|
|
94
|
+
expect.objectContaining({
|
|
95
|
+
type: 'password',
|
|
96
|
+
name: 'apiKey',
|
|
97
|
+
message: 'API key:',
|
|
98
|
+
mask: '*',
|
|
99
|
+
validate: expect.any(Function),
|
|
100
|
+
}),
|
|
101
|
+
]);
|
|
102
|
+
expect(config.apiKey).toBe('sk-secret-key');
|
|
103
|
+
});
|
|
104
|
+
it('validates apiKey is required', async () => {
|
|
105
|
+
mockInquirer.prompt.mockResolvedValueOnce({ apiKey: '' });
|
|
106
|
+
const options = {
|
|
107
|
+
model: 'gpt-4',
|
|
108
|
+
baseUrl: 'https://api.openai.com/v1',
|
|
109
|
+
};
|
|
110
|
+
await expect(collector.collectConfig(options)).rejects.toThrow('API key is required');
|
|
111
|
+
});
|
|
112
|
+
it('tests apiKey validation function', async () => {
|
|
113
|
+
const options = {
|
|
114
|
+
model: 'gpt-4',
|
|
115
|
+
baseUrl: 'https://api.openai.com/v1',
|
|
116
|
+
};
|
|
117
|
+
// Get the validate function from the prompt call
|
|
118
|
+
mockInquirer.prompt.mockImplementationOnce(async (questions) => {
|
|
119
|
+
const validate = questions[0].validate;
|
|
120
|
+
// Test empty string
|
|
121
|
+
expect(validate('')).toBe('API key is required');
|
|
122
|
+
// Test valid key
|
|
123
|
+
expect(validate('sk-valid-key')).toBe(true);
|
|
124
|
+
return { apiKey: 'sk-valid-key' };
|
|
125
|
+
});
|
|
126
|
+
await collector.collectConfig(options);
|
|
127
|
+
});
|
|
128
|
+
it('collects full configuration through interactive prompts', async () => {
|
|
129
|
+
mockInquirer.prompt.mockResolvedValueOnce({
|
|
130
|
+
baseUrl: 'https://api.openai.com/v1/',
|
|
131
|
+
});
|
|
132
|
+
mockInquirer.prompt.mockResolvedValueOnce({
|
|
133
|
+
apiKey: 'sk-proj-abc123',
|
|
134
|
+
});
|
|
135
|
+
const options = {
|
|
136
|
+
model: 'gpt-4o',
|
|
137
|
+
};
|
|
138
|
+
const config = await collector.collectConfig(options);
|
|
139
|
+
expect(config).toEqual({
|
|
140
|
+
type: 'openai',
|
|
141
|
+
modelValue: 'gpt-4o',
|
|
142
|
+
secretName: '',
|
|
143
|
+
baseUrl: 'https://api.openai.com/v1',
|
|
144
|
+
apiKey: 'sk-proj-abc123',
|
|
145
|
+
});
|
|
146
|
+
});
|
|
147
|
+
it('mixes CLI options and interactive prompts', async () => {
|
|
148
|
+
mockInquirer.prompt.mockResolvedValueOnce({
|
|
149
|
+
apiKey: 'sk-prompted-key',
|
|
150
|
+
});
|
|
151
|
+
const options = {
|
|
152
|
+
model: 'gpt-3.5-turbo',
|
|
153
|
+
baseUrl: 'https://custom-api.com/v1',
|
|
154
|
+
};
|
|
155
|
+
const config = await collector.collectConfig(options);
|
|
156
|
+
expect(config).toEqual({
|
|
157
|
+
type: 'openai',
|
|
158
|
+
modelValue: 'gpt-3.5-turbo',
|
|
159
|
+
secretName: '',
|
|
160
|
+
baseUrl: 'https://custom-api.com/v1',
|
|
161
|
+
apiKey: 'sk-prompted-key',
|
|
162
|
+
});
|
|
163
|
+
});
|
|
164
|
+
it('handles custom OpenAI-compatible endpoints', async () => {
|
|
165
|
+
const options = {
|
|
166
|
+
model: 'llama-3-8b',
|
|
167
|
+
baseUrl: 'https://localhost:8080/v1',
|
|
168
|
+
apiKey: 'local-key-123',
|
|
169
|
+
};
|
|
170
|
+
const config = await collector.collectConfig(options);
|
|
171
|
+
expect(config).toEqual({
|
|
172
|
+
type: 'openai',
|
|
173
|
+
modelValue: 'llama-3-8b',
|
|
174
|
+
secretName: '',
|
|
175
|
+
baseUrl: 'https://localhost:8080/v1',
|
|
176
|
+
apiKey: 'local-key-123',
|
|
177
|
+
});
|
|
178
|
+
});
|
|
179
|
+
});
|
|
180
|
+
});
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import type { ProviderConfig } from './index.js';
|
|
2
|
+
/**
|
|
3
|
+
* Base configuration shared by all model providers.
|
|
4
|
+
*/
|
|
5
|
+
export interface BaseProviderConfig {
|
|
6
|
+
type: string;
|
|
7
|
+
modelValue: string;
|
|
8
|
+
secretName: string;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Common options available to all providers collectors.
|
|
12
|
+
*
|
|
13
|
+
* Can be extended with provider-specific options to grant more flexibility when configuring models on different providers.
|
|
14
|
+
*
|
|
15
|
+
* @field model - Model name (e.g., 'gpt-4o-mini')
|
|
16
|
+
* @field type - Model provider type (e.g., 'azure', 'openai', 'bedrock')
|
|
17
|
+
*/
|
|
18
|
+
export interface BaseCollectorOptions {
|
|
19
|
+
/**
|
|
20
|
+
* Model name (e.g., 'gpt-4o-mini')
|
|
21
|
+
*/
|
|
22
|
+
model?: string;
|
|
23
|
+
/**
|
|
24
|
+
* Model provider type (e.g., 'azure', 'openai', 'bedrock')
|
|
25
|
+
*/
|
|
26
|
+
type?: string;
|
|
27
|
+
[key: string]: unknown;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Provider configuration collector interface.
|
|
31
|
+
*
|
|
32
|
+
* A collector is responsible for gathering all the necessary configuration
|
|
33
|
+
* parameters for a specific model provider (OpenAI, Azure, Bedrock, etc.).
|
|
34
|
+
* It handles the interactive prompting of missing values and validation
|
|
35
|
+
* of user inputs, ensuring all required fields are collected before
|
|
36
|
+
* creating the model resource.
|
|
37
|
+
*
|
|
38
|
+
* The collector pattern allows each provider to define its own specific
|
|
39
|
+
* configuration requirements and prompts without affecting other providers.
|
|
40
|
+
*/
|
|
41
|
+
export interface ProviderConfigCollector {
|
|
42
|
+
/**
|
|
43
|
+
* Collects provider-specific configuration by prompting for any missing values.
|
|
44
|
+
*
|
|
45
|
+
* @param options - Options object that may contain pre-filled values.
|
|
46
|
+
* Each collector extracts only the fields it needs.
|
|
47
|
+
* @returns A promise that resolves to a complete provider configuration with all required fields
|
|
48
|
+
* @throws Error if a required field cannot be obtained or validation fails
|
|
49
|
+
*/
|
|
50
|
+
collectConfig(options: BaseCollectorOptions): Promise<ProviderConfig>;
|
|
51
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -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
|
+
});
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import { Command } from 'commander';
|
|
2
|
+
import { marked } from 'marked';
|
|
3
|
+
// @ts-ignore - no types available
|
|
4
|
+
import TerminalRenderer from 'marked-terminal';
|
|
5
|
+
import output from '../../lib/output.js';
|
|
6
|
+
import { ExitCodes } from '../../lib/errors.js';
|
|
7
|
+
import { getResource, replaceResource } from '../../lib/kubectl.js';
|
|
8
|
+
import { listQueries } from './list.js';
|
|
9
|
+
import { deleteQuery } from './delete.js';
|
|
10
|
+
function renderMarkdown(content) {
|
|
11
|
+
if (process.stdout.isTTY) {
|
|
12
|
+
marked.setOptions({
|
|
13
|
+
// @ts-ignore - TerminalRenderer types are incomplete
|
|
14
|
+
renderer: new TerminalRenderer({
|
|
15
|
+
showSectionPrefix: false,
|
|
16
|
+
reflowText: true,
|
|
17
|
+
// @ts-ignore - preserveNewlines exists but not in types
|
|
18
|
+
preserveNewlines: true,
|
|
19
|
+
}),
|
|
20
|
+
});
|
|
21
|
+
return marked(content);
|
|
22
|
+
}
|
|
23
|
+
return content;
|
|
24
|
+
}
|
|
25
|
+
async function getQuery(name, options) {
|
|
26
|
+
try {
|
|
27
|
+
const query = await getResource('queries', name);
|
|
28
|
+
if (options.response) {
|
|
29
|
+
if (query.status?.responses && query.status.responses.length > 0) {
|
|
30
|
+
const response = query.status.responses[0];
|
|
31
|
+
if (options.output === 'markdown') {
|
|
32
|
+
console.log(renderMarkdown(response.content || ''));
|
|
33
|
+
}
|
|
34
|
+
else {
|
|
35
|
+
console.log(JSON.stringify(response, null, 2));
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
else {
|
|
39
|
+
output.warning('No response available');
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
else if (options.output === 'markdown') {
|
|
43
|
+
if (query.status?.responses && query.status.responses.length > 0) {
|
|
44
|
+
console.log(renderMarkdown(query.status.responses[0].content || ''));
|
|
45
|
+
}
|
|
46
|
+
else {
|
|
47
|
+
output.warning('No response available');
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
else {
|
|
51
|
+
console.log(JSON.stringify(query, null, 2));
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
catch (error) {
|
|
55
|
+
output.error('fetching query:', error instanceof Error ? error.message : error);
|
|
56
|
+
process.exit(ExitCodes.CliError);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
export function createQueriesCommand(_) {
|
|
60
|
+
const queriesCommand = new Command('queries');
|
|
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
|
+
});
|
|
68
|
+
const getCommand = new Command('get');
|
|
69
|
+
getCommand
|
|
70
|
+
.description('Get a specific query (@latest for most recent)')
|
|
71
|
+
.argument('<name>', 'Query name or @latest')
|
|
72
|
+
.option('-o, --output <format>', 'output format (json, markdown)', 'json')
|
|
73
|
+
.option('-r, --response', 'show only the response content', false)
|
|
74
|
+
.action(async (name, options) => {
|
|
75
|
+
await getQuery(name, options);
|
|
76
|
+
});
|
|
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);
|
|
107
|
+
return queriesCommand;
|
|
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 {};
|