@agents-at-scale/ark 0.1.39 → 0.1.41
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/arkServices.js +12 -0
- package/dist/commands/query/index.js +4 -4
- package/dist/commands/query/index.spec.js +4 -1
- package/dist/components/ChatUI.js +80 -16
- package/dist/lib/arkApiClient.js +1 -0
- package/dist/lib/duration.d.ts +1 -0
- package/dist/lib/duration.js +20 -0
- package/dist/lib/duration.spec.d.ts +1 -0
- package/dist/lib/duration.spec.js +13 -0
- package/dist/lib/errors.d.ts +6 -0
- package/dist/lib/errors.js +6 -0
- package/dist/lib/executeQuery.d.ts +2 -0
- package/dist/lib/executeQuery.js +62 -73
- package/dist/lib/executeQuery.spec.js +66 -33
- package/dist/ui/TargetSelector.d.ts +19 -0
- package/dist/ui/TargetSelector.js +48 -0
- package/package.json +1 -1
- package/templates/mcp-server/README.md +1 -0
- package/templates/mcp-server/chart/templates/mcpserver.yaml +3 -0
- package/templates/mcp-server/chart/values.yaml +2 -1
- 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
package/dist/arkServices.js
CHANGED
|
@@ -128,6 +128,18 @@ const defaultArkServices = {
|
|
|
128
128
|
k8sDeploymentName: 'ark-mcp',
|
|
129
129
|
k8sDevDeploymentName: 'ark-mcp-devspace',
|
|
130
130
|
},
|
|
131
|
+
'mcp-filesystem': {
|
|
132
|
+
name: 'mcp-filesystem',
|
|
133
|
+
helmReleaseName: 'mcp-filesystem',
|
|
134
|
+
description: 'Stateful filesystem MCP server with workspace isolation',
|
|
135
|
+
enabled: false,
|
|
136
|
+
category: 'service',
|
|
137
|
+
// namespace: undefined - uses current context namespace
|
|
138
|
+
chartPath: `${REGISTRY_BASE}/mcp-filesystem`,
|
|
139
|
+
installArgs: [],
|
|
140
|
+
k8sDeploymentName: 'mcp-filesystem',
|
|
141
|
+
k8sDevDeploymentName: 'mcp-filesystem-devspace',
|
|
142
|
+
},
|
|
131
143
|
'agents-at-scale': {
|
|
132
144
|
name: 'agents-at-scale',
|
|
133
145
|
helmReleaseName: 'agents-at-scale',
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { Command } from 'commander';
|
|
2
|
-
import
|
|
2
|
+
import chalk from 'chalk';
|
|
3
3
|
import { executeQuery, parseTarget } from '../../lib/executeQuery.js';
|
|
4
|
+
import { ExitCodes } from '../../lib/errors.js';
|
|
4
5
|
export function createQueryCommand(_) {
|
|
5
6
|
const queryCommand = new Command('query');
|
|
6
7
|
queryCommand
|
|
@@ -8,11 +9,10 @@ export function createQueryCommand(_) {
|
|
|
8
9
|
.argument('<target>', 'Query target (e.g., model/default, agent/my-agent)')
|
|
9
10
|
.argument('<message>', 'Message to send')
|
|
10
11
|
.action(async (target, message) => {
|
|
11
|
-
// Parse and validate target format
|
|
12
12
|
const parsed = parseTarget(target);
|
|
13
13
|
if (!parsed) {
|
|
14
|
-
|
|
15
|
-
process.exit(
|
|
14
|
+
console.error(chalk.red('Invalid target format. Use: model/name or agent/name etc'));
|
|
15
|
+
process.exit(ExitCodes.CliError);
|
|
16
16
|
}
|
|
17
17
|
await executeQuery({
|
|
18
18
|
targetType: parsed.type,
|
|
@@ -15,6 +15,9 @@ jest.unstable_mockModule('../../lib/output.js', () => ({
|
|
|
15
15
|
const mockExit = jest.spyOn(process, 'exit').mockImplementation((() => {
|
|
16
16
|
throw new Error('process.exit called');
|
|
17
17
|
}));
|
|
18
|
+
const mockConsoleError = jest
|
|
19
|
+
.spyOn(console, 'error')
|
|
20
|
+
.mockImplementation(() => { });
|
|
18
21
|
const { createQueryCommand } = await import('./index.js');
|
|
19
22
|
describe('createQueryCommand', () => {
|
|
20
23
|
beforeEach(() => {
|
|
@@ -47,7 +50,7 @@ describe('createQueryCommand', () => {
|
|
|
47
50
|
await expect(command.parseAsync(['node', 'test', 'invalid-target', 'Hello'])).rejects.toThrow('process.exit called');
|
|
48
51
|
expect(mockParseTarget).toHaveBeenCalledWith('invalid-target');
|
|
49
52
|
expect(mockExecuteQuery).not.toHaveBeenCalled();
|
|
50
|
-
expect(
|
|
53
|
+
expect(mockConsoleError).toHaveBeenCalledWith(expect.stringContaining('Invalid target format'));
|
|
51
54
|
expect(mockExit).toHaveBeenCalledWith(1);
|
|
52
55
|
});
|
|
53
56
|
});
|
|
@@ -7,10 +7,7 @@ import { marked } from 'marked';
|
|
|
7
7
|
// @ts-ignore - no types available
|
|
8
8
|
import TerminalRenderer from 'marked-terminal';
|
|
9
9
|
import { APIError } from 'openai';
|
|
10
|
-
import {
|
|
11
|
-
import { ModelSelector } from '../ui/ModelSelector.js';
|
|
12
|
-
import { TeamSelector } from '../ui/TeamSelector.js';
|
|
13
|
-
import { ToolSelector } from '../ui/ToolSelector.js';
|
|
10
|
+
import { TargetSelector } from '../ui/TargetSelector.js';
|
|
14
11
|
import { useAsyncOperation, AsyncOperationStatus } from './AsyncOperation.js';
|
|
15
12
|
import { createConnectingToArkOperation } from '../ui/asyncOperations/connectingToArk.js';
|
|
16
13
|
// Generate a unique ID for messages
|
|
@@ -48,6 +45,12 @@ const ChatUI = ({ initialTargetId, arkApiClient, arkApiProxy, config, }) => {
|
|
|
48
45
|
const [showModelSelector, setShowModelSelector] = React.useState(false);
|
|
49
46
|
const [showTeamSelector, setShowTeamSelector] = React.useState(false);
|
|
50
47
|
const [showToolSelector, setShowToolSelector] = React.useState(false);
|
|
48
|
+
const [agents, setAgents] = React.useState([]);
|
|
49
|
+
const [models, setModels] = React.useState([]);
|
|
50
|
+
const [teams, setTeams] = React.useState([]);
|
|
51
|
+
const [tools, setTools] = React.useState([]);
|
|
52
|
+
const [selectorLoading, setSelectorLoading] = React.useState(false);
|
|
53
|
+
const [selectorError, setSelectorError] = React.useState(undefined);
|
|
51
54
|
// Message history navigation
|
|
52
55
|
const [messageHistory, setMessageHistory] = React.useState([]);
|
|
53
56
|
const [historyIndex, setHistoryIndex] = React.useState(-1);
|
|
@@ -56,6 +59,50 @@ const ChatUI = ({ initialTargetId, arkApiClient, arkApiProxy, config, }) => {
|
|
|
56
59
|
streamingEnabled: config?.chat?.streaming ?? true,
|
|
57
60
|
currentTarget: undefined,
|
|
58
61
|
});
|
|
62
|
+
React.useEffect(() => {
|
|
63
|
+
if (showAgentSelector && agents.length === 0) {
|
|
64
|
+
setSelectorLoading(true);
|
|
65
|
+
setSelectorError(undefined);
|
|
66
|
+
arkApiClient
|
|
67
|
+
.getAgents()
|
|
68
|
+
.then(setAgents)
|
|
69
|
+
.catch((err) => setSelectorError(err.message))
|
|
70
|
+
.finally(() => setSelectorLoading(false));
|
|
71
|
+
}
|
|
72
|
+
}, [showAgentSelector, arkApiClient, agents.length]);
|
|
73
|
+
React.useEffect(() => {
|
|
74
|
+
if (showModelSelector && models.length === 0) {
|
|
75
|
+
setSelectorLoading(true);
|
|
76
|
+
setSelectorError(undefined);
|
|
77
|
+
arkApiClient
|
|
78
|
+
.getModels()
|
|
79
|
+
.then(setModels)
|
|
80
|
+
.catch((err) => setSelectorError(err.message))
|
|
81
|
+
.finally(() => setSelectorLoading(false));
|
|
82
|
+
}
|
|
83
|
+
}, [showModelSelector, arkApiClient, models.length]);
|
|
84
|
+
React.useEffect(() => {
|
|
85
|
+
if (showTeamSelector && teams.length === 0) {
|
|
86
|
+
setSelectorLoading(true);
|
|
87
|
+
setSelectorError(undefined);
|
|
88
|
+
arkApiClient
|
|
89
|
+
.getTeams()
|
|
90
|
+
.then(setTeams)
|
|
91
|
+
.catch((err) => setSelectorError(err.message))
|
|
92
|
+
.finally(() => setSelectorLoading(false));
|
|
93
|
+
}
|
|
94
|
+
}, [showTeamSelector, arkApiClient, teams.length]);
|
|
95
|
+
React.useEffect(() => {
|
|
96
|
+
if (showToolSelector && tools.length === 0) {
|
|
97
|
+
setSelectorLoading(true);
|
|
98
|
+
setSelectorError(undefined);
|
|
99
|
+
arkApiClient
|
|
100
|
+
.getTools()
|
|
101
|
+
.then(setTools)
|
|
102
|
+
.catch((err) => setSelectorError(err.message))
|
|
103
|
+
.finally(() => setSelectorLoading(false));
|
|
104
|
+
}
|
|
105
|
+
}, [showToolSelector, arkApiClient, tools.length]);
|
|
59
106
|
const chatClientRef = React.useRef(undefined);
|
|
60
107
|
// Configure markdown when output format changes
|
|
61
108
|
React.useEffect(() => {
|
|
@@ -608,8 +655,10 @@ const ChatUI = ({ initialTargetId, arkApiClient, arkApiProxy, config, }) => {
|
|
|
608
655
|
}
|
|
609
656
|
// Show agent selector if requested
|
|
610
657
|
if (showAgentSelector) {
|
|
611
|
-
return (_jsx(
|
|
612
|
-
|
|
658
|
+
return (_jsx(TargetSelector, { targets: agents, title: "Select Agent", subtitle: "Choose an agent to start a conversation with", loading: selectorLoading, error: selectorError, formatInlineDetail: (t) => t.description, showDetailPanel: true, onSelect: (target) => {
|
|
659
|
+
const agent = agents.find((a) => a.name === target.name);
|
|
660
|
+
if (!agent)
|
|
661
|
+
return;
|
|
613
662
|
const agentTarget = {
|
|
614
663
|
id: `agent/${agent.name}`,
|
|
615
664
|
name: agent.name,
|
|
@@ -620,7 +669,6 @@ const ChatUI = ({ initialTargetId, arkApiClient, arkApiProxy, config, }) => {
|
|
|
620
669
|
setChatConfig((prev) => ({ ...prev, currentTarget: agentTarget }));
|
|
621
670
|
setMessages([]);
|
|
622
671
|
setShowAgentSelector(false);
|
|
623
|
-
// Add system message about the selection
|
|
624
672
|
const systemMessage = {
|
|
625
673
|
id: generateMessageId(),
|
|
626
674
|
type: 'system',
|
|
@@ -633,8 +681,18 @@ const ChatUI = ({ initialTargetId, arkApiClient, arkApiProxy, config, }) => {
|
|
|
633
681
|
}
|
|
634
682
|
// Show model selector if requested
|
|
635
683
|
if (showModelSelector) {
|
|
636
|
-
return (_jsx(
|
|
637
|
-
|
|
684
|
+
return (_jsx(TargetSelector, { targets: models, title: "Select Model", subtitle: "Choose a model to start a conversation with", loading: selectorLoading, error: selectorError, formatLabel: (t) => {
|
|
685
|
+
const model = models.find((m) => m.name === t.name);
|
|
686
|
+
return model
|
|
687
|
+
? `${model.name}${model.type ? ` (${model.type})` : ''}`
|
|
688
|
+
: t.name;
|
|
689
|
+
}, formatInlineDetail: (t) => {
|
|
690
|
+
const model = models.find((m) => m.name === t.name);
|
|
691
|
+
return model?.model;
|
|
692
|
+
}, showDetailPanel: false, onSelect: (target) => {
|
|
693
|
+
const model = models.find((m) => m.name === target.name);
|
|
694
|
+
if (!model)
|
|
695
|
+
return;
|
|
638
696
|
const modelTarget = {
|
|
639
697
|
id: `model/${model.name}`,
|
|
640
698
|
name: model.name,
|
|
@@ -645,7 +703,6 @@ const ChatUI = ({ initialTargetId, arkApiClient, arkApiProxy, config, }) => {
|
|
|
645
703
|
setChatConfig((prev) => ({ ...prev, currentTarget: modelTarget }));
|
|
646
704
|
setMessages([]);
|
|
647
705
|
setShowModelSelector(false);
|
|
648
|
-
// Add system message about the selection
|
|
649
706
|
const systemMessage = {
|
|
650
707
|
id: generateMessageId(),
|
|
651
708
|
type: 'system',
|
|
@@ -658,8 +715,15 @@ const ChatUI = ({ initialTargetId, arkApiClient, arkApiProxy, config, }) => {
|
|
|
658
715
|
}
|
|
659
716
|
// Show team selector if requested
|
|
660
717
|
if (showTeamSelector) {
|
|
661
|
-
return (_jsx(
|
|
662
|
-
|
|
718
|
+
return (_jsx(TargetSelector, { targets: teams, title: "Select Team", subtitle: "Choose a team to start a conversation with", loading: selectorLoading, error: selectorError, formatLabel: (t) => {
|
|
719
|
+
const team = teams.find((tm) => tm.name === t.name);
|
|
720
|
+
return team
|
|
721
|
+
? `${team.name}${team.strategy ? ` (${team.strategy})` : ''}`
|
|
722
|
+
: t.name;
|
|
723
|
+
}, formatInlineDetail: (t) => t.description, showDetailPanel: true, onSelect: (target) => {
|
|
724
|
+
const team = teams.find((tm) => tm.name === target.name);
|
|
725
|
+
if (!team)
|
|
726
|
+
return;
|
|
663
727
|
const teamTarget = {
|
|
664
728
|
id: `team/${team.name}`,
|
|
665
729
|
name: team.name,
|
|
@@ -670,7 +734,6 @@ const ChatUI = ({ initialTargetId, arkApiClient, arkApiProxy, config, }) => {
|
|
|
670
734
|
setChatConfig((prev) => ({ ...prev, currentTarget: teamTarget }));
|
|
671
735
|
setMessages([]);
|
|
672
736
|
setShowTeamSelector(false);
|
|
673
|
-
// Add system message about the selection
|
|
674
737
|
const systemMessage = {
|
|
675
738
|
id: generateMessageId(),
|
|
676
739
|
type: 'system',
|
|
@@ -683,8 +746,10 @@ const ChatUI = ({ initialTargetId, arkApiClient, arkApiProxy, config, }) => {
|
|
|
683
746
|
}
|
|
684
747
|
// Show tool selector if requested
|
|
685
748
|
if (showToolSelector) {
|
|
686
|
-
return (_jsx(
|
|
687
|
-
|
|
749
|
+
return (_jsx(TargetSelector, { targets: tools, title: "Select Tool", subtitle: "Choose a tool to start a conversation with", loading: selectorLoading, error: selectorError, formatInlineDetail: (t) => t.description, showDetailPanel: true, onSelect: (target) => {
|
|
750
|
+
const tool = tools.find((tl) => tl.name === target.name);
|
|
751
|
+
if (!tool)
|
|
752
|
+
return;
|
|
688
753
|
const toolTarget = {
|
|
689
754
|
id: `tool/${tool.name}`,
|
|
690
755
|
name: tool.name,
|
|
@@ -695,7 +760,6 @@ const ChatUI = ({ initialTargetId, arkApiClient, arkApiProxy, config, }) => {
|
|
|
695
760
|
setChatConfig((prev) => ({ ...prev, currentTarget: toolTarget }));
|
|
696
761
|
setMessages([]);
|
|
697
762
|
setShowToolSelector(false);
|
|
698
|
-
// Add system message about the selection
|
|
699
763
|
const systemMessage = {
|
|
700
764
|
id: generateMessageId(),
|
|
701
765
|
type: 'system',
|
package/dist/lib/arkApiClient.js
CHANGED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function parseDuration(duration: string): number;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
export function parseDuration(duration) {
|
|
2
|
+
const match = duration.match(/^(\d+(?:\.\d+)?)(ms|s|m|h)$/);
|
|
3
|
+
if (!match) {
|
|
4
|
+
throw new Error(`Invalid duration format: ${duration}`);
|
|
5
|
+
}
|
|
6
|
+
const value = parseFloat(match[1]);
|
|
7
|
+
const unit = match[2];
|
|
8
|
+
switch (unit) {
|
|
9
|
+
case 'ms':
|
|
10
|
+
return value;
|
|
11
|
+
case 's':
|
|
12
|
+
return value * 1000;
|
|
13
|
+
case 'm':
|
|
14
|
+
return value * 60 * 1000;
|
|
15
|
+
case 'h':
|
|
16
|
+
return value * 60 * 60 * 1000;
|
|
17
|
+
default:
|
|
18
|
+
throw new Error(`Unknown duration unit: ${unit}`);
|
|
19
|
+
}
|
|
20
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { parseDuration } from './duration.js';
|
|
2
|
+
describe('parseDuration', () => {
|
|
3
|
+
it('should parse durations correctly', () => {
|
|
4
|
+
expect(parseDuration('100ms')).toBe(100);
|
|
5
|
+
expect(parseDuration('30s')).toBe(30000);
|
|
6
|
+
expect(parseDuration('5m')).toBe(300000);
|
|
7
|
+
expect(parseDuration('1h')).toBe(3600000);
|
|
8
|
+
});
|
|
9
|
+
it('should throw on invalid format', () => {
|
|
10
|
+
expect(() => parseDuration('invalid')).toThrow('Invalid duration format');
|
|
11
|
+
expect(() => parseDuration('10')).toThrow('Invalid duration format');
|
|
12
|
+
});
|
|
13
|
+
});
|
package/dist/lib/errors.d.ts
CHANGED
|
@@ -1,6 +1,12 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Centralized error handling for ARK CLI
|
|
3
3
|
*/
|
|
4
|
+
export declare const ExitCodes: {
|
|
5
|
+
readonly Success: 0;
|
|
6
|
+
readonly CliError: 1;
|
|
7
|
+
readonly OperationError: 2;
|
|
8
|
+
readonly Timeout: 3;
|
|
9
|
+
};
|
|
4
10
|
export declare enum ErrorCode {
|
|
5
11
|
INVALID_INPUT = "INVALID_INPUT",
|
|
6
12
|
FILE_NOT_FOUND = "FILE_NOT_FOUND",
|
package/dist/lib/errors.js
CHANGED
package/dist/lib/executeQuery.js
CHANGED
|
@@ -3,17 +3,24 @@
|
|
|
3
3
|
*/
|
|
4
4
|
import { execa } from 'execa';
|
|
5
5
|
import ora from 'ora';
|
|
6
|
+
import chalk from 'chalk';
|
|
6
7
|
import output from './output.js';
|
|
8
|
+
import { ExitCodes } from './errors.js';
|
|
9
|
+
import { parseDuration } from './duration.js';
|
|
7
10
|
/**
|
|
8
11
|
* Execute a query against any ARK target (model, agent, team)
|
|
9
12
|
* This is the shared implementation used by all query commands
|
|
10
13
|
*/
|
|
11
14
|
export async function executeQuery(options) {
|
|
12
15
|
const spinner = ora('Creating query...').start();
|
|
13
|
-
|
|
16
|
+
const queryTimeoutMs = options.timeout
|
|
17
|
+
? parseDuration(options.timeout)
|
|
18
|
+
: parseDuration('5m');
|
|
19
|
+
const watchTimeoutMs = options.watchTimeout
|
|
20
|
+
? parseDuration(options.watchTimeout)
|
|
21
|
+
: queryTimeoutMs + 60000;
|
|
14
22
|
const timestamp = Date.now();
|
|
15
23
|
const queryName = `cli-query-${timestamp}`;
|
|
16
|
-
// Create the Query resource
|
|
17
24
|
const queryManifest = {
|
|
18
25
|
apiVersion: 'ark.mckinsey.com/v1alpha1',
|
|
19
26
|
kind: 'Query',
|
|
@@ -22,6 +29,7 @@ export async function executeQuery(options) {
|
|
|
22
29
|
},
|
|
23
30
|
spec: {
|
|
24
31
|
input: options.message,
|
|
32
|
+
...(options.timeout && { timeout: options.timeout }),
|
|
25
33
|
targets: [
|
|
26
34
|
{
|
|
27
35
|
type: options.targetType,
|
|
@@ -37,86 +45,67 @@ export async function executeQuery(options) {
|
|
|
37
45
|
input: JSON.stringify(queryManifest),
|
|
38
46
|
stdio: ['pipe', 'pipe', 'pipe'],
|
|
39
47
|
});
|
|
40
|
-
// Watch for query completion
|
|
41
|
-
spinner.text = '
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
const errorCondition = query.status?.conditions?.find((c) => {
|
|
73
|
-
return c.type === 'Complete' && c.status === 'False';
|
|
74
|
-
});
|
|
75
|
-
if (errorCondition?.message) {
|
|
76
|
-
output.error(errorCondition.message);
|
|
77
|
-
}
|
|
78
|
-
else if (query.status?.error) {
|
|
79
|
-
output.error(query.status.error);
|
|
80
|
-
}
|
|
81
|
-
else {
|
|
82
|
-
output.error('Query failed with unknown error');
|
|
83
|
-
}
|
|
48
|
+
// Watch for query completion using kubectl wait
|
|
49
|
+
spinner.text = 'Waiting for query completion...';
|
|
50
|
+
try {
|
|
51
|
+
await execa('kubectl', [
|
|
52
|
+
'wait',
|
|
53
|
+
'--for=condition=Completed',
|
|
54
|
+
`query/${queryName}`,
|
|
55
|
+
`--timeout=${Math.floor(watchTimeoutMs / 1000)}s`,
|
|
56
|
+
], { timeout: watchTimeoutMs });
|
|
57
|
+
}
|
|
58
|
+
catch (error) {
|
|
59
|
+
spinner.stop();
|
|
60
|
+
// Check if it's a timeout or other error
|
|
61
|
+
if (error instanceof Error &&
|
|
62
|
+
error.message.includes('timed out waiting')) {
|
|
63
|
+
console.error(chalk.red(`Query did not complete within ${options.watchTimeout ?? `${Math.floor(watchTimeoutMs / 1000)}s`}`));
|
|
64
|
+
process.exit(ExitCodes.Timeout);
|
|
65
|
+
}
|
|
66
|
+
// For other errors, fetch the query to check status
|
|
67
|
+
}
|
|
68
|
+
spinner.stop();
|
|
69
|
+
// Fetch final query state
|
|
70
|
+
try {
|
|
71
|
+
const { stdout } = await execa('kubectl', ['get', 'query', queryName, '-o', 'json'], { stdio: 'pipe' });
|
|
72
|
+
const query = JSON.parse(stdout);
|
|
73
|
+
const phase = query.status?.phase;
|
|
74
|
+
// Check if query completed successfully or with error
|
|
75
|
+
if (phase === 'done') {
|
|
76
|
+
// Extract and display the response from responses array
|
|
77
|
+
if (query.status?.responses && query.status.responses.length > 0) {
|
|
78
|
+
const response = query.status.responses[0];
|
|
79
|
+
console.log(response.content || response);
|
|
84
80
|
}
|
|
85
|
-
else
|
|
86
|
-
|
|
87
|
-
spinner.warn('Query canceled');
|
|
88
|
-
// Try to get cancellation reason if available
|
|
89
|
-
if (query.status?.message) {
|
|
90
|
-
output.warning(query.status.message);
|
|
91
|
-
}
|
|
81
|
+
else {
|
|
82
|
+
output.warning('No response received');
|
|
92
83
|
}
|
|
93
84
|
}
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
85
|
+
else if (phase === 'error') {
|
|
86
|
+
const response = query.status?.responses?.[0];
|
|
87
|
+
console.error(chalk.red(response?.content || 'Query failed with unknown error'));
|
|
88
|
+
process.exit(ExitCodes.OperationError);
|
|
97
89
|
}
|
|
98
|
-
if (
|
|
99
|
-
|
|
90
|
+
else if (phase === 'canceled') {
|
|
91
|
+
spinner.warn('Query canceled');
|
|
92
|
+
if (query.status?.message) {
|
|
93
|
+
output.warning(query.status.message);
|
|
94
|
+
}
|
|
95
|
+
process.exit(ExitCodes.OperationError);
|
|
100
96
|
}
|
|
101
97
|
}
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
98
|
+
catch (error) {
|
|
99
|
+
console.error(chalk.red(error instanceof Error
|
|
100
|
+
? error.message
|
|
101
|
+
: 'Failed to fetch query result'));
|
|
102
|
+
process.exit(ExitCodes.CliError);
|
|
105
103
|
}
|
|
106
104
|
}
|
|
107
105
|
catch (error) {
|
|
108
|
-
spinner.
|
|
109
|
-
|
|
110
|
-
process.exit(
|
|
111
|
-
}
|
|
112
|
-
finally {
|
|
113
|
-
// Clean up the query resource
|
|
114
|
-
try {
|
|
115
|
-
await execa('kubectl', ['delete', 'query', queryName], { stdio: 'pipe' });
|
|
116
|
-
}
|
|
117
|
-
catch {
|
|
118
|
-
// Ignore cleanup errors
|
|
119
|
-
}
|
|
106
|
+
spinner.stop();
|
|
107
|
+
console.error(chalk.red(error instanceof Error ? error.message : 'Unknown error'));
|
|
108
|
+
process.exit(ExitCodes.CliError);
|
|
120
109
|
}
|
|
121
110
|
}
|
|
122
111
|
/**
|
|
@@ -8,6 +8,7 @@ const mockSpinner = {
|
|
|
8
8
|
succeed: jest.fn(),
|
|
9
9
|
fail: jest.fn(),
|
|
10
10
|
warn: jest.fn(),
|
|
11
|
+
stop: jest.fn(),
|
|
11
12
|
text: '',
|
|
12
13
|
};
|
|
13
14
|
const mockOra = jest.fn(() => mockSpinner);
|
|
@@ -25,7 +26,11 @@ const mockExit = jest.spyOn(process, 'exit').mockImplementation((() => {
|
|
|
25
26
|
throw new Error('process.exit called');
|
|
26
27
|
}));
|
|
27
28
|
const mockConsoleLog = jest.spyOn(console, 'log').mockImplementation(() => { });
|
|
29
|
+
const mockConsoleError = jest
|
|
30
|
+
.spyOn(console, 'error')
|
|
31
|
+
.mockImplementation(() => { });
|
|
28
32
|
const { executeQuery, parseTarget } = await import('./executeQuery.js');
|
|
33
|
+
const { ExitCodes } = await import('./errors.js');
|
|
29
34
|
describe('executeQuery', () => {
|
|
30
35
|
beforeEach(() => {
|
|
31
36
|
jest.clearAllMocks();
|
|
@@ -71,9 +76,6 @@ describe('executeQuery', () => {
|
|
|
71
76
|
exitCode: 0,
|
|
72
77
|
};
|
|
73
78
|
}
|
|
74
|
-
if (args.includes('delete')) {
|
|
75
|
-
return { stdout: '', stderr: '', exitCode: 0 };
|
|
76
|
-
}
|
|
77
79
|
return { stdout: '', stderr: '', exitCode: 0 };
|
|
78
80
|
});
|
|
79
81
|
await executeQuery({
|
|
@@ -82,14 +84,14 @@ describe('executeQuery', () => {
|
|
|
82
84
|
message: 'Hello',
|
|
83
85
|
});
|
|
84
86
|
expect(mockSpinner.start).toHaveBeenCalled();
|
|
85
|
-
expect(mockSpinner.
|
|
86
|
-
expect(mockConsoleLog).toHaveBeenCalledWith('
|
|
87
|
+
expect(mockSpinner.stop).toHaveBeenCalled();
|
|
88
|
+
expect(mockConsoleLog).toHaveBeenCalledWith('Test response');
|
|
87
89
|
});
|
|
88
|
-
it('should handle query error phase', async () => {
|
|
90
|
+
it('should handle query error phase and exit with code 2', async () => {
|
|
89
91
|
const mockQueryResponse = {
|
|
90
92
|
status: {
|
|
91
93
|
phase: 'error',
|
|
92
|
-
|
|
94
|
+
responses: [{ content: 'Query failed with test error' }],
|
|
93
95
|
},
|
|
94
96
|
};
|
|
95
97
|
mockExeca.mockImplementation(async (command, args) => {
|
|
@@ -103,20 +105,23 @@ describe('executeQuery', () => {
|
|
|
103
105
|
exitCode: 0,
|
|
104
106
|
};
|
|
105
107
|
}
|
|
106
|
-
if (args.includes('delete')) {
|
|
107
|
-
return { stdout: '', stderr: '', exitCode: 0 };
|
|
108
|
-
}
|
|
109
108
|
return { stdout: '', stderr: '', exitCode: 0 };
|
|
110
109
|
});
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
110
|
+
try {
|
|
111
|
+
await executeQuery({
|
|
112
|
+
targetType: 'model',
|
|
113
|
+
targetName: 'default',
|
|
114
|
+
message: 'Hello',
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
catch (error) {
|
|
118
|
+
expect(error.message).toBe('process.exit called');
|
|
119
|
+
}
|
|
120
|
+
expect(mockSpinner.stop).toHaveBeenCalled();
|
|
121
|
+
expect(mockConsoleError).toHaveBeenCalledWith(expect.stringContaining('Query failed with test error'));
|
|
122
|
+
expect(mockExit).toHaveBeenCalledWith(ExitCodes.OperationError);
|
|
118
123
|
});
|
|
119
|
-
it('should handle query canceled phase', async () => {
|
|
124
|
+
it('should handle query canceled phase and exit with code 2', async () => {
|
|
120
125
|
const mockQueryResponse = {
|
|
121
126
|
status: {
|
|
122
127
|
phase: 'canceled',
|
|
@@ -134,27 +139,27 @@ describe('executeQuery', () => {
|
|
|
134
139
|
exitCode: 0,
|
|
135
140
|
};
|
|
136
141
|
}
|
|
137
|
-
if (args.includes('delete')) {
|
|
138
|
-
return { stdout: '', stderr: '', exitCode: 0 };
|
|
139
|
-
}
|
|
140
142
|
return { stdout: '', stderr: '', exitCode: 0 };
|
|
141
143
|
});
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
144
|
+
try {
|
|
145
|
+
await executeQuery({
|
|
146
|
+
targetType: 'agent',
|
|
147
|
+
targetName: 'test-agent',
|
|
148
|
+
message: 'Hello',
|
|
149
|
+
});
|
|
150
|
+
}
|
|
151
|
+
catch (error) {
|
|
152
|
+
expect(error.message).toBe('process.exit called');
|
|
153
|
+
}
|
|
147
154
|
expect(mockSpinner.warn).toHaveBeenCalledWith('Query canceled');
|
|
148
155
|
expect(mockOutput.warning).toHaveBeenCalledWith('Query was canceled');
|
|
156
|
+
expect(mockExit).toHaveBeenCalledWith(ExitCodes.OperationError);
|
|
149
157
|
});
|
|
150
|
-
it('should
|
|
158
|
+
it('should handle kubectl apply failures with exit code 1', async () => {
|
|
151
159
|
mockExeca.mockImplementation(async (command, args) => {
|
|
152
160
|
if (args.includes('apply')) {
|
|
153
161
|
throw new Error('Failed to apply');
|
|
154
162
|
}
|
|
155
|
-
if (args.includes('delete')) {
|
|
156
|
-
return { stdout: '', stderr: '', exitCode: 0 };
|
|
157
|
-
}
|
|
158
163
|
return { stdout: '', stderr: '', exitCode: 0 };
|
|
159
164
|
});
|
|
160
165
|
await expect(executeQuery({
|
|
@@ -162,9 +167,37 @@ describe('executeQuery', () => {
|
|
|
162
167
|
targetName: 'default',
|
|
163
168
|
message: 'Hello',
|
|
164
169
|
})).rejects.toThrow('process.exit called');
|
|
165
|
-
expect(mockSpinner.
|
|
166
|
-
expect(
|
|
167
|
-
expect(mockExit).toHaveBeenCalledWith(
|
|
170
|
+
expect(mockSpinner.stop).toHaveBeenCalled();
|
|
171
|
+
expect(mockConsoleError).toHaveBeenCalledWith(expect.stringContaining('Failed to apply'));
|
|
172
|
+
expect(mockExit).toHaveBeenCalledWith(ExitCodes.CliError);
|
|
173
|
+
});
|
|
174
|
+
it('should handle query timeout and exit with code 3', async () => {
|
|
175
|
+
mockExeca.mockImplementation(async (command, args) => {
|
|
176
|
+
if (args.includes('apply')) {
|
|
177
|
+
return { stdout: '', stderr: '', exitCode: 0 };
|
|
178
|
+
}
|
|
179
|
+
if (args.includes('wait')) {
|
|
180
|
+
// Simulate kubectl wait timeout
|
|
181
|
+
const error = new Error('timed out waiting for the condition');
|
|
182
|
+
throw error;
|
|
183
|
+
}
|
|
184
|
+
return { stdout: '', stderr: '', exitCode: 0 };
|
|
185
|
+
});
|
|
186
|
+
try {
|
|
187
|
+
await executeQuery({
|
|
188
|
+
targetType: 'model',
|
|
189
|
+
targetName: 'default',
|
|
190
|
+
message: 'Hello',
|
|
191
|
+
timeout: '100ms',
|
|
192
|
+
watchTimeout: '200ms',
|
|
193
|
+
});
|
|
194
|
+
}
|
|
195
|
+
catch (error) {
|
|
196
|
+
expect(error.message).toBe('process.exit called');
|
|
197
|
+
}
|
|
198
|
+
expect(mockSpinner.stop).toHaveBeenCalled();
|
|
199
|
+
expect(mockConsoleError).toHaveBeenCalledWith(expect.stringContaining('Query did not complete within 200ms'));
|
|
200
|
+
expect(mockExit).toHaveBeenCalledWith(ExitCodes.Timeout);
|
|
168
201
|
});
|
|
169
202
|
});
|
|
170
203
|
});
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
export interface Target {
|
|
2
|
+
name: string;
|
|
3
|
+
namespace: string;
|
|
4
|
+
description?: string;
|
|
5
|
+
}
|
|
6
|
+
interface TargetSelectorProps {
|
|
7
|
+
targets: Target[];
|
|
8
|
+
title: string;
|
|
9
|
+
subtitle: string;
|
|
10
|
+
onSelect: (target: Target) => void;
|
|
11
|
+
onExit: () => void;
|
|
12
|
+
formatLabel?: (target: Target) => string;
|
|
13
|
+
formatInlineDetail?: (target: Target) => string | undefined;
|
|
14
|
+
showDetailPanel?: boolean;
|
|
15
|
+
loading?: boolean;
|
|
16
|
+
error?: string;
|
|
17
|
+
}
|
|
18
|
+
export declare function TargetSelector({ targets, title, subtitle, onSelect, onExit, formatLabel, formatInlineDetail, showDetailPanel, loading, error, }: TargetSelectorProps): import("react/jsx-runtime").JSX.Element;
|
|
19
|
+
export {};
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { jsxs as _jsxs, jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { useState } from 'react';
|
|
3
|
+
import { Box, Text, useInput } from 'ink';
|
|
4
|
+
export function TargetSelector({ targets, title, subtitle, onSelect, onExit, formatLabel = (target) => target.name, formatInlineDetail, showDetailPanel = false, loading = false, error, }) {
|
|
5
|
+
const [selectedIndex, setSelectedIndex] = useState(0);
|
|
6
|
+
useInput((input, key) => {
|
|
7
|
+
if (key.escape) {
|
|
8
|
+
onExit();
|
|
9
|
+
}
|
|
10
|
+
else if (key.upArrow || input === 'k') {
|
|
11
|
+
setSelectedIndex((prev) => (prev === 0 ? targets.length - 1 : prev - 1));
|
|
12
|
+
}
|
|
13
|
+
else if (key.downArrow || input === 'j') {
|
|
14
|
+
setSelectedIndex((prev) => (prev === targets.length - 1 ? 0 : prev + 1));
|
|
15
|
+
}
|
|
16
|
+
else if (key.return) {
|
|
17
|
+
onSelect(targets[selectedIndex]);
|
|
18
|
+
}
|
|
19
|
+
else {
|
|
20
|
+
const num = parseInt(input, 10);
|
|
21
|
+
if (!isNaN(num) && num >= 1 && num <= targets.length) {
|
|
22
|
+
onSelect(targets[num - 1]);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
});
|
|
26
|
+
const truncate = (text, maxLength) => {
|
|
27
|
+
if (text.length <= maxLength)
|
|
28
|
+
return text;
|
|
29
|
+
return text.substring(0, maxLength - 3) + '...';
|
|
30
|
+
};
|
|
31
|
+
if (loading) {
|
|
32
|
+
return (_jsx(Box, { children: _jsxs(Text, { children: ["Loading ", title.toLowerCase(), "..."] }) }));
|
|
33
|
+
}
|
|
34
|
+
if (error) {
|
|
35
|
+
return (_jsx(Box, { children: _jsxs(Text, { color: "red", children: ["Error: ", error] }) }));
|
|
36
|
+
}
|
|
37
|
+
if (targets.length === 0) {
|
|
38
|
+
return (_jsx(Box, { children: _jsxs(Text, { children: ["No ", title.toLowerCase(), " available"] }) }));
|
|
39
|
+
}
|
|
40
|
+
const selectedTarget = targets[selectedIndex];
|
|
41
|
+
return (_jsxs(Box, { flexDirection: "column", borderStyle: "round", borderColor: "gray", paddingX: 2, paddingY: 1, children: [_jsx(Box, { marginBottom: 1, children: _jsx(Text, { bold: true, children: title }) }), _jsx(Box, { marginBottom: 1, children: _jsx(Text, { dimColor: true, children: subtitle }) }), _jsx(Box, { flexDirection: "column", children: targets.map((target, index) => {
|
|
42
|
+
const label = formatLabel(target);
|
|
43
|
+
const prefix = `${index === selectedIndex ? '❯ ' : ' '}${index + 1}. ${label}`;
|
|
44
|
+
const inlineDetail = formatInlineDetail?.(target);
|
|
45
|
+
const maxDetailLength = 80 - prefix.length;
|
|
46
|
+
return (_jsxs(Box, { marginBottom: 0, children: [_jsx(Text, { color: index === selectedIndex ? 'green' : undefined, children: prefix }), inlineDetail && (_jsxs(Text, { color: "gray", dimColor: true, children: [' ', truncate(inlineDetail, maxDetailLength)] }))] }, `${target.namespace}-${target.name}`));
|
|
47
|
+
}) }), showDetailPanel && selectedTarget.description && (_jsx(Box, { marginTop: 1, paddingLeft: 2, children: _jsx(Text, { dimColor: true, wrap: "wrap", children: selectedTarget.description }) })), _jsx(Box, { marginTop: 1, children: _jsx(Text, { dimColor: true, children: "Enter to confirm \u00B7 Number to select \u00B7 Esc to exit" }) })] }));
|
|
48
|
+
}
|
package/package.json
CHANGED
|
@@ -62,6 +62,7 @@ kubectl get queries
|
|
|
62
62
|
| `image.repository` | string | `"{{ .Values.mcpServerName }}"` | Image repository |
|
|
63
63
|
| `image.tag` | string | `"latest"` | Image tag |
|
|
64
64
|
| `service.port` | int | `8080` | Service port |
|
|
65
|
+
| `mcpServer.timeout` | string | `"30s"` | Timeout for MCP tool calls (e.g., "5m", "10m") |
|
|
65
66
|
|
|
66
67
|
{{- if .Values.requiresAuth }}
|
|
67
68
|
| `auth.token` | string | `""` | Authentication token |
|
|
@@ -15,6 +15,9 @@ spec:
|
|
|
15
15
|
path: {{ .Values.mcpServer.path }}
|
|
16
16
|
{{- end }}
|
|
17
17
|
transport: sse
|
|
18
|
+
{{- if .Values.mcpServer.timeout }}
|
|
19
|
+
timeout: {{ .Values.mcpServer.timeout | quote }}
|
|
20
|
+
{{- end }}
|
|
18
21
|
{{- if .Values.mcpServer.description }}
|
|
19
22
|
description: {{ .Values.mcpServer.description | quote }}
|
|
20
23
|
{{- end }}
|
|
@@ -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 {};
|
package/dist/ui/ToolSelector.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 ToolSelector({ arkApiClient, onSelect, onExit, }) {
|
|
5
|
-
const [selectedIndex, setSelectedIndex] = useState(0);
|
|
6
|
-
const [tools, setTools] = useState([]);
|
|
7
|
-
const [loading, setLoading] = useState(true);
|
|
8
|
-
const [error, setError] = useState(null);
|
|
9
|
-
useEffect(() => {
|
|
10
|
-
arkApiClient
|
|
11
|
-
.getTools()
|
|
12
|
-
.then((fetchedTools) => {
|
|
13
|
-
setTools(fetchedTools);
|
|
14
|
-
setLoading(false);
|
|
15
|
-
})
|
|
16
|
-
.catch((err) => {
|
|
17
|
-
setError(err.message || 'Failed to fetch tools');
|
|
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 ? tools.length - 1 : prev - 1));
|
|
27
|
-
}
|
|
28
|
-
else if (key.downArrow || input === 'j') {
|
|
29
|
-
setSelectedIndex((prev) => (prev === tools.length - 1 ? 0 : prev + 1));
|
|
30
|
-
}
|
|
31
|
-
else if (key.return) {
|
|
32
|
-
onSelect(tools[selectedIndex]);
|
|
33
|
-
}
|
|
34
|
-
else {
|
|
35
|
-
// Handle number keys for quick selection
|
|
36
|
-
const num = parseInt(input, 10);
|
|
37
|
-
if (!isNaN(num) && num >= 1 && num <= tools.length) {
|
|
38
|
-
onSelect(tools[num - 1]);
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
});
|
|
42
|
-
if (loading) {
|
|
43
|
-
return (_jsx(Box, { children: _jsx(Text, { children: "Loading tools..." }) }));
|
|
44
|
-
}
|
|
45
|
-
if (error) {
|
|
46
|
-
return (_jsx(Box, { children: _jsxs(Text, { color: "red", children: ["Error: ", error] }) }));
|
|
47
|
-
}
|
|
48
|
-
if (tools.length === 0) {
|
|
49
|
-
return (_jsx(Box, { children: _jsx(Text, { children: "No tools available" }) }));
|
|
50
|
-
}
|
|
51
|
-
const selectedTool = tools[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 Tool" }) }), _jsx(Box, { marginBottom: 1, children: _jsx(Text, { dimColor: true, children: "Choose a tool to start a conversation with" }) }), _jsx(Box, { flexDirection: "column", children: tools.map((tool, index) => (_jsx(Box, { marginBottom: 0, children: _jsxs(Text, { color: index === selectedIndex ? 'green' : undefined, children: [index === selectedIndex ? '❯ ' : ' ', index + 1, ". ", tool.name] }) }, tool.name))) }), selectedTool && selectedTool.description && (_jsx(Box, { marginTop: 1, paddingLeft: 2, children: _jsx(Text, { dimColor: true, wrap: "wrap", children: selectedTool.description }) })), _jsx(Box, { marginTop: 1, children: _jsx(Text, { dimColor: true, children: "Enter to confirm \u00B7 Number to select \u00B7 Esc to exit" }) })] }));
|
|
53
|
-
}
|