@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
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
import { jest } from '@jest/globals';
|
|
2
|
+
import { UNSUPPORTED_OUTPUT_FORMAT_MESSAGE } from './validation.js';
|
|
3
|
+
import output from '../../lib/output.js';
|
|
4
|
+
const mockExeca = jest.fn();
|
|
5
|
+
jest.unstable_mockModule('execa', () => ({
|
|
6
|
+
execa: mockExeca,
|
|
7
|
+
}));
|
|
8
|
+
const { createQueriesCommand } = await import('./index.js');
|
|
9
|
+
describe('queries list 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 list all queries in text format by default', async () => {
|
|
18
|
+
const mockQueries = [
|
|
19
|
+
{
|
|
20
|
+
metadata: {
|
|
21
|
+
name: 'query-1',
|
|
22
|
+
creationTimestamp: '2024-01-01T00:00:00Z',
|
|
23
|
+
},
|
|
24
|
+
status: {
|
|
25
|
+
phase: 'done',
|
|
26
|
+
},
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
metadata: {
|
|
30
|
+
name: 'query-2',
|
|
31
|
+
creationTimestamp: '2024-01-02T00:00:00Z',
|
|
32
|
+
},
|
|
33
|
+
status: {
|
|
34
|
+
phase: 'running',
|
|
35
|
+
},
|
|
36
|
+
},
|
|
37
|
+
];
|
|
38
|
+
mockExeca.mockResolvedValue({
|
|
39
|
+
stdout: JSON.stringify({ items: mockQueries }),
|
|
40
|
+
});
|
|
41
|
+
const command = createQueriesCommand({});
|
|
42
|
+
await command.parseAsync(['node', 'test', 'list']);
|
|
43
|
+
expect(console.log).toHaveBeenCalledWith(expect.stringMatching(/NAME.*STATUS/));
|
|
44
|
+
expect(console.log).toHaveBeenCalledWith(expect.stringMatching(/query-1/));
|
|
45
|
+
expect(console.log).toHaveBeenCalledWith(expect.stringMatching(/query-2/));
|
|
46
|
+
expect(mockExeca).toHaveBeenCalledWith('kubectl', ['get', 'queries', '-o', 'json'], { stdio: 'pipe' });
|
|
47
|
+
});
|
|
48
|
+
it('should list all queries in JSON format', async () => {
|
|
49
|
+
const mockQueries = [
|
|
50
|
+
{
|
|
51
|
+
metadata: {
|
|
52
|
+
name: 'query-1',
|
|
53
|
+
creationTimestamp: '2024-01-01T00:00:00Z',
|
|
54
|
+
},
|
|
55
|
+
},
|
|
56
|
+
{
|
|
57
|
+
metadata: {
|
|
58
|
+
name: 'query-2',
|
|
59
|
+
creationTimestamp: '2024-01-02T00:00:00Z',
|
|
60
|
+
},
|
|
61
|
+
},
|
|
62
|
+
];
|
|
63
|
+
mockExeca.mockResolvedValue({
|
|
64
|
+
stdout: JSON.stringify({ items: mockQueries }),
|
|
65
|
+
});
|
|
66
|
+
const command = createQueriesCommand({});
|
|
67
|
+
await command.parseAsync(['node', 'test', '--output', 'json']);
|
|
68
|
+
expect(console.log).toHaveBeenCalledWith(JSON.stringify(mockQueries, null, 2));
|
|
69
|
+
expect(mockExeca).toHaveBeenCalledWith('kubectl', ['get', 'queries', '-o', 'json'], { stdio: 'pipe' });
|
|
70
|
+
});
|
|
71
|
+
it('should support sorting by creation timestamp', async () => {
|
|
72
|
+
const mockQueries = [
|
|
73
|
+
{
|
|
74
|
+
metadata: {
|
|
75
|
+
name: 'query-1',
|
|
76
|
+
creationTimestamp: '2024-01-01T00:00:00Z',
|
|
77
|
+
},
|
|
78
|
+
status: {
|
|
79
|
+
phase: 'done',
|
|
80
|
+
},
|
|
81
|
+
},
|
|
82
|
+
{
|
|
83
|
+
metadata: {
|
|
84
|
+
name: 'query-2',
|
|
85
|
+
creationTimestamp: '2024-01-02T00:00:00Z',
|
|
86
|
+
},
|
|
87
|
+
status: {
|
|
88
|
+
phase: 'running',
|
|
89
|
+
},
|
|
90
|
+
},
|
|
91
|
+
];
|
|
92
|
+
mockExeca.mockResolvedValue({
|
|
93
|
+
stdout: JSON.stringify({ items: mockQueries }),
|
|
94
|
+
});
|
|
95
|
+
const command = createQueriesCommand({});
|
|
96
|
+
await command.parseAsync([
|
|
97
|
+
'node',
|
|
98
|
+
'test',
|
|
99
|
+
'--sort-by',
|
|
100
|
+
'.metadata.creationTimestamp',
|
|
101
|
+
]);
|
|
102
|
+
expect(console.log).toHaveBeenCalledWith(expect.stringMatching(/NAME.*STATUS/));
|
|
103
|
+
expect(console.log).toHaveBeenCalledWith(expect.stringMatching(/query-1/));
|
|
104
|
+
expect(console.log).toHaveBeenCalledWith(expect.stringMatching(/query-2/));
|
|
105
|
+
expect(mockExeca).toHaveBeenCalledWith('kubectl', ['get', 'queries', '--sort-by=.metadata.creationTimestamp', '-o', 'json'], { stdio: 'pipe' });
|
|
106
|
+
});
|
|
107
|
+
it('should display warning when no queries exist', async () => {
|
|
108
|
+
mockExeca.mockResolvedValue({
|
|
109
|
+
stdout: JSON.stringify({ items: [] }),
|
|
110
|
+
});
|
|
111
|
+
const command = createQueriesCommand({});
|
|
112
|
+
await command.parseAsync(['node', 'test', 'list']);
|
|
113
|
+
expect(output.warning).toHaveBeenCalledWith('no queries available');
|
|
114
|
+
expect(mockExeca).toHaveBeenCalledWith('kubectl', ['get', 'queries', '-o', 'json'], { stdio: 'pipe' });
|
|
115
|
+
});
|
|
116
|
+
it('should handle errors when listing queries', async () => {
|
|
117
|
+
mockExeca.mockRejectedValue(new Error('kubectl connection failed'));
|
|
118
|
+
const command = createQueriesCommand({});
|
|
119
|
+
await command.parseAsync(['node', 'test', 'list']);
|
|
120
|
+
expect(output.error).toHaveBeenCalled();
|
|
121
|
+
expect(process.exit).toHaveBeenCalled();
|
|
122
|
+
});
|
|
123
|
+
it('should handle invalid output format gracefully', async () => {
|
|
124
|
+
const mockQueries = [
|
|
125
|
+
{
|
|
126
|
+
metadata: {
|
|
127
|
+
name: 'query-1',
|
|
128
|
+
creationTimestamp: '2024-01-01T00:00:00Z',
|
|
129
|
+
},
|
|
130
|
+
status: {
|
|
131
|
+
phase: 'done',
|
|
132
|
+
},
|
|
133
|
+
},
|
|
134
|
+
];
|
|
135
|
+
mockExeca.mockResolvedValue({
|
|
136
|
+
stdout: JSON.stringify({ items: mockQueries }),
|
|
137
|
+
});
|
|
138
|
+
const command = createQueriesCommand({});
|
|
139
|
+
await command.parseAsync(['node', 'test', '--output', 'xml']);
|
|
140
|
+
expect(output.error).toHaveBeenCalledWith(expect.anything(), expect.stringMatching(UNSUPPORTED_OUTPUT_FORMAT_MESSAGE));
|
|
141
|
+
expect(mockExeca).not.toHaveBeenCalled();
|
|
142
|
+
expect(console.log).not.toHaveBeenCalledWith(expect.stringMatching(/query-1/));
|
|
143
|
+
expect(process.exit).toHaveBeenCalled();
|
|
144
|
+
});
|
|
145
|
+
it('should list many queries without truncation', async () => {
|
|
146
|
+
// Create 100 mock queries
|
|
147
|
+
const mockQueries = Array.from({ length: 100 }, (_, i) => ({
|
|
148
|
+
metadata: {
|
|
149
|
+
name: `query-${i + 1}`,
|
|
150
|
+
creationTimestamp: new Date(2024, 0, i + 1).toISOString(),
|
|
151
|
+
},
|
|
152
|
+
status: {
|
|
153
|
+
phase: i % 3 === 0 ? 'done' : i % 2 === 0 ? 'running' : 'initializing',
|
|
154
|
+
},
|
|
155
|
+
}));
|
|
156
|
+
mockExeca.mockResolvedValue({
|
|
157
|
+
stdout: JSON.stringify({ items: mockQueries }),
|
|
158
|
+
});
|
|
159
|
+
const command = createQueriesCommand({});
|
|
160
|
+
await command.parseAsync(['node', 'test', 'list']);
|
|
161
|
+
// Check for header and separator
|
|
162
|
+
expect(console.log).toHaveBeenCalledWith(expect.stringMatching(/NAME.*STATUS/));
|
|
163
|
+
// Verify all queries are logged
|
|
164
|
+
for (let i = 1; i <= 100; i++) {
|
|
165
|
+
expect(console.log).toHaveBeenCalledWith(expect.stringMatching(new RegExp(`query-${i}`)));
|
|
166
|
+
}
|
|
167
|
+
// Verify console.log was called: header + 100 queries
|
|
168
|
+
expect(console.log).toHaveBeenCalledTimes(101);
|
|
169
|
+
});
|
|
170
|
+
});
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { InvalidArgumentError } from 'commander';
|
|
2
|
+
const SUPPORTED_OUTPUT_FORMATS = ['json', 'text'];
|
|
3
|
+
export const UNSUPPORTED_OUTPUT_FORMAT_MESSAGE = `unsupported "output" format`;
|
|
4
|
+
const VALID_OUTPUT_FORMATS_MESSAGE = `valid formats are: ${SUPPORTED_OUTPUT_FORMATS.join(', ')}`;
|
|
5
|
+
export function assertSupportedOutputFormat(format) {
|
|
6
|
+
if (format && !SUPPORTED_OUTPUT_FORMATS.includes(format)) {
|
|
7
|
+
const message = `${UNSUPPORTED_OUTPUT_FORMAT_MESSAGE}: "${format}". ${VALID_OUTPUT_FORMATS_MESSAGE}`;
|
|
8
|
+
throw new InvalidArgumentError(message);
|
|
9
|
+
}
|
|
10
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { jest } from '@jest/globals';
|
|
2
|
+
import { InvalidArgumentError } from 'commander';
|
|
3
|
+
import { assertSupportedOutputFormat, UNSUPPORTED_OUTPUT_FORMAT_MESSAGE, } from './validation.js';
|
|
4
|
+
jest.spyOn(console, 'error').mockImplementation(() => { });
|
|
5
|
+
describe('queries validation', () => {
|
|
6
|
+
describe('assertSupportedOutputFormat', () => {
|
|
7
|
+
it('should not throw for supported formats', () => {
|
|
8
|
+
expect(() => assertSupportedOutputFormat('json')).not.toThrow();
|
|
9
|
+
expect(() => assertSupportedOutputFormat('text')).not.toThrow();
|
|
10
|
+
});
|
|
11
|
+
it('should not throw when format is undefined', () => {
|
|
12
|
+
expect(() => assertSupportedOutputFormat(undefined)).not.toThrow();
|
|
13
|
+
});
|
|
14
|
+
it('should throw InvalidArgumentError for unsupported format', () => {
|
|
15
|
+
expect(() => assertSupportedOutputFormat('xml')).toThrow(InvalidArgumentError);
|
|
16
|
+
});
|
|
17
|
+
it('should include format and supported formats in error message', () => {
|
|
18
|
+
expect(() => assertSupportedOutputFormat('xml')).toThrow(UNSUPPORTED_OUTPUT_FORMAT_MESSAGE);
|
|
19
|
+
});
|
|
20
|
+
it('should work with various invalid formats', () => {
|
|
21
|
+
const invalidFormats = ['yaml', 'csv', 'html', 'pdf'];
|
|
22
|
+
for (const format of invalidFormats) {
|
|
23
|
+
expect(() => assertSupportedOutputFormat(format)).toThrow(InvalidArgumentError);
|
|
24
|
+
}
|
|
25
|
+
});
|
|
26
|
+
});
|
|
27
|
+
});
|
|
@@ -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 } from '../../arkServices.js';
|
|
8
|
+
import { isMarketplaceService, extractMarketplaceServiceName, getMarketplaceService, getAllMarketplaceServices, } from '../../marketplaceServices.js';
|
|
8
9
|
async function uninstallService(service, verbose = false) {
|
|
9
10
|
const helmArgs = ['uninstall', service.helmReleaseName, '--ignore-not-found'];
|
|
10
11
|
// Only add namespace flag if service has explicit namespace
|
|
@@ -25,6 +26,32 @@ async function uninstallArk(config, serviceName, options = {}) {
|
|
|
25
26
|
console.log(); // Add blank line after cluster info
|
|
26
27
|
// If a specific service is requested, uninstall only that service
|
|
27
28
|
if (serviceName) {
|
|
29
|
+
// Check if it's a marketplace service
|
|
30
|
+
if (isMarketplaceService(serviceName)) {
|
|
31
|
+
const marketplaceServiceName = extractMarketplaceServiceName(serviceName);
|
|
32
|
+
const service = getMarketplaceService(marketplaceServiceName);
|
|
33
|
+
if (!service) {
|
|
34
|
+
output.error(`marketplace service '${marketplaceServiceName}' not found`);
|
|
35
|
+
output.info('available marketplace services:');
|
|
36
|
+
const marketplaceServices = getAllMarketplaceServices();
|
|
37
|
+
for (const serviceName of Object.keys(marketplaceServices)) {
|
|
38
|
+
output.info(` marketplace/services/${serviceName}`);
|
|
39
|
+
}
|
|
40
|
+
process.exit(1);
|
|
41
|
+
}
|
|
42
|
+
output.info(`uninstalling marketplace service ${service.name}...`);
|
|
43
|
+
try {
|
|
44
|
+
await uninstallService(service, options.verbose);
|
|
45
|
+
output.success(`${service.name} uninstalled successfully`);
|
|
46
|
+
}
|
|
47
|
+
catch (error) {
|
|
48
|
+
output.error(`failed to uninstall ${service.name}`);
|
|
49
|
+
console.error(error);
|
|
50
|
+
process.exit(1);
|
|
51
|
+
}
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
// Core ARK service
|
|
28
55
|
const services = getInstallableServices();
|
|
29
56
|
const service = Object.values(services).find((s) => s.name === serviceName);
|
|
30
57
|
if (!service) {
|
|
@@ -18,12 +18,12 @@ const generateMessageId = () => {
|
|
|
18
18
|
// Configure marked with terminal renderer for markdown output
|
|
19
19
|
const configureMarkdown = () => {
|
|
20
20
|
marked.setOptions({
|
|
21
|
-
// @ts-
|
|
21
|
+
// @ts-ignore - TerminalRenderer types are incomplete
|
|
22
22
|
renderer: new TerminalRenderer({
|
|
23
23
|
showSectionPrefix: false,
|
|
24
24
|
width: 80,
|
|
25
25
|
reflowText: true,
|
|
26
|
-
// @ts-
|
|
26
|
+
// @ts-ignore - preserveNewlines exists but not in types
|
|
27
27
|
preserveNewlines: true,
|
|
28
28
|
}),
|
|
29
29
|
});
|
package/dist/index.js
CHANGED
|
@@ -16,6 +16,7 @@ import { createDocsCommand } from './commands/docs/index.js';
|
|
|
16
16
|
import { createEvaluationCommand } from './commands/evaluation/index.js';
|
|
17
17
|
import { createGenerateCommand } from './commands/generate/index.js';
|
|
18
18
|
import { createInstallCommand } from './commands/install/index.js';
|
|
19
|
+
import { createMarketplaceCommand } from './commands/marketplace/index.js';
|
|
19
20
|
import { createMemoryCommand } from './commands/memory/index.js';
|
|
20
21
|
import { createModelsCommand } from './commands/models/index.js';
|
|
21
22
|
import { createQueryCommand } from './commands/query/index.js';
|
|
@@ -49,6 +50,7 @@ async function main() {
|
|
|
49
50
|
program.addCommand(createEvaluationCommand(config));
|
|
50
51
|
program.addCommand(createGenerateCommand(config));
|
|
51
52
|
program.addCommand(createInstallCommand(config));
|
|
53
|
+
program.addCommand(createMarketplaceCommand(config));
|
|
52
54
|
program.addCommand(createMemoryCommand(config));
|
|
53
55
|
program.addCommand(createModelsCommand(config));
|
|
54
56
|
program.addCommand(createQueryCommand(config));
|
package/dist/lib/arkApiClient.js
CHANGED
|
@@ -146,6 +146,8 @@ export class ArkApiClient {
|
|
|
146
146
|
}));
|
|
147
147
|
}
|
|
148
148
|
async *createChatCompletionStream(params) {
|
|
149
|
+
// Errors from OpenAI SDK will automatically propagate with proper error messages
|
|
150
|
+
// and kill the CLI, so no try/catch needed here
|
|
149
151
|
const stream = await this.openai.chat.completions.create({
|
|
150
152
|
...params,
|
|
151
153
|
stream: true,
|
|
@@ -11,10 +11,6 @@ export interface QueryOptions {
|
|
|
11
11
|
verbose?: boolean;
|
|
12
12
|
outputFormat?: string;
|
|
13
13
|
}
|
|
14
|
-
/**
|
|
15
|
-
* Execute a query against any ARK target (model, agent, team)
|
|
16
|
-
* This is the shared implementation used by all query commands
|
|
17
|
-
*/
|
|
18
14
|
export declare function executeQuery(options: QueryOptions): Promise<void>;
|
|
19
15
|
/**
|
|
20
16
|
* Parse a target string like "model/default" or "agent/weather"
|
package/dist/lib/executeQuery.js
CHANGED
|
@@ -4,24 +4,91 @@
|
|
|
4
4
|
import { execa } from 'execa';
|
|
5
5
|
import ora from 'ora';
|
|
6
6
|
import chalk from 'chalk';
|
|
7
|
-
import output from './output.js';
|
|
8
7
|
import { ExitCodes } from './errors.js';
|
|
9
|
-
import {
|
|
10
|
-
import {
|
|
11
|
-
/**
|
|
12
|
-
* Execute a query against any ARK target (model, agent, team)
|
|
13
|
-
* This is the shared implementation used by all query commands
|
|
14
|
-
*/
|
|
8
|
+
import { ArkApiProxy } from './arkApiProxy.js';
|
|
9
|
+
import { ChatClient } from './chatClient.js';
|
|
15
10
|
export async function executeQuery(options) {
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
11
|
+
if (options.outputFormat) {
|
|
12
|
+
return executeQueryWithFormat(options);
|
|
13
|
+
}
|
|
14
|
+
let arkApiProxy;
|
|
15
|
+
const spinner = ora('Connecting to Ark API...').start();
|
|
16
|
+
try {
|
|
17
|
+
arkApiProxy = new ArkApiProxy();
|
|
18
|
+
const arkApiClient = await arkApiProxy.start();
|
|
19
|
+
const chatClient = new ChatClient(arkApiClient);
|
|
20
|
+
spinner.text = 'Executing query...';
|
|
21
|
+
const targetId = `${options.targetType}/${options.targetName}`;
|
|
22
|
+
const messages = [{ role: 'user', content: options.message }];
|
|
23
|
+
const state = {
|
|
24
|
+
toolCalls: new Map(),
|
|
25
|
+
content: '',
|
|
26
|
+
};
|
|
27
|
+
let lastAgentName;
|
|
28
|
+
let headerShown = false;
|
|
29
|
+
let firstOutput = true;
|
|
30
|
+
await chatClient.sendMessage(targetId, messages, { streamingEnabled: true }, (chunk, toolCalls, arkMetadata) => {
|
|
31
|
+
if (firstOutput) {
|
|
32
|
+
spinner.stop();
|
|
33
|
+
firstOutput = false;
|
|
34
|
+
}
|
|
35
|
+
const agentName = arkMetadata?.agent || arkMetadata?.team;
|
|
36
|
+
if (agentName && agentName !== lastAgentName) {
|
|
37
|
+
if (lastAgentName) {
|
|
38
|
+
if (state.content) {
|
|
39
|
+
process.stdout.write('\n');
|
|
40
|
+
}
|
|
41
|
+
process.stdout.write('\n');
|
|
42
|
+
}
|
|
43
|
+
const prefix = arkMetadata?.team ? '◆' : '●';
|
|
44
|
+
const color = arkMetadata?.team ? 'green' : 'cyan';
|
|
45
|
+
process.stdout.write(chalk[color](`${prefix} ${agentName}\n`));
|
|
46
|
+
lastAgentName = agentName;
|
|
47
|
+
state.content = '';
|
|
48
|
+
state.toolCalls.clear();
|
|
49
|
+
headerShown = true;
|
|
50
|
+
}
|
|
51
|
+
if (toolCalls && toolCalls.length > 0) {
|
|
52
|
+
for (const toolCall of toolCalls) {
|
|
53
|
+
if (!state.toolCalls.has(toolCall.id)) {
|
|
54
|
+
state.toolCalls.set(toolCall.id, toolCall);
|
|
55
|
+
if (state.content) {
|
|
56
|
+
process.stdout.write('\n');
|
|
57
|
+
}
|
|
58
|
+
process.stdout.write(chalk.magenta(`🔧 ${toolCall.function.name}\n`));
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
if (chunk) {
|
|
63
|
+
if (state.toolCalls.size > 0 && !state.content) {
|
|
64
|
+
process.stdout.write('\n');
|
|
65
|
+
}
|
|
66
|
+
process.stdout.write(chunk);
|
|
67
|
+
state.content += chunk;
|
|
68
|
+
}
|
|
69
|
+
});
|
|
70
|
+
if (spinner.isSpinning) {
|
|
71
|
+
spinner.stop();
|
|
72
|
+
}
|
|
73
|
+
if ((state.content || state.toolCalls.size > 0) && headerShown) {
|
|
74
|
+
process.stdout.write('\n');
|
|
75
|
+
}
|
|
76
|
+
if (arkApiProxy) {
|
|
77
|
+
arkApiProxy.stop();
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
catch (error) {
|
|
81
|
+
if (spinner.isSpinning) {
|
|
82
|
+
spinner.stop();
|
|
83
|
+
}
|
|
84
|
+
if (arkApiProxy) {
|
|
85
|
+
arkApiProxy.stop();
|
|
86
|
+
}
|
|
87
|
+
console.error(chalk.red(error instanceof Error ? error.message : 'Unknown error'));
|
|
88
|
+
process.exit(ExitCodes.CliError);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
async function executeQueryWithFormat(options) {
|
|
25
92
|
const timestamp = Date.now();
|
|
26
93
|
const queryName = `cli-query-${timestamp}`;
|
|
27
94
|
const queryManifest = {
|
|
@@ -42,104 +109,31 @@ export async function executeQuery(options) {
|
|
|
42
109
|
},
|
|
43
110
|
};
|
|
44
111
|
try {
|
|
45
|
-
// Apply the query
|
|
46
|
-
if (spinner)
|
|
47
|
-
spinner.text = 'Submitting query...';
|
|
48
112
|
await execa('kubectl', ['apply', '-f', '-'], {
|
|
49
113
|
input: JSON.stringify(queryManifest),
|
|
50
114
|
stdio: ['pipe', 'pipe', 'pipe'],
|
|
51
115
|
});
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
], { timeout: watchTimeoutMs });
|
|
62
|
-
}
|
|
63
|
-
catch (error) {
|
|
64
|
-
if (spinner)
|
|
65
|
-
spinner.stop();
|
|
66
|
-
// Check if it's a timeout or other error
|
|
67
|
-
if (error instanceof Error &&
|
|
68
|
-
error.message.includes('timed out waiting')) {
|
|
69
|
-
console.error(chalk.red(`Query did not complete within ${options.watchTimeout ?? `${Math.floor(watchTimeoutMs / 1000)}s`}`));
|
|
70
|
-
process.exit(ExitCodes.Timeout);
|
|
71
|
-
}
|
|
72
|
-
// For other errors, fetch the query to check status
|
|
73
|
-
}
|
|
74
|
-
if (spinner)
|
|
75
|
-
spinner.stop();
|
|
76
|
-
// If output format is specified, output the resource and return
|
|
77
|
-
if (options.outputFormat) {
|
|
78
|
-
try {
|
|
79
|
-
if (options.outputFormat === 'name') {
|
|
80
|
-
console.log(queryName);
|
|
81
|
-
}
|
|
82
|
-
else if (options.outputFormat === 'json' ||
|
|
83
|
-
options.outputFormat === 'yaml') {
|
|
84
|
-
const { stdout } = await execa('kubectl', ['get', 'query', queryName, '-o', options.outputFormat], { stdio: 'pipe' });
|
|
85
|
-
console.log(stdout);
|
|
86
|
-
}
|
|
87
|
-
else {
|
|
88
|
-
console.error(chalk.red(`Invalid output format: ${options.outputFormat}. Use: yaml, json, or name`));
|
|
89
|
-
process.exit(ExitCodes.CliError);
|
|
90
|
-
}
|
|
91
|
-
return;
|
|
92
|
-
}
|
|
93
|
-
catch (error) {
|
|
94
|
-
console.error(chalk.red(error instanceof Error
|
|
95
|
-
? error.message
|
|
96
|
-
: 'Failed to fetch query resource'));
|
|
97
|
-
process.exit(ExitCodes.CliError);
|
|
98
|
-
}
|
|
116
|
+
const timeoutSeconds = 300;
|
|
117
|
+
await execa('kubectl', [
|
|
118
|
+
'wait',
|
|
119
|
+
'--for=condition=Completed',
|
|
120
|
+
`query/${queryName}`,
|
|
121
|
+
`--timeout=${timeoutSeconds}s`,
|
|
122
|
+
], { timeout: timeoutSeconds * 1000 });
|
|
123
|
+
if (options.outputFormat === 'name') {
|
|
124
|
+
console.log(queryName);
|
|
99
125
|
}
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
const
|
|
103
|
-
|
|
104
|
-
// Check if query completed successfully or with error
|
|
105
|
-
if (phase === 'done') {
|
|
106
|
-
// Extract and display the response from responses array
|
|
107
|
-
if (query.status?.responses && query.status.responses.length > 0) {
|
|
108
|
-
const response = query.status.responses[0];
|
|
109
|
-
console.log(response.content || response);
|
|
110
|
-
}
|
|
111
|
-
else {
|
|
112
|
-
output.warning('No response received');
|
|
113
|
-
}
|
|
114
|
-
}
|
|
115
|
-
else if (phase === 'error') {
|
|
116
|
-
const response = query.status?.responses?.[0];
|
|
117
|
-
console.error(chalk.red(response?.content || 'Query failed with unknown error'));
|
|
118
|
-
process.exit(ExitCodes.OperationError);
|
|
119
|
-
}
|
|
120
|
-
else if (phase === 'canceled') {
|
|
121
|
-
if (spinner) {
|
|
122
|
-
spinner.warn('Query canceled');
|
|
123
|
-
}
|
|
124
|
-
else {
|
|
125
|
-
output.warning('Query canceled');
|
|
126
|
-
}
|
|
127
|
-
if (query.status?.message) {
|
|
128
|
-
output.warning(query.status.message);
|
|
129
|
-
}
|
|
130
|
-
process.exit(ExitCodes.OperationError);
|
|
131
|
-
}
|
|
126
|
+
else if (options.outputFormat === 'json' ||
|
|
127
|
+
options.outputFormat === 'yaml') {
|
|
128
|
+
const { stdout } = await execa('kubectl', ['get', 'query', queryName, '-o', options.outputFormat], { stdio: 'pipe' });
|
|
129
|
+
console.log(stdout);
|
|
132
130
|
}
|
|
133
|
-
|
|
134
|
-
console.error(chalk.red(
|
|
135
|
-
? error.message
|
|
136
|
-
: 'Failed to fetch query result'));
|
|
131
|
+
else {
|
|
132
|
+
console.error(chalk.red(`Invalid output format: ${options.outputFormat}. Use: yaml, json, or name`));
|
|
137
133
|
process.exit(ExitCodes.CliError);
|
|
138
134
|
}
|
|
139
135
|
}
|
|
140
136
|
catch (error) {
|
|
141
|
-
if (spinner)
|
|
142
|
-
spinner.stop();
|
|
143
137
|
console.error(chalk.red(error instanceof Error ? error.message : 'Unknown error'));
|
|
144
138
|
process.exit(ExitCodes.CliError);
|
|
145
139
|
}
|