@agents-at-scale/ark 0.1.46 → 0.1.49
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/.arkrc.template.yaml +51 -0
- package/README.md +4 -0
- package/dist/arkServices.js +22 -8
- package/dist/arkServices.spec.js +6 -0
- package/dist/commands/agents/index.d.ts +1 -1
- package/dist/commands/agents/index.js +4 -2
- package/dist/commands/chat/index.js +1 -1
- package/dist/commands/completion/index.js +19 -5
- package/dist/commands/dashboard/index.d.ts +2 -2
- package/dist/commands/dashboard/index.js +5 -4
- package/dist/commands/export/index.d.ts +3 -0
- package/dist/commands/export/index.js +73 -0
- package/dist/commands/export/index.spec.d.ts +1 -0
- package/dist/commands/export/index.spec.js +145 -0
- package/dist/commands/import/index.d.ts +3 -0
- package/dist/commands/import/index.js +27 -0
- package/dist/commands/import/index.spec.d.ts +1 -0
- package/dist/commands/import/index.spec.js +46 -0
- package/dist/commands/install/index.js +20 -10
- package/dist/commands/marketplace/index.js +51 -23
- package/dist/commands/marketplace/index.spec.d.ts +1 -0
- package/dist/commands/marketplace/index.spec.js +88 -0
- package/dist/commands/memory/index.js +9 -4
- package/dist/commands/models/index.d.ts +1 -1
- package/dist/commands/models/index.js +4 -2
- package/dist/commands/query/index.d.ts +1 -1
- package/dist/commands/query/index.js +6 -2
- package/dist/commands/status/index.js +7 -2
- package/dist/commands/teams/index.d.ts +1 -1
- package/dist/commands/teams/index.js +4 -2
- package/dist/commands/uninstall/index.js +20 -10
- package/dist/index.js +4 -0
- package/dist/lib/arkApiProxy.d.ts +1 -1
- package/dist/lib/arkApiProxy.js +2 -2
- package/dist/lib/arkServiceProxy.d.ts +3 -1
- package/dist/lib/arkServiceProxy.js +34 -1
- package/dist/lib/arkServiceProxy.spec.d.ts +1 -0
- package/dist/lib/arkServiceProxy.spec.js +100 -0
- package/dist/lib/chatClient.d.ts +2 -0
- package/dist/lib/chatClient.js +10 -2
- package/dist/lib/config.d.ts +17 -1
- package/dist/lib/config.js +62 -7
- package/dist/lib/config.spec.js +103 -0
- package/dist/lib/constants.d.ts +3 -0
- package/dist/lib/constants.js +5 -0
- package/dist/lib/executeQuery.d.ts +1 -0
- package/dist/lib/executeQuery.js +21 -4
- package/dist/lib/executeQuery.spec.js +4 -1
- package/dist/lib/kubectl.d.ts +3 -0
- package/dist/lib/kubectl.js +68 -0
- package/dist/lib/kubectl.spec.js +16 -0
- package/dist/lib/marketplaceFetcher.d.ts +6 -0
- package/dist/lib/marketplaceFetcher.js +80 -0
- package/dist/lib/marketplaceFetcher.spec.d.ts +1 -0
- package/dist/lib/marketplaceFetcher.spec.js +225 -0
- package/dist/lib/types.d.ts +1 -0
- package/dist/marketplaceServices.d.ts +15 -6
- package/dist/marketplaceServices.js +38 -40
- package/dist/marketplaceServices.spec.d.ts +1 -0
- package/dist/marketplaceServices.spec.js +74 -0
- package/dist/types/marketplace.d.ts +37 -0
- package/dist/types/marketplace.js +1 -0
- package/dist/ui/MainMenu.js +6 -2
- package/package.json +4 -2
- package/templates/marketplace/marketplace.json.example +59 -0
|
@@ -1,7 +1,11 @@
|
|
|
1
1
|
import { Command } from 'commander';
|
|
2
2
|
import chalk from 'chalk';
|
|
3
|
-
import {
|
|
3
|
+
import { getMarketplaceRepoUrl, getMarketplaceRegistry, } from '../../lib/config.js';
|
|
4
|
+
import { getAllMarketplaceServices, getAllMarketplaceAgents, } from '../../marketplaceServices.js';
|
|
5
|
+
import { fetchMarketplaceManifest } from '../../lib/marketplaceFetcher.js';
|
|
4
6
|
function createMarketplaceCommand(_config) {
|
|
7
|
+
const repoUrl = getMarketplaceRepoUrl();
|
|
8
|
+
const registry = getMarketplaceRegistry();
|
|
5
9
|
const marketplace = new Command('marketplace');
|
|
6
10
|
marketplace
|
|
7
11
|
.description('Manage marketplace services')
|
|
@@ -9,39 +13,63 @@ function createMarketplaceCommand(_config) {
|
|
|
9
13
|
${chalk.blue('🏪 ARK Marketplace')}
|
|
10
14
|
Install community-contributed services from the ARK Marketplace.
|
|
11
15
|
|
|
12
|
-
Repository: ${chalk.cyan(
|
|
13
|
-
Registry: ${chalk.cyan(
|
|
16
|
+
Repository: ${chalk.cyan(repoUrl)}
|
|
17
|
+
Registry: ${chalk.cyan(registry.replace('oci://', ''))}
|
|
14
18
|
`)
|
|
15
19
|
.addHelpText('after', `
|
|
16
20
|
${chalk.cyan('Examples:')}
|
|
17
|
-
${chalk.yellow('ark marketplace list')} # List available services
|
|
18
|
-
${chalk.yellow('ark install marketplace/services/phoenix')} # Install Phoenix
|
|
21
|
+
${chalk.yellow('ark marketplace list')} # List available services and agents
|
|
22
|
+
${chalk.yellow('ark install marketplace/services/phoenix')} # Install Phoenix service
|
|
23
|
+
${chalk.yellow('ark install marketplace/agents/noah')} # Install Noah agent
|
|
19
24
|
${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
|
`);
|
|
25
26
|
// List command
|
|
26
27
|
const list = new Command('list');
|
|
27
28
|
list
|
|
28
29
|
.alias('ls')
|
|
29
|
-
.description('List available marketplace services')
|
|
30
|
-
.action(() => {
|
|
31
|
-
const services = getAllMarketplaceServices();
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
console.log(
|
|
39
|
-
|
|
40
|
-
console.log(` ${chalk.dim(namespaceInfo)}`);
|
|
30
|
+
.description('List available marketplace services and agents')
|
|
31
|
+
.action(async () => {
|
|
32
|
+
const services = await getAllMarketplaceServices();
|
|
33
|
+
const agents = await getAllMarketplaceAgents();
|
|
34
|
+
const manifest = await fetchMarketplaceManifest();
|
|
35
|
+
console.log(chalk.blue('\n🏪 ARK Marketplace\n'));
|
|
36
|
+
if (!manifest) {
|
|
37
|
+
console.log(chalk.yellow('⚠️ Marketplace unavailable\n'));
|
|
38
|
+
console.log(chalk.gray('Could not fetch marketplace.json from repository.\n'));
|
|
39
|
+
console.log(chalk.cyan(`Repository: ${repoUrl}`));
|
|
40
|
+
console.log(chalk.cyan(`Registry: ${registry}`));
|
|
41
41
|
console.log();
|
|
42
|
+
return;
|
|
42
43
|
}
|
|
43
|
-
console.log(chalk.
|
|
44
|
-
|
|
44
|
+
console.log(chalk.dim(`Using marketplace.json (version: ${manifest.version})\n`));
|
|
45
|
+
if (services && Object.keys(services).length > 0) {
|
|
46
|
+
console.log(chalk.bold('Services:'));
|
|
47
|
+
console.log(chalk.gray('Install with: ark install marketplace/services/<name>\n'));
|
|
48
|
+
for (const [key, service] of Object.entries(services)) {
|
|
49
|
+
const icon = '📦';
|
|
50
|
+
const serviceName = `marketplace/services/${key.padEnd(12)}`;
|
|
51
|
+
const serviceDesc = service.description;
|
|
52
|
+
console.log(`${icon} ${chalk.green(serviceName)} ${chalk.gray(serviceDesc)}`);
|
|
53
|
+
const namespaceInfo = `namespace: ${service.namespace || 'default'}`;
|
|
54
|
+
console.log(` ${chalk.dim(namespaceInfo)}`);
|
|
55
|
+
console.log();
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
if (agents && Object.keys(agents).length > 0) {
|
|
59
|
+
console.log(chalk.bold('Agents:'));
|
|
60
|
+
console.log(chalk.gray('Install with: ark install marketplace/agents/<name>\n'));
|
|
61
|
+
for (const [key, agent] of Object.entries(agents)) {
|
|
62
|
+
const icon = '🤖';
|
|
63
|
+
const agentName = `marketplace/agents/${key.padEnd(12)}`;
|
|
64
|
+
const agentDesc = agent.description;
|
|
65
|
+
console.log(`${icon} ${chalk.green(agentName)} ${chalk.gray(agentDesc)}`);
|
|
66
|
+
const namespaceInfo = `namespace: ${agent.namespace || 'default'}`;
|
|
67
|
+
console.log(` ${chalk.dim(namespaceInfo)}`);
|
|
68
|
+
console.log();
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
console.log(chalk.cyan(`Repository: ${repoUrl}`));
|
|
72
|
+
console.log(chalk.cyan(`Registry: ${registry}`));
|
|
45
73
|
console.log();
|
|
46
74
|
});
|
|
47
75
|
marketplace.addCommand(list);
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import { jest } from '@jest/globals';
|
|
2
|
+
import { Command } from 'commander';
|
|
3
|
+
const mockGetAllMarketplaceServices = jest.fn();
|
|
4
|
+
const mockGetAllMarketplaceAgents = jest.fn();
|
|
5
|
+
const mockFetchMarketplaceManifest = jest.fn();
|
|
6
|
+
const mockConsoleLog = jest.spyOn(console, 'log').mockImplementation(() => { });
|
|
7
|
+
jest.unstable_mockModule('../../marketplaceServices.js', () => ({
|
|
8
|
+
getAllMarketplaceServices: mockGetAllMarketplaceServices,
|
|
9
|
+
getAllMarketplaceAgents: mockGetAllMarketplaceAgents,
|
|
10
|
+
}));
|
|
11
|
+
jest.unstable_mockModule('../../lib/marketplaceFetcher.js', () => ({
|
|
12
|
+
fetchMarketplaceManifest: mockFetchMarketplaceManifest,
|
|
13
|
+
}));
|
|
14
|
+
const { createMarketplaceCommand } = await import('./index.js');
|
|
15
|
+
describe('marketplace command', () => {
|
|
16
|
+
beforeEach(() => {
|
|
17
|
+
jest.clearAllMocks();
|
|
18
|
+
});
|
|
19
|
+
it('creates marketplace command with correct structure', () => {
|
|
20
|
+
const command = createMarketplaceCommand({});
|
|
21
|
+
expect(command).toBeInstanceOf(Command);
|
|
22
|
+
expect(command.name()).toBe('marketplace');
|
|
23
|
+
});
|
|
24
|
+
it('lists services and agents from manifest', async () => {
|
|
25
|
+
const mockServices = {
|
|
26
|
+
'test-service': {
|
|
27
|
+
name: 'test-service',
|
|
28
|
+
helmReleaseName: 'test-service',
|
|
29
|
+
description: 'Test service description',
|
|
30
|
+
enabled: true,
|
|
31
|
+
category: 'marketplace',
|
|
32
|
+
namespace: 'test-ns',
|
|
33
|
+
},
|
|
34
|
+
};
|
|
35
|
+
const mockAgents = {
|
|
36
|
+
'test-agent': {
|
|
37
|
+
name: 'test-agent',
|
|
38
|
+
helmReleaseName: 'test-agent',
|
|
39
|
+
description: 'Test agent description',
|
|
40
|
+
enabled: true,
|
|
41
|
+
category: 'marketplace',
|
|
42
|
+
namespace: 'test-ns',
|
|
43
|
+
},
|
|
44
|
+
};
|
|
45
|
+
const mockManifest = {
|
|
46
|
+
version: '1.0.0',
|
|
47
|
+
marketplace: 'ARK Marketplace',
|
|
48
|
+
items: [
|
|
49
|
+
{
|
|
50
|
+
name: 'test-service',
|
|
51
|
+
description: 'Test service',
|
|
52
|
+
type: 'service',
|
|
53
|
+
ark: {
|
|
54
|
+
chartPath: 'oci://registry/test-service',
|
|
55
|
+
namespace: 'test',
|
|
56
|
+
},
|
|
57
|
+
},
|
|
58
|
+
{
|
|
59
|
+
name: 'test-agent',
|
|
60
|
+
description: 'Test agent',
|
|
61
|
+
type: 'agent',
|
|
62
|
+
ark: {
|
|
63
|
+
chartPath: 'oci://registry/test-agent',
|
|
64
|
+
namespace: 'test',
|
|
65
|
+
},
|
|
66
|
+
},
|
|
67
|
+
],
|
|
68
|
+
};
|
|
69
|
+
mockGetAllMarketplaceServices.mockResolvedValue(mockServices);
|
|
70
|
+
mockGetAllMarketplaceAgents.mockResolvedValue(mockAgents);
|
|
71
|
+
mockFetchMarketplaceManifest.mockResolvedValue(mockManifest);
|
|
72
|
+
const command = createMarketplaceCommand({});
|
|
73
|
+
await command.parseAsync(['node', 'test', 'list']);
|
|
74
|
+
expect(mockGetAllMarketplaceServices).toHaveBeenCalled();
|
|
75
|
+
expect(mockGetAllMarketplaceAgents).toHaveBeenCalled();
|
|
76
|
+
expect(mockConsoleLog).toHaveBeenCalled();
|
|
77
|
+
});
|
|
78
|
+
it('shows unavailable message when marketplace unavailable', async () => {
|
|
79
|
+
mockGetAllMarketplaceServices.mockResolvedValue(null);
|
|
80
|
+
mockGetAllMarketplaceAgents.mockResolvedValue(null);
|
|
81
|
+
mockFetchMarketplaceManifest.mockResolvedValue(null);
|
|
82
|
+
const command = createMarketplaceCommand({});
|
|
83
|
+
await command.parseAsync(['node', 'test', 'list']);
|
|
84
|
+
expect(mockConsoleLog).toHaveBeenCalled();
|
|
85
|
+
const logCalls = mockConsoleLog.mock.calls.map((c) => c[0]).join(' ');
|
|
86
|
+
expect(logCalls).toContain('unavailable');
|
|
87
|
+
});
|
|
88
|
+
});
|
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
import { Command } from 'commander';
|
|
2
|
+
import { loadConfig } from '../../lib/config.js';
|
|
2
3
|
import output from '../../lib/output.js';
|
|
3
4
|
import { ArkApiProxy } from '../../lib/arkApiProxy.js';
|
|
4
5
|
export async function listSessions(options) {
|
|
5
6
|
try {
|
|
6
|
-
const
|
|
7
|
+
const config = loadConfig();
|
|
8
|
+
const proxy = new ArkApiProxy(undefined, config.services?.reusePortForwards ?? false);
|
|
7
9
|
const arkApiClient = await proxy.start();
|
|
8
10
|
const sessions = await arkApiClient.getSessions();
|
|
9
11
|
if (options.output === 'json') {
|
|
@@ -27,7 +29,8 @@ export async function listSessions(options) {
|
|
|
27
29
|
}
|
|
28
30
|
export async function deleteSession(sessionId, options) {
|
|
29
31
|
try {
|
|
30
|
-
const
|
|
32
|
+
const config = loadConfig();
|
|
33
|
+
const proxy = new ArkApiProxy(undefined, config.services?.reusePortForwards ?? false);
|
|
31
34
|
const arkApiClient = await proxy.start();
|
|
32
35
|
const response = await arkApiClient.deleteSession(sessionId);
|
|
33
36
|
if (options.output === 'json') {
|
|
@@ -44,7 +47,8 @@ export async function deleteSession(sessionId, options) {
|
|
|
44
47
|
}
|
|
45
48
|
export async function deleteQuery(sessionId, queryId, options) {
|
|
46
49
|
try {
|
|
47
|
-
const
|
|
50
|
+
const config = loadConfig();
|
|
51
|
+
const proxy = new ArkApiProxy(undefined, config.services?.reusePortForwards ?? false);
|
|
48
52
|
const arkApiClient = await proxy.start();
|
|
49
53
|
const response = await arkApiClient.deleteQueryMessages(sessionId, queryId);
|
|
50
54
|
if (options.output === 'json') {
|
|
@@ -61,7 +65,8 @@ export async function deleteQuery(sessionId, queryId, options) {
|
|
|
61
65
|
}
|
|
62
66
|
export async function deleteAll(options) {
|
|
63
67
|
try {
|
|
64
|
-
const
|
|
68
|
+
const config = loadConfig();
|
|
69
|
+
const proxy = new ArkApiProxy(undefined, config.services?.reusePortForwards ?? false);
|
|
65
70
|
const arkApiClient = await proxy.start();
|
|
66
71
|
const response = await arkApiClient.deleteAllSessions();
|
|
67
72
|
if (options.output === 'json') {
|
|
@@ -31,7 +31,7 @@ async function listModels(options) {
|
|
|
31
31
|
process.exit(1);
|
|
32
32
|
}
|
|
33
33
|
}
|
|
34
|
-
export function createModelsCommand(
|
|
34
|
+
export function createModelsCommand(config) {
|
|
35
35
|
const modelsCommand = new Command('models');
|
|
36
36
|
modelsCommand
|
|
37
37
|
.description('List available models')
|
|
@@ -69,11 +69,13 @@ export function createModelsCommand(_) {
|
|
|
69
69
|
.description('Query a model')
|
|
70
70
|
.argument('<name>', 'Model name (e.g., default)')
|
|
71
71
|
.argument('<message>', 'Message to send')
|
|
72
|
-
.
|
|
72
|
+
.option('--timeout <timeout>', 'Query timeout (e.g., 30s, 5m, 1h)')
|
|
73
|
+
.action(async (name, message, options) => {
|
|
73
74
|
await executeQuery({
|
|
74
75
|
targetType: 'model',
|
|
75
76
|
targetName: name,
|
|
76
77
|
message,
|
|
78
|
+
timeout: options.timeout || config.queryTimeout,
|
|
77
79
|
});
|
|
78
80
|
});
|
|
79
81
|
modelsCommand.addCommand(queryCommand);
|
|
@@ -2,14 +2,16 @@ import { Command } from 'commander';
|
|
|
2
2
|
import chalk from 'chalk';
|
|
3
3
|
import { executeQuery, parseTarget } from '../../lib/executeQuery.js';
|
|
4
4
|
import { ExitCodes } from '../../lib/errors.js';
|
|
5
|
-
export function createQueryCommand(
|
|
5
|
+
export function createQueryCommand(config) {
|
|
6
6
|
const queryCommand = new Command('query');
|
|
7
7
|
queryCommand
|
|
8
8
|
.description('Execute a single query against a model or agent')
|
|
9
9
|
.argument('<target>', 'Query target (e.g., model/default, agent/my-agent)')
|
|
10
10
|
.argument('<message>', 'Message to send')
|
|
11
|
-
.option('-o, --output <format>', 'Output format: yaml, json, or
|
|
11
|
+
.option('-o, --output <format>', 'Output format: yaml, json, name or events (shows structured event data)')
|
|
12
|
+
.option('--timeout <timeout>', 'Query timeout (e.g., 30s, 5m, 1h)')
|
|
12
13
|
.option('--session-id <sessionId>', 'Session ID to associate with the query for conversation continuity')
|
|
14
|
+
.option('--conversation-id <conversationId>', 'Conversation ID to associate with the query for memory continuity')
|
|
13
15
|
.action(async (target, message, options) => {
|
|
14
16
|
const parsed = parseTarget(target);
|
|
15
17
|
if (!parsed) {
|
|
@@ -21,7 +23,9 @@ export function createQueryCommand(_) {
|
|
|
21
23
|
targetName: parsed.name,
|
|
22
24
|
message,
|
|
23
25
|
outputFormat: options.output,
|
|
26
|
+
timeout: options.timeout || config.queryTimeout,
|
|
24
27
|
sessionId: options.sessionId,
|
|
28
|
+
conversationId: options.conversationId,
|
|
25
29
|
});
|
|
26
30
|
});
|
|
27
31
|
return queryCommand;
|
|
@@ -327,7 +327,12 @@ export function createStatusCommand() {
|
|
|
327
327
|
statusCommand
|
|
328
328
|
.description('Check ARK system status')
|
|
329
329
|
.argument('[services...]', 'specific services to check (optional)')
|
|
330
|
-
.option('--wait-for-ready
|
|
331
|
-
.action((services, options) =>
|
|
330
|
+
.option('--wait-for-ready [timeout]', 'wait for services to be ready, e.g, 30s, 2m, 1h (default: 30m)')
|
|
331
|
+
.action((services, options) => {
|
|
332
|
+
if (options.waitForReady === true) {
|
|
333
|
+
options.waitForReady = '30m';
|
|
334
|
+
}
|
|
335
|
+
checkStatus(services, options);
|
|
336
|
+
});
|
|
332
337
|
return statusCommand;
|
|
333
338
|
}
|
|
@@ -29,7 +29,7 @@ async function listTeams(options) {
|
|
|
29
29
|
process.exit(1);
|
|
30
30
|
}
|
|
31
31
|
}
|
|
32
|
-
export function createTeamsCommand(
|
|
32
|
+
export function createTeamsCommand(config) {
|
|
33
33
|
const teamsCommand = new Command('teams');
|
|
34
34
|
teamsCommand
|
|
35
35
|
.description('List available teams')
|
|
@@ -52,11 +52,13 @@ export function createTeamsCommand(_) {
|
|
|
52
52
|
.description('Query a team')
|
|
53
53
|
.argument('<name>', 'Team name')
|
|
54
54
|
.argument('<message>', 'Message to send')
|
|
55
|
-
.
|
|
55
|
+
.option('--timeout <timeout>', 'Query timeout (e.g., 30s, 5m, 1h)')
|
|
56
|
+
.action(async (name, message, options) => {
|
|
56
57
|
await executeQuery({
|
|
57
58
|
targetType: 'team',
|
|
58
59
|
targetName: name,
|
|
59
60
|
message,
|
|
61
|
+
timeout: options.timeout || config.queryTimeout,
|
|
60
62
|
});
|
|
61
63
|
});
|
|
62
64
|
teamsCommand.addCommand(queryCommand);
|
|
@@ -5,7 +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,
|
|
8
|
+
import { isMarketplaceService, getMarketplaceItem, getAllMarketplaceServices, getAllMarketplaceAgents, } from '../../marketplaceServices.js';
|
|
9
9
|
async function uninstallService(service, verbose = false) {
|
|
10
10
|
const helmArgs = ['uninstall', service.helmReleaseName, '--ignore-not-found'];
|
|
11
11
|
// Only add namespace flag if service has explicit namespace
|
|
@@ -26,20 +26,30 @@ async function uninstallArk(config, serviceName, options = {}) {
|
|
|
26
26
|
console.log(); // Add blank line after cluster info
|
|
27
27
|
// If a specific service is requested, uninstall only that service
|
|
28
28
|
if (serviceName) {
|
|
29
|
-
// Check if it's a marketplace
|
|
29
|
+
// Check if it's a marketplace item
|
|
30
30
|
if (isMarketplaceService(serviceName)) {
|
|
31
|
-
const
|
|
32
|
-
const service = getMarketplaceService(marketplaceServiceName);
|
|
31
|
+
const service = await getMarketplaceItem(serviceName);
|
|
33
32
|
if (!service) {
|
|
34
|
-
output.error(`marketplace
|
|
35
|
-
output.info('available marketplace
|
|
36
|
-
const marketplaceServices = getAllMarketplaceServices();
|
|
37
|
-
|
|
38
|
-
|
|
33
|
+
output.error(`marketplace item '${serviceName}' not found`);
|
|
34
|
+
output.info('available marketplace items:');
|
|
35
|
+
const marketplaceServices = await getAllMarketplaceServices();
|
|
36
|
+
if (marketplaceServices) {
|
|
37
|
+
for (const name of Object.keys(marketplaceServices)) {
|
|
38
|
+
output.info(` marketplace/services/${name}`);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
const marketplaceAgents = await getAllMarketplaceAgents();
|
|
42
|
+
if (marketplaceAgents) {
|
|
43
|
+
for (const name of Object.keys(marketplaceAgents)) {
|
|
44
|
+
output.info(` marketplace/agents/${name}`);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
if (!marketplaceServices && !marketplaceAgents) {
|
|
48
|
+
output.warning('Marketplace unavailable');
|
|
39
49
|
}
|
|
40
50
|
process.exit(1);
|
|
41
51
|
}
|
|
42
|
-
output.info(`uninstalling marketplace
|
|
52
|
+
output.info(`uninstalling marketplace item ${service.name}...`);
|
|
43
53
|
try {
|
|
44
54
|
await uninstallService(service, options.verbose);
|
|
45
55
|
output.success(`${service.name} uninstalled successfully`);
|
package/dist/index.js
CHANGED
|
@@ -14,7 +14,9 @@ import { createCompletionCommand } from './commands/completion/index.js';
|
|
|
14
14
|
import { createDashboardCommand } from './commands/dashboard/index.js';
|
|
15
15
|
import { createDocsCommand } from './commands/docs/index.js';
|
|
16
16
|
import { createEvaluationCommand } from './commands/evaluation/index.js';
|
|
17
|
+
import { createExportCommand } from './commands/export/index.js';
|
|
17
18
|
import { createGenerateCommand } from './commands/generate/index.js';
|
|
19
|
+
import { createImportCommand } from './commands/import/index.js';
|
|
18
20
|
import { createInstallCommand } from './commands/install/index.js';
|
|
19
21
|
import { createMarketplaceCommand } from './commands/marketplace/index.js';
|
|
20
22
|
import { createMemoryCommand } from './commands/memory/index.js';
|
|
@@ -48,7 +50,9 @@ async function main() {
|
|
|
48
50
|
program.addCommand(createDashboardCommand(config));
|
|
49
51
|
program.addCommand(createDocsCommand(config));
|
|
50
52
|
program.addCommand(createEvaluationCommand(config));
|
|
53
|
+
program.addCommand(createExportCommand(config));
|
|
51
54
|
program.addCommand(createGenerateCommand(config));
|
|
55
|
+
program.addCommand(createImportCommand(config));
|
|
52
56
|
program.addCommand(createInstallCommand(config));
|
|
53
57
|
program.addCommand(createMarketplaceCommand(config));
|
|
54
58
|
program.addCommand(createMemoryCommand(config));
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { ArkApiClient } from './arkApiClient.js';
|
|
2
2
|
export declare class ArkApiProxy {
|
|
3
3
|
private serviceProxy;
|
|
4
|
-
constructor(localPort?: number);
|
|
4
|
+
constructor(localPort?: number, reusePortForwards?: boolean);
|
|
5
5
|
start(): Promise<ArkApiClient>;
|
|
6
6
|
stop(): void;
|
|
7
7
|
isRunning(): boolean;
|
package/dist/lib/arkApiProxy.js
CHANGED
|
@@ -2,9 +2,9 @@ import { ArkApiClient } from './arkApiClient.js';
|
|
|
2
2
|
import { ArkServiceProxy } from './arkServiceProxy.js';
|
|
3
3
|
import { arkServices } from '../arkServices.js';
|
|
4
4
|
export class ArkApiProxy {
|
|
5
|
-
constructor(localPort) {
|
|
5
|
+
constructor(localPort, reusePortForwards = false) {
|
|
6
6
|
const arkApiService = arkServices['ark-api'];
|
|
7
|
-
this.serviceProxy = new ArkServiceProxy(arkApiService, localPort);
|
|
7
|
+
this.serviceProxy = new ArkServiceProxy(arkApiService, localPort, reusePortForwards);
|
|
8
8
|
}
|
|
9
9
|
async start() {
|
|
10
10
|
const arkApiUrl = await this.serviceProxy.start();
|
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
import { ArkService } from '../arkServices.js';
|
|
2
2
|
export declare class ArkServiceProxy {
|
|
3
|
+
private reusePortForwards;
|
|
3
4
|
private kubectlProcess?;
|
|
4
5
|
private localPort;
|
|
5
6
|
private isReady;
|
|
6
7
|
private service;
|
|
7
|
-
constructor(service: ArkService, localPort?: number);
|
|
8
|
+
constructor(service: ArkService, localPort?: number, reusePortForwards?: boolean);
|
|
8
9
|
private getRandomPort;
|
|
10
|
+
private checkExistingPortForward;
|
|
9
11
|
start(): Promise<string>;
|
|
10
12
|
stop(): void;
|
|
11
13
|
isRunning(): boolean;
|
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
import { spawn } from 'child_process';
|
|
2
|
+
import find from 'find-process';
|
|
3
|
+
import Debug from 'debug';
|
|
4
|
+
const debug = Debug('ark:service-proxy');
|
|
2
5
|
export class ArkServiceProxy {
|
|
3
|
-
constructor(service, localPort) {
|
|
6
|
+
constructor(service, localPort, reusePortForwards = false) {
|
|
7
|
+
this.reusePortForwards = reusePortForwards;
|
|
4
8
|
this.isReady = false;
|
|
5
9
|
this.service = service;
|
|
6
10
|
this.localPort =
|
|
@@ -9,10 +13,39 @@ export class ArkServiceProxy {
|
|
|
9
13
|
getRandomPort() {
|
|
10
14
|
return Math.floor(Math.random() * (65535 - 1024) + 1024);
|
|
11
15
|
}
|
|
16
|
+
async checkExistingPortForward() {
|
|
17
|
+
try {
|
|
18
|
+
const processes = await find('port', this.localPort);
|
|
19
|
+
if (processes.length === 0) {
|
|
20
|
+
return false;
|
|
21
|
+
}
|
|
22
|
+
const kubectlProcess = processes.find((proc) => proc.cmd?.includes('kubectl') && proc.cmd?.includes('port-forward'));
|
|
23
|
+
if (kubectlProcess) {
|
|
24
|
+
debug(`Reusing existing kubectl port-forward on port ${this.localPort} (PID: ${kubectlProcess.pid})`);
|
|
25
|
+
this.isReady = true;
|
|
26
|
+
return true;
|
|
27
|
+
}
|
|
28
|
+
const processInfo = processes[0];
|
|
29
|
+
throw new Error(`${this.service.name} port forward failed: port ${this.localPort} is already in use by ${processInfo.name} (PID: ${processInfo.pid})`);
|
|
30
|
+
}
|
|
31
|
+
catch (error) {
|
|
32
|
+
if (error instanceof Error && error.message.includes('already in use')) {
|
|
33
|
+
throw error;
|
|
34
|
+
}
|
|
35
|
+
debug(`Error checking for existing port-forward: ${error}`);
|
|
36
|
+
return false;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
12
39
|
async start() {
|
|
13
40
|
if (!this.service.k8sServiceName || !this.service.k8sServicePort) {
|
|
14
41
|
throw new Error(`${this.service.name} service configuration missing k8sServiceName or k8sServicePort`);
|
|
15
42
|
}
|
|
43
|
+
if (this.reusePortForwards) {
|
|
44
|
+
const isReused = await this.checkExistingPortForward();
|
|
45
|
+
if (isReused) {
|
|
46
|
+
return `http://localhost:${this.localPort}`;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
16
49
|
return new Promise((resolve, reject) => {
|
|
17
50
|
const args = [
|
|
18
51
|
'port-forward',
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import { jest } from '@jest/globals';
|
|
2
|
+
import { Buffer } from 'buffer';
|
|
3
|
+
const mockFind = jest.fn();
|
|
4
|
+
jest.unstable_mockModule('find-process', () => ({
|
|
5
|
+
default: mockFind,
|
|
6
|
+
}));
|
|
7
|
+
const mockSpawn = jest.fn();
|
|
8
|
+
const mockChildProcess = {
|
|
9
|
+
spawn: mockSpawn,
|
|
10
|
+
};
|
|
11
|
+
jest.unstable_mockModule('child_process', () => ({
|
|
12
|
+
...mockChildProcess,
|
|
13
|
+
spawn: mockSpawn,
|
|
14
|
+
}));
|
|
15
|
+
const { ArkServiceProxy } = await import('./arkServiceProxy.js');
|
|
16
|
+
describe('ArkServiceProxy', () => {
|
|
17
|
+
const mockService = {
|
|
18
|
+
name: 'test-service',
|
|
19
|
+
helmReleaseName: 'test-service',
|
|
20
|
+
description: 'Test service',
|
|
21
|
+
k8sServiceName: 'test-service-k8s',
|
|
22
|
+
k8sServicePort: 8080,
|
|
23
|
+
namespace: 'default',
|
|
24
|
+
enabled: true,
|
|
25
|
+
category: 'test',
|
|
26
|
+
};
|
|
27
|
+
beforeEach(() => {
|
|
28
|
+
jest.clearAllMocks();
|
|
29
|
+
});
|
|
30
|
+
describe('port-forward reuse', () => {
|
|
31
|
+
it('creates new port-forward when reuse is disabled', async () => {
|
|
32
|
+
const proxy = new ArkServiceProxy(mockService, 3000, false);
|
|
33
|
+
const mockProcess = {
|
|
34
|
+
stdout: { on: jest.fn() },
|
|
35
|
+
stderr: { on: jest.fn() },
|
|
36
|
+
on: jest.fn(),
|
|
37
|
+
kill: jest.fn(),
|
|
38
|
+
};
|
|
39
|
+
mockSpawn.mockReturnValue(mockProcess);
|
|
40
|
+
setTimeout(() => {
|
|
41
|
+
const stdoutCallback = mockProcess.stdout.on.mock.calls.find((call) => call[0] === 'data')?.[1];
|
|
42
|
+
if (stdoutCallback) {
|
|
43
|
+
stdoutCallback(Buffer.from('Forwarding from 127.0.0.1:3000'));
|
|
44
|
+
}
|
|
45
|
+
}, 10);
|
|
46
|
+
const url = await proxy.start();
|
|
47
|
+
expect(url).toBe('http://localhost:3000');
|
|
48
|
+
expect(mockFind).not.toHaveBeenCalled();
|
|
49
|
+
expect(mockSpawn).toHaveBeenCalledWith('kubectl', ['port-forward', 'service/test-service-k8s', '3000:8080', '--namespace', 'default'], expect.any(Object));
|
|
50
|
+
});
|
|
51
|
+
it('reuses existing kubectl port-forward when reuse is enabled', async () => {
|
|
52
|
+
mockFind.mockResolvedValue([
|
|
53
|
+
{
|
|
54
|
+
pid: 12345,
|
|
55
|
+
name: 'kubectl',
|
|
56
|
+
cmd: 'kubectl port-forward service/test-service-k8s 3000:8080',
|
|
57
|
+
},
|
|
58
|
+
]);
|
|
59
|
+
const proxy = new ArkServiceProxy(mockService, 3000, true);
|
|
60
|
+
const url = await proxy.start();
|
|
61
|
+
expect(url).toBe('http://localhost:3000');
|
|
62
|
+
expect(mockFind).toHaveBeenCalledWith('port', 3000);
|
|
63
|
+
expect(mockSpawn).not.toHaveBeenCalled();
|
|
64
|
+
});
|
|
65
|
+
it('creates new port-forward when port is not in use', async () => {
|
|
66
|
+
mockFind.mockResolvedValue([]);
|
|
67
|
+
const proxy = new ArkServiceProxy(mockService, 3000, true);
|
|
68
|
+
const mockProcess = {
|
|
69
|
+
stdout: { on: jest.fn() },
|
|
70
|
+
stderr: { on: jest.fn() },
|
|
71
|
+
on: jest.fn(),
|
|
72
|
+
kill: jest.fn(),
|
|
73
|
+
};
|
|
74
|
+
mockSpawn.mockReturnValue(mockProcess);
|
|
75
|
+
setTimeout(() => {
|
|
76
|
+
const stdoutCallback = mockProcess.stdout.on.mock.calls.find((call) => call[0] === 'data')?.[1];
|
|
77
|
+
if (stdoutCallback) {
|
|
78
|
+
stdoutCallback(Buffer.from('Forwarding from 127.0.0.1:3000'));
|
|
79
|
+
}
|
|
80
|
+
}, 10);
|
|
81
|
+
const url = await proxy.start();
|
|
82
|
+
expect(url).toBe('http://localhost:3000');
|
|
83
|
+
expect(mockFind).toHaveBeenCalledWith('port', 3000);
|
|
84
|
+
expect(mockSpawn).toHaveBeenCalled();
|
|
85
|
+
});
|
|
86
|
+
it('throws error when port is in use by non-kubectl process', async () => {
|
|
87
|
+
mockFind.mockResolvedValue([
|
|
88
|
+
{
|
|
89
|
+
pid: 54321,
|
|
90
|
+
name: 'node',
|
|
91
|
+
cmd: 'node server.js',
|
|
92
|
+
},
|
|
93
|
+
]);
|
|
94
|
+
const proxy = new ArkServiceProxy(mockService, 3000, true);
|
|
95
|
+
await expect(proxy.start()).rejects.toThrow('test-service port forward failed: port 3000 is already in use by node (PID: 54321)');
|
|
96
|
+
expect(mockFind).toHaveBeenCalledWith('port', 3000);
|
|
97
|
+
expect(mockSpawn).not.toHaveBeenCalled();
|
|
98
|
+
});
|
|
99
|
+
});
|
|
100
|
+
});
|
package/dist/lib/chatClient.d.ts
CHANGED
package/dist/lib/chatClient.js
CHANGED
|
@@ -17,12 +17,20 @@ export class ChatClient {
|
|
|
17
17
|
signal: signal,
|
|
18
18
|
};
|
|
19
19
|
// Build metadata object - only add if we have something to include
|
|
20
|
-
if (config.sessionId ||
|
|
20
|
+
if (config.sessionId ||
|
|
21
|
+
config.conversationId ||
|
|
22
|
+
config.a2aContextId ||
|
|
23
|
+
config.queryTimeout) {
|
|
21
24
|
params.metadata = {};
|
|
22
|
-
// Add sessionId directly to metadata (goes to spec, not annotations)
|
|
23
25
|
if (config.sessionId) {
|
|
24
26
|
params.metadata.sessionId = config.sessionId;
|
|
25
27
|
}
|
|
28
|
+
if (config.conversationId) {
|
|
29
|
+
params.metadata.conversationId = config.conversationId;
|
|
30
|
+
}
|
|
31
|
+
if (config.queryTimeout) {
|
|
32
|
+
params.metadata.timeout = config.queryTimeout;
|
|
33
|
+
}
|
|
26
34
|
// Add A2A context ID to queryAnnotations (goes to annotations)
|
|
27
35
|
if (config.a2aContextId) {
|
|
28
36
|
const queryAnnotations = {
|