@agents-at-scale/ark 0.1.47 → 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 +2 -0
- package/dist/arkServices.js +9 -7
- package/dist/commands/chat/index.js +1 -1
- package/dist/commands/completion/index.js +17 -1
- 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/memory/index.js +9 -4
- package/dist/commands/query/index.js +2 -0
- 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 +1 -0
- package/dist/lib/chatClient.js +7 -1
- package/dist/lib/config.d.ts +3 -1
- package/dist/lib/config.js +21 -7
- package/dist/lib/config.spec.js +10 -0
- package/dist/lib/executeQuery.d.ts +1 -0
- package/dist/lib/executeQuery.js +13 -2
- package/dist/lib/kubectl.d.ts +2 -0
- package/dist/lib/kubectl.js +6 -0
- package/dist/lib/kubectl.spec.js +16 -0
- package/dist/lib/types.d.ts +1 -0
- package/dist/ui/MainMenu.js +6 -2
- package/package.json +4 -2
- package/dist/ui/AgentSelector.d.ts +0 -8
- package/dist/ui/AgentSelector.js +0 -53
- package/dist/ui/ModelSelector.d.ts +0 -8
- package/dist/ui/ModelSelector.js +0 -53
- package/dist/ui/TeamSelector.d.ts +0 -8
- package/dist/ui/TeamSelector.js +0 -55
- package/dist/ui/ToolSelector.d.ts +0 -8
- package/dist/ui/ToolSelector.js +0 -53
|
@@ -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
|
package/dist/arkServices.js
CHANGED
|
@@ -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',
|
|
@@ -171,7 +171,9 @@ function applyConfigOverrides(defaults) {
|
|
|
171
171
|
const overrides = config?.services || {};
|
|
172
172
|
const result = {};
|
|
173
173
|
for (const [key, service] of Object.entries(defaults)) {
|
|
174
|
-
|
|
174
|
+
const override = overrides[key];
|
|
175
|
+
result[key] =
|
|
176
|
+
override && typeof override === 'object' ? { ...service, ...override } : service;
|
|
175
177
|
}
|
|
176
178
|
return result;
|
|
177
179
|
}
|
|
@@ -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,6 +84,11 @@ _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
93
|
opts="marketplace/services/phoenix marketplace/services/langfuse marketplace/agents/noah"
|
|
89
94
|
COMPREPLY=( $(compgen -W "\${opts}" -- \${cur}) )
|
|
@@ -155,9 +160,13 @@ _ark() {
|
|
|
155
160
|
'config[Configuration management]' \\
|
|
156
161
|
'dashboard[Open ARK dashboard]' \\
|
|
157
162
|
'docs[Open ARK documentation]' \\
|
|
163
|
+
'evaluation[Execute evaluations against evaluators]' \\
|
|
164
|
+
'export[Export ARK resources to a file]' \\
|
|
158
165
|
'generate[Generate ARK resources]' \\
|
|
166
|
+
'import[Import ARK resources from a file]' \\
|
|
159
167
|
'install[Install ARK services]' \\
|
|
160
168
|
'marketplace[Manage marketplace services]' \\
|
|
169
|
+
'memory[Manage memory sessions and queries]' \\
|
|
161
170
|
'models[List available models]' \\
|
|
162
171
|
'queries[Manage query resources]' \\
|
|
163
172
|
'query[Execute a single query]' \\
|
|
@@ -225,6 +234,13 @@ _ark() {
|
|
|
225
234
|
'list[List available marketplace services]' \\
|
|
226
235
|
'ls[List available marketplace services]'
|
|
227
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
|
+
;;
|
|
228
244
|
install)
|
|
229
245
|
_values 'services to install' \\
|
|
230
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
|
+
});
|
|
@@ -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') {
|
|
@@ -11,6 +11,7 @@ export function createQueryCommand(config) {
|
|
|
11
11
|
.option('-o, --output <format>', 'Output format: yaml, json, name or events (shows structured event data)')
|
|
12
12
|
.option('--timeout <timeout>', 'Query timeout (e.g., 30s, 5m, 1h)')
|
|
13
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')
|
|
14
15
|
.action(async (target, message, options) => {
|
|
15
16
|
const parsed = parseTarget(target);
|
|
16
17
|
if (!parsed) {
|
|
@@ -24,6 +25,7 @@ export function createQueryCommand(config) {
|
|
|
24
25
|
outputFormat: options.output,
|
|
25
26
|
timeout: options.timeout || config.queryTimeout,
|
|
26
27
|
sessionId: options.sessionId,
|
|
28
|
+
conversationId: options.conversationId,
|
|
27
29
|
});
|
|
28
30
|
});
|
|
29
31
|
return queryCommand;
|
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;
|