@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
|
@@ -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,11 +17,17 @@ 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
25
|
if (config.sessionId) {
|
|
23
26
|
params.metadata.sessionId = config.sessionId;
|
|
24
27
|
}
|
|
28
|
+
if (config.conversationId) {
|
|
29
|
+
params.metadata.conversationId = config.conversationId;
|
|
30
|
+
}
|
|
25
31
|
if (config.queryTimeout) {
|
|
26
32
|
params.metadata.timeout = config.queryTimeout;
|
|
27
33
|
}
|
package/dist/lib/config.d.ts
CHANGED
|
@@ -12,9 +12,11 @@ export interface ArkConfig {
|
|
|
12
12
|
chat?: ChatConfig;
|
|
13
13
|
marketplace?: MarketplaceConfig;
|
|
14
14
|
services?: {
|
|
15
|
-
|
|
15
|
+
reusePortForwards?: boolean;
|
|
16
|
+
[serviceName: string]: Partial<ArkService> | boolean | undefined;
|
|
16
17
|
};
|
|
17
18
|
queryTimeout?: string;
|
|
19
|
+
defaultExportTypes?: string[];
|
|
18
20
|
clusterInfo?: ClusterInfo;
|
|
19
21
|
}
|
|
20
22
|
/**
|
package/dist/lib/config.js
CHANGED
|
@@ -20,6 +20,9 @@ export function loadConfig() {
|
|
|
20
20
|
repoUrl: 'https://github.com/mckinsey/agents-at-scale-marketplace',
|
|
21
21
|
registry: 'oci://ghcr.io/mckinsey/agents-at-scale-marketplace/charts',
|
|
22
22
|
},
|
|
23
|
+
services: {
|
|
24
|
+
reusePortForwards: false,
|
|
25
|
+
},
|
|
23
26
|
};
|
|
24
27
|
// Load user config from home directory
|
|
25
28
|
const userConfigPath = path.join(os.homedir(), '.arkrc.yaml');
|
|
@@ -48,9 +51,7 @@ export function loadConfig() {
|
|
|
48
51
|
// Apply environment variable overrides
|
|
49
52
|
if (process.env.ARK_CHAT_STREAMING !== undefined) {
|
|
50
53
|
config.chat = config.chat || {};
|
|
51
|
-
config.chat.streaming =
|
|
52
|
-
process.env.ARK_CHAT_STREAMING === '1' ||
|
|
53
|
-
process.env.ARK_CHAT_STREAMING === 'true';
|
|
54
|
+
config.chat.streaming = process.env.ARK_CHAT_STREAMING === '1';
|
|
54
55
|
}
|
|
55
56
|
if (process.env.ARK_CHAT_OUTPUT_FORMAT !== undefined) {
|
|
56
57
|
config.chat = config.chat || {};
|
|
@@ -70,6 +71,11 @@ export function loadConfig() {
|
|
|
70
71
|
config.marketplace = config.marketplace || {};
|
|
71
72
|
config.marketplace.registry = process.env.ARK_MARKETPLACE_REGISTRY;
|
|
72
73
|
}
|
|
74
|
+
if (process.env.ARK_SERVICES_REUSE_PORT_FORWARDS !== undefined) {
|
|
75
|
+
config.services = config.services || {};
|
|
76
|
+
config.services.reusePortForwards =
|
|
77
|
+
process.env.ARK_SERVICES_REUSE_PORT_FORWARDS === '1';
|
|
78
|
+
}
|
|
73
79
|
return config;
|
|
74
80
|
}
|
|
75
81
|
/**
|
|
@@ -96,16 +102,24 @@ function mergeConfig(target, source) {
|
|
|
96
102
|
}
|
|
97
103
|
if (source.services) {
|
|
98
104
|
target.services = target.services || {};
|
|
105
|
+
if (source.services.reusePortForwards !== undefined) {
|
|
106
|
+
target.services.reusePortForwards = source.services.reusePortForwards;
|
|
107
|
+
}
|
|
99
108
|
for (const [serviceName, overrides] of Object.entries(source.services)) {
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
109
|
+
if (serviceName !== 'reusePortForwards' && typeof overrides === 'object') {
|
|
110
|
+
target.services[serviceName] = {
|
|
111
|
+
...target.services[serviceName],
|
|
112
|
+
...overrides,
|
|
113
|
+
};
|
|
114
|
+
}
|
|
104
115
|
}
|
|
105
116
|
}
|
|
106
117
|
if (source.queryTimeout !== undefined) {
|
|
107
118
|
target.queryTimeout = source.queryTimeout;
|
|
108
119
|
}
|
|
120
|
+
if (source.defaultExportTypes) {
|
|
121
|
+
target.defaultExportTypes = source.defaultExportTypes;
|
|
122
|
+
}
|
|
109
123
|
}
|
|
110
124
|
/**
|
|
111
125
|
* Get the paths checked for config files
|
package/dist/lib/config.spec.js
CHANGED
|
@@ -39,6 +39,9 @@ describe('config', () => {
|
|
|
39
39
|
repoUrl: 'https://github.com/mckinsey/agents-at-scale-marketplace',
|
|
40
40
|
registry: 'oci://ghcr.io/mckinsey/agents-at-scale-marketplace/charts',
|
|
41
41
|
},
|
|
42
|
+
services: {
|
|
43
|
+
reusePortForwards: false,
|
|
44
|
+
},
|
|
42
45
|
});
|
|
43
46
|
});
|
|
44
47
|
it('loads and merges configs in order: defaults, user, project', () => {
|
|
@@ -77,6 +80,13 @@ describe('config', () => {
|
|
|
77
80
|
const config = loadConfig();
|
|
78
81
|
expect(config.queryTimeout).toBe('30m');
|
|
79
82
|
});
|
|
83
|
+
it('loads defaultExportTypes from config file', () => {
|
|
84
|
+
mockFs.existsSync.mockReturnValue(true);
|
|
85
|
+
mockFs.readFileSync.mockReturnValue('yaml');
|
|
86
|
+
mockYaml.parse.mockReturnValue({ defaultExportTypes: ['agents', 'teams'] });
|
|
87
|
+
const config = loadConfig();
|
|
88
|
+
expect(config.defaultExportTypes).toEqual(['agents', 'teams']);
|
|
89
|
+
});
|
|
80
90
|
it('ARK_QUERY_TIMEOUT environment variable overrides config', () => {
|
|
81
91
|
mockFs.existsSync.mockReturnValue(true);
|
|
82
92
|
mockFs.readFileSync.mockReturnValue('yaml');
|
package/dist/lib/executeQuery.js
CHANGED
|
@@ -8,6 +8,7 @@ import { ExitCodes } from './errors.js';
|
|
|
8
8
|
import { ArkApiProxy } from './arkApiProxy.js';
|
|
9
9
|
import { ChatClient } from './chatClient.js';
|
|
10
10
|
import { watchEventsLive } from './kubectl.js';
|
|
11
|
+
import { loadConfig } from './config.js';
|
|
11
12
|
export async function executeQuery(options) {
|
|
12
13
|
if (options.outputFormat) {
|
|
13
14
|
return executeQueryWithFormat(options);
|
|
@@ -15,7 +16,8 @@ export async function executeQuery(options) {
|
|
|
15
16
|
let arkApiProxy;
|
|
16
17
|
const spinner = ora('Connecting to Ark API...').start();
|
|
17
18
|
try {
|
|
18
|
-
|
|
19
|
+
const config = loadConfig();
|
|
20
|
+
arkApiProxy = new ArkApiProxy(undefined, config.services?.reusePortForwards ?? false);
|
|
19
21
|
const arkApiClient = await arkApiProxy.start();
|
|
20
22
|
const chatClient = new ChatClient(arkApiClient);
|
|
21
23
|
spinner.text = 'Executing query...';
|
|
@@ -29,7 +31,13 @@ export async function executeQuery(options) {
|
|
|
29
31
|
let headerShown = false;
|
|
30
32
|
let firstOutput = true;
|
|
31
33
|
const sessionId = options.sessionId || process.env.ARK_SESSION_ID;
|
|
32
|
-
|
|
34
|
+
const conversationId = options.conversationId || process.env.ARK_CONVERSATION_ID;
|
|
35
|
+
await chatClient.sendMessage(targetId, messages, {
|
|
36
|
+
streamingEnabled: true,
|
|
37
|
+
sessionId,
|
|
38
|
+
conversationId,
|
|
39
|
+
queryTimeout: options.timeout,
|
|
40
|
+
}, (chunk, toolCalls, arkMetadata) => {
|
|
33
41
|
if (firstOutput) {
|
|
34
42
|
spinner.stop();
|
|
35
43
|
firstOutput = false;
|
|
@@ -105,6 +113,9 @@ async function executeQueryWithFormat(options) {
|
|
|
105
113
|
...((options.sessionId || process.env.ARK_SESSION_ID) && {
|
|
106
114
|
sessionId: options.sessionId || process.env.ARK_SESSION_ID,
|
|
107
115
|
}),
|
|
116
|
+
...((options.conversationId || process.env.ARK_CONVERSATION_ID) && {
|
|
117
|
+
conversationId: options.conversationId || process.env.ARK_CONVERSATION_ID,
|
|
118
|
+
}),
|
|
108
119
|
targets: [
|
|
109
120
|
{
|
|
110
121
|
type: options.targetType,
|
package/dist/lib/kubectl.d.ts
CHANGED
|
@@ -6,6 +6,8 @@ interface K8sResource {
|
|
|
6
6
|
}
|
|
7
7
|
export declare function getResource<T extends K8sResource>(resourceType: string, name: string): Promise<T>;
|
|
8
8
|
export declare function listResources<T extends K8sResource>(resourceType: string, options?: {
|
|
9
|
+
namespace?: string;
|
|
10
|
+
labels?: string;
|
|
9
11
|
sortBy?: string;
|
|
10
12
|
}): Promise<T[]>;
|
|
11
13
|
export declare function deleteResource(resourceType: string, name?: string, options?: {
|
package/dist/lib/kubectl.js
CHANGED
|
@@ -25,6 +25,12 @@ export async function listResources(resourceType, options) {
|
|
|
25
25
|
if (options?.sortBy) {
|
|
26
26
|
args.push(`--sort-by=${options.sortBy}`);
|
|
27
27
|
}
|
|
28
|
+
if (options?.namespace) {
|
|
29
|
+
args.push('-n', options.namespace);
|
|
30
|
+
}
|
|
31
|
+
if (options?.labels) {
|
|
32
|
+
args.push('-l', options.labels);
|
|
33
|
+
}
|
|
28
34
|
args.push('-o', 'json');
|
|
29
35
|
const result = await execa('kubectl', args, { stdio: 'pipe' });
|
|
30
36
|
const data = JSON.parse(result.stdout);
|
package/dist/lib/kubectl.spec.js
CHANGED
|
@@ -152,6 +152,22 @@ describe('kubectl', () => {
|
|
|
152
152
|
'json',
|
|
153
153
|
], { stdio: 'pipe' });
|
|
154
154
|
});
|
|
155
|
+
it('should pass namespace and label filters', async () => {
|
|
156
|
+
const result = await listResources('queries', {
|
|
157
|
+
labels: 'app=test',
|
|
158
|
+
namespace: 'foo'
|
|
159
|
+
});
|
|
160
|
+
expect(mockExeca).toHaveBeenCalledWith('kubectl', [
|
|
161
|
+
'get',
|
|
162
|
+
'queries',
|
|
163
|
+
'-n',
|
|
164
|
+
'foo',
|
|
165
|
+
'-l',
|
|
166
|
+
'app=test',
|
|
167
|
+
'-o',
|
|
168
|
+
'json',
|
|
169
|
+
], { stdio: 'pipe' });
|
|
170
|
+
});
|
|
155
171
|
it('should handle kubectl errors when listing resources', async () => {
|
|
156
172
|
mockExeca.mockRejectedValue(new Error('kubectl connection error'));
|
|
157
173
|
await expect(listResources('queries')).rejects.toThrow('kubectl connection error');
|
package/dist/lib/types.d.ts
CHANGED
package/dist/ui/MainMenu.js
CHANGED
|
@@ -155,9 +155,11 @@ const MainMenu = ({ config }) => {
|
|
|
155
155
|
// Import and start ChatUI in the same process
|
|
156
156
|
const { render } = await import('ink');
|
|
157
157
|
const { ArkApiProxy } = await import('../lib/arkApiProxy.js');
|
|
158
|
+
const { loadConfig } = await import('../lib/config.js');
|
|
158
159
|
const ChatUI = (await import('../components/ChatUI.js')).default;
|
|
159
160
|
try {
|
|
160
|
-
const
|
|
161
|
+
const config = loadConfig();
|
|
162
|
+
const proxy = new ArkApiProxy(undefined, config.services?.reusePortForwards ?? false);
|
|
161
163
|
const arkApiClient = await proxy.start();
|
|
162
164
|
// Render ChatUI as a new Ink app
|
|
163
165
|
render(_jsx(ChatUI, { arkApiClient: arkApiClient, arkApiProxy: proxy }));
|
|
@@ -211,7 +213,9 @@ const MainMenu = ({ config }) => {
|
|
|
211
213
|
// Unmount fullscreen app and clear screen.
|
|
212
214
|
await unmountInkApp();
|
|
213
215
|
const { openDashboard } = await import('../commands/dashboard/index.js');
|
|
214
|
-
await
|
|
216
|
+
const { loadConfig } = await import('../lib/config.js');
|
|
217
|
+
const config = loadConfig();
|
|
218
|
+
await openDashboard(config);
|
|
215
219
|
break;
|
|
216
220
|
}
|
|
217
221
|
case 'status': {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@agents-at-scale/ark",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.49",
|
|
4
4
|
"description": "Ark CLI - Interactive terminal interface for ARK agents",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"type": "module",
|
|
@@ -9,7 +9,8 @@
|
|
|
9
9
|
},
|
|
10
10
|
"files": [
|
|
11
11
|
"dist",
|
|
12
|
-
"templates"
|
|
12
|
+
"templates",
|
|
13
|
+
".arkrc.template.yaml"
|
|
13
14
|
],
|
|
14
15
|
"scripts": {
|
|
15
16
|
"build": "npm run copy-templates && tsc && chmod +x dist/index.js",
|
|
@@ -48,6 +49,7 @@
|
|
|
48
49
|
"commander": "^12.1.0",
|
|
49
50
|
"debug": "^4.4.1",
|
|
50
51
|
"execa": "^9.6.0",
|
|
52
|
+
"find-process": "^1.4.7",
|
|
51
53
|
"ink": "^6.0.1",
|
|
52
54
|
"ink-select-input": "^6.2.0",
|
|
53
55
|
"ink-spinner": "^5.0.0",
|
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
import { Agent, ArkApiClient } from '../lib/arkApiClient.js';
|
|
2
|
-
interface AgentSelectorProps {
|
|
3
|
-
arkApiClient: ArkApiClient;
|
|
4
|
-
onSelect: (agent: Agent) => void;
|
|
5
|
-
onExit: () => void;
|
|
6
|
-
}
|
|
7
|
-
export declare function AgentSelector({ arkApiClient, onSelect, onExit, }: AgentSelectorProps): import("react/jsx-runtime").JSX.Element;
|
|
8
|
-
export {};
|
package/dist/ui/AgentSelector.js
DELETED
|
@@ -1,53 +0,0 @@
|
|
|
1
|
-
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
import { useState, useEffect } from 'react';
|
|
3
|
-
import { Box, Text, useInput } from 'ink';
|
|
4
|
-
export function AgentSelector({ arkApiClient, onSelect, onExit, }) {
|
|
5
|
-
const [selectedIndex, setSelectedIndex] = useState(0);
|
|
6
|
-
const [agents, setAgents] = useState([]);
|
|
7
|
-
const [loading, setLoading] = useState(true);
|
|
8
|
-
const [error, setError] = useState(null);
|
|
9
|
-
useEffect(() => {
|
|
10
|
-
arkApiClient
|
|
11
|
-
.getAgents()
|
|
12
|
-
.then((fetchedAgents) => {
|
|
13
|
-
setAgents(fetchedAgents);
|
|
14
|
-
setLoading(false);
|
|
15
|
-
})
|
|
16
|
-
.catch((err) => {
|
|
17
|
-
setError(err.message || 'Failed to fetch agents');
|
|
18
|
-
setLoading(false);
|
|
19
|
-
});
|
|
20
|
-
}, [arkApiClient]);
|
|
21
|
-
useInput((input, key) => {
|
|
22
|
-
if (key.escape) {
|
|
23
|
-
onExit();
|
|
24
|
-
}
|
|
25
|
-
else if (key.upArrow || input === 'k') {
|
|
26
|
-
setSelectedIndex((prev) => (prev === 0 ? agents.length - 1 : prev - 1));
|
|
27
|
-
}
|
|
28
|
-
else if (key.downArrow || input === 'j') {
|
|
29
|
-
setSelectedIndex((prev) => (prev === agents.length - 1 ? 0 : prev + 1));
|
|
30
|
-
}
|
|
31
|
-
else if (key.return) {
|
|
32
|
-
onSelect(agents[selectedIndex]);
|
|
33
|
-
}
|
|
34
|
-
else {
|
|
35
|
-
// Handle number keys for quick selection
|
|
36
|
-
const num = parseInt(input, 10);
|
|
37
|
-
if (!isNaN(num) && num >= 1 && num <= agents.length) {
|
|
38
|
-
onSelect(agents[num - 1]);
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
});
|
|
42
|
-
if (loading) {
|
|
43
|
-
return (_jsx(Box, { children: _jsx(Text, { children: "Loading agents..." }) }));
|
|
44
|
-
}
|
|
45
|
-
if (error) {
|
|
46
|
-
return (_jsx(Box, { children: _jsxs(Text, { color: "red", children: ["Error: ", error] }) }));
|
|
47
|
-
}
|
|
48
|
-
if (agents.length === 0) {
|
|
49
|
-
return (_jsx(Box, { children: _jsx(Text, { children: "No agents available" }) }));
|
|
50
|
-
}
|
|
51
|
-
const selectedAgent = agents[selectedIndex];
|
|
52
|
-
return (_jsxs(Box, { flexDirection: "column", borderStyle: "round", borderColor: "gray", paddingX: 2, paddingY: 1, children: [_jsx(Box, { marginBottom: 1, children: _jsx(Text, { bold: true, children: "Select Agent" }) }), _jsx(Box, { marginBottom: 1, children: _jsx(Text, { dimColor: true, children: "Choose an agent to start a conversation with" }) }), _jsx(Box, { flexDirection: "column", children: agents.map((agent, index) => (_jsx(Box, { marginBottom: 0, children: _jsxs(Text, { color: index === selectedIndex ? 'green' : undefined, children: [index === selectedIndex ? '❯ ' : ' ', index + 1, ". ", agent.name] }) }, agent.name))) }), selectedAgent.description && (_jsx(Box, { marginTop: 1, paddingLeft: 2, children: _jsx(Text, { dimColor: true, wrap: "wrap", children: selectedAgent.description }) })), _jsx(Box, { marginTop: 1, children: _jsx(Text, { dimColor: true, children: "Enter to confirm \u00B7 Number to select \u00B7 Esc to exit" }) })] }));
|
|
53
|
-
}
|
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
import { Model, ArkApiClient } from '../lib/arkApiClient.js';
|
|
2
|
-
interface ModelSelectorProps {
|
|
3
|
-
arkApiClient: ArkApiClient;
|
|
4
|
-
onSelect: (model: Model) => void;
|
|
5
|
-
onExit: () => void;
|
|
6
|
-
}
|
|
7
|
-
export declare function ModelSelector({ arkApiClient, onSelect, onExit, }: ModelSelectorProps): import("react/jsx-runtime").JSX.Element;
|
|
8
|
-
export {};
|
package/dist/ui/ModelSelector.js
DELETED
|
@@ -1,53 +0,0 @@
|
|
|
1
|
-
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
import { useState, useEffect } from 'react';
|
|
3
|
-
import { Box, Text, useInput } from 'ink';
|
|
4
|
-
export function ModelSelector({ arkApiClient, onSelect, onExit, }) {
|
|
5
|
-
const [selectedIndex, setSelectedIndex] = useState(0);
|
|
6
|
-
const [models, setModels] = useState([]);
|
|
7
|
-
const [loading, setLoading] = useState(true);
|
|
8
|
-
const [error, setError] = useState(null);
|
|
9
|
-
useEffect(() => {
|
|
10
|
-
arkApiClient
|
|
11
|
-
.getModels()
|
|
12
|
-
.then((fetchedModels) => {
|
|
13
|
-
setModels(fetchedModels);
|
|
14
|
-
setLoading(false);
|
|
15
|
-
})
|
|
16
|
-
.catch((err) => {
|
|
17
|
-
setError(err.message || 'Failed to fetch models');
|
|
18
|
-
setLoading(false);
|
|
19
|
-
});
|
|
20
|
-
}, [arkApiClient]);
|
|
21
|
-
useInput((input, key) => {
|
|
22
|
-
if (key.escape) {
|
|
23
|
-
onExit();
|
|
24
|
-
}
|
|
25
|
-
else if (key.upArrow || input === 'k') {
|
|
26
|
-
setSelectedIndex((prev) => (prev === 0 ? models.length - 1 : prev - 1));
|
|
27
|
-
}
|
|
28
|
-
else if (key.downArrow || input === 'j') {
|
|
29
|
-
setSelectedIndex((prev) => (prev === models.length - 1 ? 0 : prev + 1));
|
|
30
|
-
}
|
|
31
|
-
else if (key.return) {
|
|
32
|
-
onSelect(models[selectedIndex]);
|
|
33
|
-
}
|
|
34
|
-
else {
|
|
35
|
-
// Handle number keys for quick selection
|
|
36
|
-
const num = parseInt(input, 10);
|
|
37
|
-
if (!isNaN(num) && num >= 1 && num <= models.length) {
|
|
38
|
-
onSelect(models[num - 1]);
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
});
|
|
42
|
-
if (loading) {
|
|
43
|
-
return (_jsx(Box, { children: _jsx(Text, { children: "Loading models..." }) }));
|
|
44
|
-
}
|
|
45
|
-
if (error) {
|
|
46
|
-
return (_jsx(Box, { children: _jsxs(Text, { color: "red", children: ["Error: ", error] }) }));
|
|
47
|
-
}
|
|
48
|
-
if (models.length === 0) {
|
|
49
|
-
return (_jsx(Box, { children: _jsx(Text, { children: "No models available" }) }));
|
|
50
|
-
}
|
|
51
|
-
const selectedModel = models[selectedIndex];
|
|
52
|
-
return (_jsxs(Box, { flexDirection: "column", borderStyle: "round", borderColor: "gray", paddingX: 2, paddingY: 1, children: [_jsx(Box, { marginBottom: 1, children: _jsx(Text, { bold: true, children: "Select Model" }) }), _jsx(Box, { marginBottom: 1, children: _jsx(Text, { dimColor: true, children: "Choose a model to start a conversation with" }) }), _jsx(Box, { flexDirection: "column", children: models.map((model, index) => (_jsx(Box, { marginBottom: 0, children: _jsxs(Text, { color: index === selectedIndex ? 'green' : undefined, children: [index === selectedIndex ? '❯ ' : ' ', index + 1, ". ", model.name, model.type ? ` (${model.type})` : ''] }) }, model.name))) }), selectedModel && selectedModel.model && (_jsx(Box, { marginTop: 1, paddingLeft: 2, children: _jsxs(Text, { dimColor: true, wrap: "wrap", children: ["Model: ", selectedModel.model] }) })), _jsx(Box, { marginTop: 1, children: _jsx(Text, { dimColor: true, children: "Enter to confirm \u00B7 Number to select \u00B7 Esc to exit" }) })] }));
|
|
53
|
-
}
|
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
import { Team, ArkApiClient } from '../lib/arkApiClient.js';
|
|
2
|
-
interface TeamSelectorProps {
|
|
3
|
-
arkApiClient: ArkApiClient;
|
|
4
|
-
onSelect: (team: Team) => void;
|
|
5
|
-
onExit: () => void;
|
|
6
|
-
}
|
|
7
|
-
export declare function TeamSelector({ arkApiClient, onSelect, onExit, }: TeamSelectorProps): import("react/jsx-runtime").JSX.Element;
|
|
8
|
-
export {};
|
package/dist/ui/TeamSelector.js
DELETED
|
@@ -1,55 +0,0 @@
|
|
|
1
|
-
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
import { useState, useEffect } from 'react';
|
|
3
|
-
import { Box, Text, useInput } from 'ink';
|
|
4
|
-
export function TeamSelector({ arkApiClient, onSelect, onExit, }) {
|
|
5
|
-
const [selectedIndex, setSelectedIndex] = useState(0);
|
|
6
|
-
const [teams, setTeams] = useState([]);
|
|
7
|
-
const [loading, setLoading] = useState(true);
|
|
8
|
-
const [error, setError] = useState(null);
|
|
9
|
-
useEffect(() => {
|
|
10
|
-
arkApiClient
|
|
11
|
-
.getTeams()
|
|
12
|
-
.then((fetchedTeams) => {
|
|
13
|
-
setTeams(fetchedTeams);
|
|
14
|
-
setLoading(false);
|
|
15
|
-
})
|
|
16
|
-
.catch((err) => {
|
|
17
|
-
setError(err.message || 'Failed to fetch teams');
|
|
18
|
-
setLoading(false);
|
|
19
|
-
});
|
|
20
|
-
}, [arkApiClient]);
|
|
21
|
-
useInput((input, key) => {
|
|
22
|
-
if (key.escape) {
|
|
23
|
-
onExit();
|
|
24
|
-
}
|
|
25
|
-
else if (key.upArrow || input === 'k') {
|
|
26
|
-
setSelectedIndex((prev) => (prev === 0 ? teams.length - 1 : prev - 1));
|
|
27
|
-
}
|
|
28
|
-
else if (key.downArrow || input === 'j') {
|
|
29
|
-
setSelectedIndex((prev) => (prev === teams.length - 1 ? 0 : prev + 1));
|
|
30
|
-
}
|
|
31
|
-
else if (key.return) {
|
|
32
|
-
onSelect(teams[selectedIndex]);
|
|
33
|
-
}
|
|
34
|
-
else {
|
|
35
|
-
// Handle number keys for quick selection
|
|
36
|
-
const num = parseInt(input, 10);
|
|
37
|
-
if (!isNaN(num) && num >= 1 && num <= teams.length) {
|
|
38
|
-
onSelect(teams[num - 1]);
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
});
|
|
42
|
-
if (loading) {
|
|
43
|
-
return (_jsx(Box, { children: _jsx(Text, { children: "Loading teams..." }) }));
|
|
44
|
-
}
|
|
45
|
-
if (error) {
|
|
46
|
-
return (_jsx(Box, { children: _jsxs(Text, { color: "red", children: ["Error: ", error] }) }));
|
|
47
|
-
}
|
|
48
|
-
if (teams.length === 0) {
|
|
49
|
-
return (_jsx(Box, { children: _jsx(Text, { children: "No teams available" }) }));
|
|
50
|
-
}
|
|
51
|
-
const selectedTeam = teams[selectedIndex];
|
|
52
|
-
return (_jsxs(Box, { flexDirection: "column", borderStyle: "round", borderColor: "gray", paddingX: 2, paddingY: 1, children: [_jsx(Box, { marginBottom: 1, children: _jsx(Text, { bold: true, children: "Select Team" }) }), _jsx(Box, { marginBottom: 1, children: _jsx(Text, { dimColor: true, children: "Choose a team to start a conversation with" }) }), _jsx(Box, { flexDirection: "column", children: teams.map((team, index) => (_jsx(Box, { marginBottom: 0, children: _jsxs(Text, { color: index === selectedIndex ? 'green' : undefined, children: [index === selectedIndex ? '❯ ' : ' ', index + 1, ". ", team.name, team.strategy ? ` (${team.strategy})` : ''] }) }, team.name))) }), selectedTeam &&
|
|
53
|
-
(selectedTeam.description || selectedTeam.members_count) && (_jsx(Box, { marginTop: 1, paddingLeft: 2, children: _jsx(Text, { dimColor: true, wrap: "wrap", children: selectedTeam.description ||
|
|
54
|
-
`Members: ${selectedTeam.members_count}` }) })), _jsx(Box, { marginTop: 1, children: _jsx(Text, { dimColor: true, children: "Enter to confirm \u00B7 Number to select \u00B7 Esc to exit" }) })] }));
|
|
55
|
-
}
|
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
import { Tool, ArkApiClient } from '../lib/arkApiClient.js';
|
|
2
|
-
interface ToolSelectorProps {
|
|
3
|
-
arkApiClient: ArkApiClient;
|
|
4
|
-
onSelect: (tool: Tool) => void;
|
|
5
|
-
onExit: () => void;
|
|
6
|
-
}
|
|
7
|
-
export declare function ToolSelector({ arkApiClient, onSelect, onExit, }: ToolSelectorProps): import("react/jsx-runtime").JSX.Element;
|
|
8
|
-
export {};
|