@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
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
# Ark CLI Configuration Template
|
|
2
|
+
# Copy this to ~/.arkrc.yaml (user config) or .arkrc.yaml (project config)
|
|
3
|
+
# All fields are optional - only include what you want to override
|
|
4
|
+
|
|
5
|
+
# Chat configuration
|
|
6
|
+
chat:
|
|
7
|
+
# Enable/disable streaming for chat responses (default: true)
|
|
8
|
+
# streaming: true
|
|
9
|
+
|
|
10
|
+
# Output format: 'text' or 'markdown' (default: text)
|
|
11
|
+
# outputFormat: text
|
|
12
|
+
|
|
13
|
+
# Service configuration
|
|
14
|
+
services:
|
|
15
|
+
# Reuse existing kubectl port-forward processes instead of failing (default: false)
|
|
16
|
+
# reusePortForwards: false
|
|
17
|
+
|
|
18
|
+
# Service overrides - customize specific Ark services
|
|
19
|
+
# Example: Enable localhost-gateway
|
|
20
|
+
# localhost-gateway:
|
|
21
|
+
# enabled: true
|
|
22
|
+
|
|
23
|
+
# Example: Change namespace
|
|
24
|
+
# ark-controller:
|
|
25
|
+
# namespace: custom-namespace
|
|
26
|
+
|
|
27
|
+
# Example: Add custom install arguments
|
|
28
|
+
# ark-api:
|
|
29
|
+
# installArgs:
|
|
30
|
+
# - --set
|
|
31
|
+
# - replicas=3
|
|
32
|
+
|
|
33
|
+
# Example: Configure Agents @ Scale with container registry
|
|
34
|
+
# agents-at-scale:
|
|
35
|
+
# enabled: true
|
|
36
|
+
# installArgs:
|
|
37
|
+
# - --set
|
|
38
|
+
# - containerRegistry.enabled=true
|
|
39
|
+
# - --set
|
|
40
|
+
# - containerRegistry.username=YOUR_USERNAME
|
|
41
|
+
# - --set
|
|
42
|
+
# - containerRegistry.password=YOUR_PASSWORD
|
|
43
|
+
|
|
44
|
+
# Available service properties:
|
|
45
|
+
# enabled: boolean - Enable/disable service installation
|
|
46
|
+
# namespace: string - Kubernetes namespace
|
|
47
|
+
# chartPath: string - Custom Helm chart path
|
|
48
|
+
# installArgs: string[] - Additional Helm install arguments
|
|
49
|
+
# k8sServiceName: string - Kubernetes service name for port forwarding
|
|
50
|
+
# k8sServicePort: number - Kubernetes service port for port forwarding
|
|
51
|
+
# k8sPortForwardLocalPort: number - Local port for port forwarding
|
package/README.md
CHANGED
|
@@ -46,6 +46,8 @@ To troubleshoot an installation, run `ark status`.
|
|
|
46
46
|
|
|
47
47
|
You can customize Ark service installations using a `.arkrc.yaml` file in your home directory (`~/.arkrc.yaml`) or project directory. This allows you to override service properties like enabled status, namespace, or chart location.
|
|
48
48
|
|
|
49
|
+
See [`.arkrc.template.yaml`](.arkrc.template.yaml) for a complete reference of all available configuration options.
|
|
50
|
+
|
|
49
51
|
Example `.arkrc.yaml`:
|
|
50
52
|
|
|
51
53
|
```yaml
|
|
@@ -76,3 +78,5 @@ services:
|
|
|
76
78
|
```
|
|
77
79
|
|
|
78
80
|
Replace `YOUR_USERNAME` and `YOUR_PASSWORD` with your JFrog credentials.
|
|
81
|
+
|
|
82
|
+
See [.arkrc.yaml.sample](.arkrc.yaml.sample) for a complete example with all available options.
|
package/dist/arkServices.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Centralized ARK service definitions used by both install and status commands
|
|
3
3
|
*/
|
|
4
|
-
import { loadConfig } from './lib/config.js';
|
|
4
|
+
import { loadConfig, getMarketplaceRegistry } from './lib/config.js';
|
|
5
5
|
const REGISTRY_BASE = 'oci://ghcr.io/mckinsey/agents-at-scale-ark/charts';
|
|
6
6
|
/**
|
|
7
7
|
* Dependencies that should be installed before ARK services
|
|
@@ -119,17 +119,17 @@ const defaultArkServices = {
|
|
|
119
119
|
k8sDeploymentName: 'ark-mcp',
|
|
120
120
|
k8sDevDeploymentName: 'ark-mcp-devspace',
|
|
121
121
|
},
|
|
122
|
-
'ark-
|
|
123
|
-
name: 'ark-
|
|
124
|
-
helmReleaseName: 'ark-
|
|
122
|
+
'ark-broker': {
|
|
123
|
+
name: 'ark-broker',
|
|
124
|
+
helmReleaseName: 'ark-broker',
|
|
125
125
|
description: 'In-memory storage service with streaming support for Ark queries',
|
|
126
126
|
enabled: true,
|
|
127
127
|
category: 'service',
|
|
128
128
|
// namespace: undefined - uses current context namespace
|
|
129
|
-
chartPath: `${REGISTRY_BASE}/ark-
|
|
129
|
+
chartPath: `${REGISTRY_BASE}/ark-broker`,
|
|
130
130
|
installArgs: [],
|
|
131
|
-
k8sDeploymentName: 'ark-
|
|
132
|
-
k8sDevDeploymentName: 'ark-
|
|
131
|
+
k8sDeploymentName: 'ark-broker',
|
|
132
|
+
k8sDevDeploymentName: 'ark-broker-devspace',
|
|
133
133
|
},
|
|
134
134
|
'mcp-filesystem': {
|
|
135
135
|
name: 'mcp-filesystem',
|
|
@@ -153,13 +153,27 @@ const defaultArkServices = {
|
|
|
153
153
|
chartPath: `${REGISTRY_BASE}/localhost-gateway`,
|
|
154
154
|
installArgs: [],
|
|
155
155
|
},
|
|
156
|
+
'noah': {
|
|
157
|
+
name: 'noah',
|
|
158
|
+
helmReleaseName: 'noah',
|
|
159
|
+
description: 'Runtime administration agent with cluster privileges',
|
|
160
|
+
enabled: true,
|
|
161
|
+
category: 'service',
|
|
162
|
+
chartPath: `${getMarketplaceRegistry()}/noah`,
|
|
163
|
+
installArgs: [],
|
|
164
|
+
k8sServiceName: 'noah-mcp',
|
|
165
|
+
k8sServicePort: 8639,
|
|
166
|
+
k8sDeploymentName: 'noah-mcp',
|
|
167
|
+
},
|
|
156
168
|
};
|
|
157
169
|
function applyConfigOverrides(defaults) {
|
|
158
170
|
const config = loadConfig();
|
|
159
171
|
const overrides = config?.services || {};
|
|
160
172
|
const result = {};
|
|
161
173
|
for (const [key, service] of Object.entries(defaults)) {
|
|
162
|
-
|
|
174
|
+
const override = overrides[key];
|
|
175
|
+
result[key] =
|
|
176
|
+
override && typeof override === 'object' ? { ...service, ...override } : service;
|
|
163
177
|
}
|
|
164
178
|
return result;
|
|
165
179
|
}
|
package/dist/arkServices.spec.js
CHANGED
|
@@ -1,12 +1,18 @@
|
|
|
1
1
|
import { jest } from '@jest/globals';
|
|
2
2
|
const mockLoadConfig = jest.fn();
|
|
3
|
+
const mockGetMarketplaceRegistry = jest.fn();
|
|
3
4
|
jest.unstable_mockModule('./lib/config.js', () => ({
|
|
4
5
|
loadConfig: mockLoadConfig,
|
|
6
|
+
getMarketplaceRegistry: mockGetMarketplaceRegistry,
|
|
5
7
|
}));
|
|
8
|
+
mockLoadConfig.mockReturnValue({});
|
|
9
|
+
mockGetMarketplaceRegistry.mockReturnValue('oci://test-registry/charts');
|
|
6
10
|
const { arkDependencies, arkServices: originalArkServices, getInstallableServices, } = await import('./arkServices.js');
|
|
7
11
|
describe('arkServices', () => {
|
|
8
12
|
beforeEach(() => {
|
|
9
13
|
jest.clearAllMocks();
|
|
14
|
+
mockLoadConfig.mockReturnValue({});
|
|
15
|
+
mockGetMarketplaceRegistry.mockReturnValue('oci://test-registry/charts');
|
|
10
16
|
});
|
|
11
17
|
it('exports arkDependencies with expected structure', () => {
|
|
12
18
|
expect(arkDependencies).toBeDefined();
|
|
@@ -30,7 +30,7 @@ async function listAgents(options) {
|
|
|
30
30
|
process.exit(1);
|
|
31
31
|
}
|
|
32
32
|
}
|
|
33
|
-
export function createAgentsCommand(
|
|
33
|
+
export function createAgentsCommand(config) {
|
|
34
34
|
const agentsCommand = new Command('agents');
|
|
35
35
|
agentsCommand
|
|
36
36
|
.description('list available agents')
|
|
@@ -54,11 +54,13 @@ export function createAgentsCommand(_) {
|
|
|
54
54
|
.description('Query an agent')
|
|
55
55
|
.argument('<name>', 'Agent name')
|
|
56
56
|
.argument('<message>', 'Message to send')
|
|
57
|
-
.
|
|
57
|
+
.option('--timeout <timeout>', 'Query timeout (e.g., 30s, 5m, 1h)')
|
|
58
|
+
.action(async (name, message, options) => {
|
|
58
59
|
await executeQuery({
|
|
59
60
|
targetType: 'agent',
|
|
60
61
|
targetName: name,
|
|
61
62
|
message,
|
|
63
|
+
timeout: options.timeout || config.queryTimeout,
|
|
62
64
|
});
|
|
63
65
|
});
|
|
64
66
|
return agentsCommand;
|
|
@@ -14,7 +14,7 @@ export function createChatCommand(config) {
|
|
|
14
14
|
const initialTargetId = targetArg;
|
|
15
15
|
// Config is passed from main
|
|
16
16
|
try {
|
|
17
|
-
const proxy = new ArkApiProxy();
|
|
17
|
+
const proxy = new ArkApiProxy(undefined, config.services?.reusePortForwards ?? false);
|
|
18
18
|
const arkApiClient = await proxy.start();
|
|
19
19
|
render(_jsx(ChatUI, { initialTargetId: initialTargetId, arkApiClient: arkApiClient, arkApiProxy: proxy, config: config }));
|
|
20
20
|
}
|
|
@@ -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 marketplace models queries query routes status targets teams tools uninstall help"
|
|
31
|
+
opts="agents chat cluster completion config dashboard docs evaluation export generate import install marketplace memory models queries query routes status targets teams tools uninstall help"
|
|
32
32
|
COMPREPLY=( $(compgen -W "\${opts}" -- \${cur}) )
|
|
33
33
|
return 0
|
|
34
34
|
;;
|
|
@@ -84,15 +84,18 @@ _ark_completion() {
|
|
|
84
84
|
COMPREPLY=( $(compgen -W "\${opts}" -- \${cur}) )
|
|
85
85
|
return 0
|
|
86
86
|
;;
|
|
87
|
+
memory)
|
|
88
|
+
opts="list ls delete reset"
|
|
89
|
+
COMPREPLY=( $(compgen -W "\${opts}" -- \${cur}) )
|
|
90
|
+
return 0
|
|
91
|
+
;;
|
|
87
92
|
install)
|
|
88
|
-
|
|
89
|
-
opts="marketplace/services/phoenix marketplace/services/langfuse"
|
|
93
|
+
opts="marketplace/services/phoenix marketplace/services/langfuse marketplace/agents/noah"
|
|
90
94
|
COMPREPLY=( $(compgen -W "\${opts}" -- \${cur}) )
|
|
91
95
|
return 0
|
|
92
96
|
;;
|
|
93
97
|
uninstall)
|
|
94
|
-
|
|
95
|
-
opts="marketplace/services/phoenix marketplace/services/langfuse"
|
|
98
|
+
opts="marketplace/services/phoenix marketplace/services/langfuse marketplace/agents/noah"
|
|
96
99
|
COMPREPLY=( $(compgen -W "\${opts}" -- \${cur}) )
|
|
97
100
|
return 0
|
|
98
101
|
;;
|
|
@@ -157,9 +160,13 @@ _ark() {
|
|
|
157
160
|
'config[Configuration management]' \\
|
|
158
161
|
'dashboard[Open ARK dashboard]' \\
|
|
159
162
|
'docs[Open ARK documentation]' \\
|
|
163
|
+
'evaluation[Execute evaluations against evaluators]' \\
|
|
164
|
+
'export[Export ARK resources to a file]' \\
|
|
160
165
|
'generate[Generate ARK resources]' \\
|
|
166
|
+
'import[Import ARK resources from a file]' \\
|
|
161
167
|
'install[Install ARK services]' \\
|
|
162
168
|
'marketplace[Manage marketplace services]' \\
|
|
169
|
+
'memory[Manage memory sessions and queries]' \\
|
|
163
170
|
'models[List available models]' \\
|
|
164
171
|
'queries[Manage query resources]' \\
|
|
165
172
|
'query[Execute a single query]' \\
|
|
@@ -227,6 +234,13 @@ _ark() {
|
|
|
227
234
|
'list[List available marketplace services]' \\
|
|
228
235
|
'ls[List available marketplace services]'
|
|
229
236
|
;;
|
|
237
|
+
memory)
|
|
238
|
+
_values 'memory commands' \\
|
|
239
|
+
'list[List all sessions]' \\
|
|
240
|
+
'ls[List all sessions]' \\
|
|
241
|
+
'delete[Delete memory data]' \\
|
|
242
|
+
'reset[Delete memory data]'
|
|
243
|
+
;;
|
|
230
244
|
install)
|
|
231
245
|
_values 'services to install' \\
|
|
232
246
|
'marketplace/services/phoenix[Phoenix observability platform]' \\
|
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
import { Command } from 'commander';
|
|
2
2
|
import type { ArkConfig } from '../../lib/config.js';
|
|
3
|
-
export declare function openDashboard(): Promise<void>;
|
|
4
|
-
export declare function createDashboardCommand(
|
|
3
|
+
export declare function openDashboard(config: ArkConfig): Promise<void>;
|
|
4
|
+
export declare function createDashboardCommand(config: ArkConfig): Command;
|
|
@@ -4,11 +4,12 @@ import open from 'open';
|
|
|
4
4
|
import ora from 'ora';
|
|
5
5
|
import { ArkServiceProxy } from '../../lib/arkServiceProxy.js';
|
|
6
6
|
import { arkServices } from '../../arkServices.js';
|
|
7
|
-
export async function openDashboard() {
|
|
7
|
+
export async function openDashboard(config) {
|
|
8
8
|
const spinner = ora('Connecting to dashboard').start();
|
|
9
9
|
try {
|
|
10
10
|
const dashboardService = arkServices['ark-dashboard'];
|
|
11
|
-
const proxy = new ArkServiceProxy(dashboardService, 3274
|
|
11
|
+
const proxy = new ArkServiceProxy(dashboardService, 3274, // DASH on phone keypad
|
|
12
|
+
config.services?.reusePortForwards ?? false);
|
|
12
13
|
const url = await proxy.start();
|
|
13
14
|
spinner.succeed('Dashboard connected');
|
|
14
15
|
console.log(`ARK dashboard running on: ${chalk.green(url)}`);
|
|
@@ -30,10 +31,10 @@ export async function openDashboard() {
|
|
|
30
31
|
process.exit(1);
|
|
31
32
|
}
|
|
32
33
|
}
|
|
33
|
-
export function createDashboardCommand(
|
|
34
|
+
export function createDashboardCommand(config) {
|
|
34
35
|
const dashboardCommand = new Command('dashboard');
|
|
35
36
|
dashboardCommand
|
|
36
37
|
.description('Open the ARK dashboard in your browser')
|
|
37
|
-
.action(openDashboard);
|
|
38
|
+
.action(() => openDashboard(config));
|
|
38
39
|
return dashboardCommand;
|
|
39
40
|
}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { Command } from 'commander';
|
|
2
|
+
import * as fs from 'fs/promises';
|
|
3
|
+
import yaml from 'yaml';
|
|
4
|
+
import { listResources } from '../../lib/kubectl.js';
|
|
5
|
+
import output from '../../lib/output.js';
|
|
6
|
+
// resource types in dependency order so that they can be loaded correctly
|
|
7
|
+
// by default these will all be exported if not specified; can be overridden with defaultExportTypes in config
|
|
8
|
+
const RESOURCE_ORDER = [
|
|
9
|
+
'secrets',
|
|
10
|
+
'tools',
|
|
11
|
+
'models',
|
|
12
|
+
'agents',
|
|
13
|
+
'teams',
|
|
14
|
+
'evaluators',
|
|
15
|
+
'mcpservers',
|
|
16
|
+
'a2aservers',
|
|
17
|
+
];
|
|
18
|
+
async function exportResources(options, config) {
|
|
19
|
+
try {
|
|
20
|
+
const allResourceTypes = config.defaultExportTypes || RESOURCE_ORDER;
|
|
21
|
+
const outputPath = options.output || 'ark-export.yaml';
|
|
22
|
+
let resourceTypes = options.types
|
|
23
|
+
? (options.types.split(','))
|
|
24
|
+
: allResourceTypes;
|
|
25
|
+
// ensure that we get resources in the correct order; e.g. agents before teams that use the agents
|
|
26
|
+
resourceTypes.sort((a, b) => {
|
|
27
|
+
return RESOURCE_ORDER.indexOf(a) - RESOURCE_ORDER.indexOf(b);
|
|
28
|
+
});
|
|
29
|
+
output.info(`exporting ark resources to ${outputPath}...`);
|
|
30
|
+
const allResources = [];
|
|
31
|
+
let allResourceCount = 0;
|
|
32
|
+
for (const resourceType of resourceTypes) {
|
|
33
|
+
if (!RESOURCE_ORDER.includes(resourceType)) {
|
|
34
|
+
output.warning(`unknown resource type: ${resourceType}, skipping`);
|
|
35
|
+
continue;
|
|
36
|
+
}
|
|
37
|
+
output.info(`fetching ${resourceType}...`);
|
|
38
|
+
const resources = await listResources(resourceType, {
|
|
39
|
+
namespace: options.namespace,
|
|
40
|
+
labels: options.labels
|
|
41
|
+
});
|
|
42
|
+
const resourceCount = resources.length;
|
|
43
|
+
if (resources.length > 0) {
|
|
44
|
+
output.success(`found ${resourceCount} ${resourceType}`);
|
|
45
|
+
allResources.push(...resources);
|
|
46
|
+
allResourceCount += resourceCount;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
if (allResourceCount === 0) {
|
|
50
|
+
output.warning('no resources found to export');
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
const yamlContent = allResources.map((resource) => yaml.stringify(resource)).join("\n---\n");
|
|
54
|
+
await fs.writeFile(outputPath, yamlContent, 'utf-8');
|
|
55
|
+
output.success(`exported ${allResourceCount} resources to ${outputPath}`);
|
|
56
|
+
}
|
|
57
|
+
catch (error) {
|
|
58
|
+
output.error('export failed:', error instanceof Error ? error.message : error);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
export function createExportCommand(config) {
|
|
62
|
+
const exportCommand = new Command('export');
|
|
63
|
+
exportCommand
|
|
64
|
+
.description('export ARK resources to a file')
|
|
65
|
+
.option('-o, --output <file>', 'output file path', 'ark-export.yaml')
|
|
66
|
+
.option('-n, --namespace <namespace>', 'namespace to export from')
|
|
67
|
+
.option('-t, --types <types>', 'comma-separated list of resource types to export')
|
|
68
|
+
.option('-l, --labels <labels>', 'label selector to filter resources')
|
|
69
|
+
.action(async (options) => {
|
|
70
|
+
await exportResources(options, config);
|
|
71
|
+
});
|
|
72
|
+
return exportCommand;
|
|
73
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
import { jest } from '@jest/globals';
|
|
2
|
+
import { Command } from 'commander';
|
|
3
|
+
const mockExeca = jest.fn();
|
|
4
|
+
jest.unstable_mockModule('execa', () => ({
|
|
5
|
+
execa: mockExeca,
|
|
6
|
+
}));
|
|
7
|
+
const mockWriteFile = jest.fn();
|
|
8
|
+
jest.unstable_mockModule('fs/promises', () => ({
|
|
9
|
+
writeFile: mockWriteFile,
|
|
10
|
+
}));
|
|
11
|
+
const mockOutput = {
|
|
12
|
+
info: jest.fn(),
|
|
13
|
+
success: jest.fn(),
|
|
14
|
+
warning: jest.fn(),
|
|
15
|
+
error: jest.fn(),
|
|
16
|
+
};
|
|
17
|
+
jest.unstable_mockModule('../../lib/output.js', () => ({
|
|
18
|
+
default: mockOutput,
|
|
19
|
+
}));
|
|
20
|
+
const mockExit = jest.spyOn(process, 'exit').mockImplementation((() => {
|
|
21
|
+
throw new Error('process.exit called');
|
|
22
|
+
}));
|
|
23
|
+
const mockKubectlGetResponse = {
|
|
24
|
+
"apiVersion": "v1",
|
|
25
|
+
"items": [{ "spec": "foo" }],
|
|
26
|
+
"kind": "List",
|
|
27
|
+
"metadata": {
|
|
28
|
+
"resourceVersion": ""
|
|
29
|
+
}
|
|
30
|
+
};
|
|
31
|
+
const { createExportCommand } = await import('./index.js');
|
|
32
|
+
describe('export command', () => {
|
|
33
|
+
const mockConfig = {};
|
|
34
|
+
beforeEach(() => {
|
|
35
|
+
jest.clearAllMocks();
|
|
36
|
+
});
|
|
37
|
+
it('should create export command with correct description', () => {
|
|
38
|
+
const command = createExportCommand(mockConfig);
|
|
39
|
+
expect(command).toBeInstanceOf(Command);
|
|
40
|
+
expect(command.name()).toBe('export');
|
|
41
|
+
expect(command.description()).toBe('export ARK resources to a file');
|
|
42
|
+
});
|
|
43
|
+
it('should export all resource types by default', async () => {
|
|
44
|
+
mockExeca.mockResolvedValue({
|
|
45
|
+
stdout: JSON.stringify(mockKubectlGetResponse),
|
|
46
|
+
});
|
|
47
|
+
mockWriteFile.mockResolvedValue(undefined);
|
|
48
|
+
const command = createExportCommand(mockConfig);
|
|
49
|
+
await command.parseAsync(['node', 'test', '-o', 'test.yaml']);
|
|
50
|
+
const expectedResourceTypes = [
|
|
51
|
+
'secrets',
|
|
52
|
+
'tools',
|
|
53
|
+
'models',
|
|
54
|
+
'agents',
|
|
55
|
+
'teams',
|
|
56
|
+
'evaluators',
|
|
57
|
+
'mcpservers',
|
|
58
|
+
'a2aservers',
|
|
59
|
+
];
|
|
60
|
+
expect(mockExeca).toHaveBeenCalledTimes(expectedResourceTypes.length);
|
|
61
|
+
for (const resourceType of expectedResourceTypes) {
|
|
62
|
+
expect(mockExeca).toHaveBeenCalledWith('kubectl', expect.arrayContaining(['get', resourceType, '-o', 'json']), expect.any(Object));
|
|
63
|
+
expect(mockOutput.success).toHaveBeenCalledWith(`found 1 ${resourceType}`);
|
|
64
|
+
}
|
|
65
|
+
expect(mockWriteFile).toHaveBeenCalledTimes(1);
|
|
66
|
+
});
|
|
67
|
+
it('should export types specified in config in dependency order', async () => {
|
|
68
|
+
mockExeca.mockResolvedValue({
|
|
69
|
+
stdout: JSON.stringify(mockKubectlGetResponse),
|
|
70
|
+
});
|
|
71
|
+
mockWriteFile.mockResolvedValue(undefined);
|
|
72
|
+
const newDefaultTypes = ['teams', 'agents'];
|
|
73
|
+
const modifiedConfig = { defaultExportTypes: newDefaultTypes };
|
|
74
|
+
const command = createExportCommand(modifiedConfig);
|
|
75
|
+
await command.parseAsync(['node', 'test', '-o', 'test.yaml']);
|
|
76
|
+
expect(mockExeca.mock.calls).toEqual([
|
|
77
|
+
['kubectl', expect.arrayContaining(['get', 'agents']), expect.any(Object)],
|
|
78
|
+
['kubectl', expect.arrayContaining(['get', 'teams']), expect.any(Object)],
|
|
79
|
+
]);
|
|
80
|
+
expect(mockWriteFile).toHaveBeenCalledTimes(1);
|
|
81
|
+
});
|
|
82
|
+
it('should filter by resource types when specified and export in order', async () => {
|
|
83
|
+
mockExeca.mockResolvedValue({
|
|
84
|
+
stdout: JSON.stringify(mockKubectlGetResponse),
|
|
85
|
+
});
|
|
86
|
+
mockWriteFile.mockResolvedValue(undefined);
|
|
87
|
+
const command = createExportCommand(mockConfig);
|
|
88
|
+
await command.parseAsync([
|
|
89
|
+
'node',
|
|
90
|
+
'test',
|
|
91
|
+
'-t',
|
|
92
|
+
'agents,models',
|
|
93
|
+
'-o',
|
|
94
|
+
'test.yaml',
|
|
95
|
+
]);
|
|
96
|
+
expect(mockExeca.mock.calls).toEqual([
|
|
97
|
+
['kubectl', expect.arrayContaining(['get', 'models']), expect.any(Object)],
|
|
98
|
+
['kubectl', expect.arrayContaining(['get', 'agents']), expect.any(Object)],
|
|
99
|
+
]);
|
|
100
|
+
});
|
|
101
|
+
it('should use namespace filter when specified', async () => {
|
|
102
|
+
mockExeca.mockResolvedValue({
|
|
103
|
+
stdout: JSON.stringify(mockKubectlGetResponse),
|
|
104
|
+
});
|
|
105
|
+
mockWriteFile.mockResolvedValue(undefined);
|
|
106
|
+
const command = createExportCommand(mockConfig);
|
|
107
|
+
await command.parseAsync([
|
|
108
|
+
'node',
|
|
109
|
+
'test',
|
|
110
|
+
'-n',
|
|
111
|
+
'custom-namespace',
|
|
112
|
+
'-t',
|
|
113
|
+
'agents',
|
|
114
|
+
'-o',
|
|
115
|
+
'test.yaml',
|
|
116
|
+
]);
|
|
117
|
+
expect(mockExeca).toHaveBeenCalledWith('kubectl', expect.arrayContaining(['-n', 'custom-namespace']), expect.any(Object));
|
|
118
|
+
expect(mockWriteFile).toHaveBeenCalledTimes(1);
|
|
119
|
+
});
|
|
120
|
+
it('should use label selector when specified', async () => {
|
|
121
|
+
mockExeca.mockResolvedValue({
|
|
122
|
+
stdout: JSON.stringify(mockKubectlGetResponse),
|
|
123
|
+
});
|
|
124
|
+
mockWriteFile.mockResolvedValue(undefined);
|
|
125
|
+
const command = createExportCommand(mockConfig);
|
|
126
|
+
await command.parseAsync([
|
|
127
|
+
'node',
|
|
128
|
+
'test',
|
|
129
|
+
'-l',
|
|
130
|
+
'app=test',
|
|
131
|
+
'-t',
|
|
132
|
+
'agents',
|
|
133
|
+
'-o',
|
|
134
|
+
'test.yaml',
|
|
135
|
+
]);
|
|
136
|
+
expect(mockExeca).toHaveBeenCalledWith('kubectl', expect.arrayContaining(['-l', 'app=test']), expect.any(Object));
|
|
137
|
+
expect(mockWriteFile).toHaveBeenCalledTimes(1);
|
|
138
|
+
});
|
|
139
|
+
it('fails if kubectl get fails for a resource type', async () => {
|
|
140
|
+
mockExeca.mockRejectedValue('Export broke');
|
|
141
|
+
const command = createExportCommand(mockConfig);
|
|
142
|
+
await command.parseAsync(['node', 'test']);
|
|
143
|
+
expect(mockOutput.error).toHaveBeenCalledWith('export failed:', 'Export broke');
|
|
144
|
+
});
|
|
145
|
+
});
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { Command } from 'commander';
|
|
2
|
+
import { execa } from 'execa';
|
|
3
|
+
import output from '../../lib/output.js';
|
|
4
|
+
async function importResources(filepath) {
|
|
5
|
+
try {
|
|
6
|
+
output.info(`importing ark resources from ${filepath}...`);
|
|
7
|
+
const args = ['create', '-f', filepath];
|
|
8
|
+
const result = await execa('kubectl', args, {
|
|
9
|
+
stdio: 'pipe',
|
|
10
|
+
});
|
|
11
|
+
output.success(`imported resources from ${filepath}`);
|
|
12
|
+
}
|
|
13
|
+
catch (error) {
|
|
14
|
+
output.error('import failed:', error instanceof Error ? error.message : error);
|
|
15
|
+
process.exit(1);
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
export function createImportCommand(_) {
|
|
19
|
+
const importCommand = new Command('import');
|
|
20
|
+
importCommand
|
|
21
|
+
.description('import ARK resources from a file')
|
|
22
|
+
.argument('<filepath>', 'input file path')
|
|
23
|
+
.action(async (filepath) => {
|
|
24
|
+
await importResources(filepath);
|
|
25
|
+
});
|
|
26
|
+
return importCommand;
|
|
27
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { jest } from '@jest/globals';
|
|
2
|
+
import { Command } from 'commander';
|
|
3
|
+
const mockExeca = jest.fn();
|
|
4
|
+
jest.unstable_mockModule('execa', () => ({
|
|
5
|
+
execa: mockExeca,
|
|
6
|
+
}));
|
|
7
|
+
const mockOutput = {
|
|
8
|
+
info: jest.fn(),
|
|
9
|
+
success: jest.fn(),
|
|
10
|
+
warning: jest.fn(),
|
|
11
|
+
error: jest.fn(),
|
|
12
|
+
};
|
|
13
|
+
jest.unstable_mockModule('../../lib/output.js', () => ({
|
|
14
|
+
default: mockOutput,
|
|
15
|
+
}));
|
|
16
|
+
const mockExit = jest.spyOn(process, 'exit').mockImplementation((() => {
|
|
17
|
+
throw new Error('process.exit called');
|
|
18
|
+
}));
|
|
19
|
+
const { createImportCommand } = await import('./index.js');
|
|
20
|
+
describe('import command', () => {
|
|
21
|
+
const mockConfig = {};
|
|
22
|
+
beforeEach(() => {
|
|
23
|
+
jest.clearAllMocks();
|
|
24
|
+
});
|
|
25
|
+
it('should create import command with correct description', () => {
|
|
26
|
+
const command = createImportCommand(mockConfig);
|
|
27
|
+
expect(command).toBeInstanceOf(Command);
|
|
28
|
+
expect(command.name()).toBe('import');
|
|
29
|
+
expect(command.description()).toBe('import ARK resources from a file');
|
|
30
|
+
});
|
|
31
|
+
it('should use kubectl to import', async () => {
|
|
32
|
+
mockExeca.mockResolvedValue({
|
|
33
|
+
stdout: JSON.stringify({ items: [] }),
|
|
34
|
+
});
|
|
35
|
+
const command = createImportCommand(mockConfig);
|
|
36
|
+
await command.parseAsync(['node', 'test', 'test.yaml']);
|
|
37
|
+
expect(mockExeca).toHaveBeenCalledWith('kubectl', ['create', '-f', 'test.yaml'], expect.any(Object));
|
|
38
|
+
});
|
|
39
|
+
it('exits with error when kubectl create has error', async () => {
|
|
40
|
+
mockExeca.mockRejectedValue('Import broke');
|
|
41
|
+
const command = createImportCommand(mockConfig);
|
|
42
|
+
await expect(command.parseAsync(['node', 'test', 'test.yaml'])).rejects.toThrow('process.exit called');
|
|
43
|
+
expect(mockOutput.error).toHaveBeenCalledWith('import failed:', 'Import broke');
|
|
44
|
+
expect(mockExit).toHaveBeenCalledWith(1);
|
|
45
|
+
});
|
|
46
|
+
});
|
|
@@ -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, arkDependencies, arkServices, } from '../../arkServices.js';
|
|
8
|
-
import { isMarketplaceService,
|
|
8
|
+
import { isMarketplaceService, getMarketplaceItem, getAllMarketplaceServices, getAllMarketplaceAgents, } from '../../marketplaceServices.js';
|
|
9
9
|
import { printNextSteps } from '../../lib/nextSteps.js';
|
|
10
10
|
import ora from 'ora';
|
|
11
11
|
import { waitForServicesReady, } from '../../lib/waitForReady.js';
|
|
@@ -42,20 +42,30 @@ export async function installArk(config, serviceName, options = {}) {
|
|
|
42
42
|
console.log(); // Add blank line after cluster info
|
|
43
43
|
// If a specific service is requested, install only that service
|
|
44
44
|
if (serviceName) {
|
|
45
|
-
// Check if it's a marketplace
|
|
45
|
+
// Check if it's a marketplace item
|
|
46
46
|
if (isMarketplaceService(serviceName)) {
|
|
47
|
-
const
|
|
48
|
-
const service = getMarketplaceService(marketplaceServiceName);
|
|
47
|
+
const service = await getMarketplaceItem(serviceName);
|
|
49
48
|
if (!service) {
|
|
50
|
-
output.error(`marketplace
|
|
51
|
-
output.info('available marketplace
|
|
52
|
-
const marketplaceServices = getAllMarketplaceServices();
|
|
53
|
-
|
|
54
|
-
|
|
49
|
+
output.error(`marketplace item '${serviceName}' not found`);
|
|
50
|
+
output.info('available marketplace items:');
|
|
51
|
+
const marketplaceServices = await getAllMarketplaceServices();
|
|
52
|
+
if (marketplaceServices) {
|
|
53
|
+
for (const name of Object.keys(marketplaceServices)) {
|
|
54
|
+
output.info(` marketplace/services/${name}`);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
const marketplaceAgents = await getAllMarketplaceAgents();
|
|
58
|
+
if (marketplaceAgents) {
|
|
59
|
+
for (const name of Object.keys(marketplaceAgents)) {
|
|
60
|
+
output.info(` marketplace/agents/${name}`);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
if (!marketplaceServices && !marketplaceAgents) {
|
|
64
|
+
output.warning('Marketplace unavailable');
|
|
55
65
|
}
|
|
56
66
|
process.exit(1);
|
|
57
67
|
}
|
|
58
|
-
output.info(`installing marketplace
|
|
68
|
+
output.info(`installing marketplace item ${service.name}...`);
|
|
59
69
|
try {
|
|
60
70
|
await installService(service, options.verbose);
|
|
61
71
|
output.success(`${service.name} installed successfully`);
|