@agents-at-scale/ark 0.1.36 → 0.1.38
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/README.md +53 -70
- package/dist/arkServices.d.ts +3 -27
- package/dist/arkServices.js +31 -3
- package/dist/arkServices.spec.js +118 -10
- package/dist/commands/chat/index.js +1 -2
- package/dist/commands/completion/index.js +0 -2
- package/dist/commands/generate/generators/project.js +33 -26
- package/dist/commands/generate/index.js +2 -2
- package/dist/commands/generate/templateDiscovery.js +13 -4
- package/dist/commands/install/index.js +49 -58
- package/dist/commands/models/create.d.ts +9 -1
- package/dist/commands/models/create.js +97 -90
- package/dist/commands/models/create.spec.js +9 -37
- package/dist/commands/models/index.js +8 -2
- package/dist/commands/models/index.spec.js +1 -1
- package/dist/commands/status/index.d.ts +3 -1
- package/dist/commands/status/index.js +54 -2
- package/dist/components/AsyncOperation.d.ts +54 -0
- package/dist/components/AsyncOperation.js +110 -0
- package/dist/components/ChatUI.js +39 -72
- package/dist/components/SelectMenu.d.ts +17 -0
- package/dist/components/SelectMenu.js +21 -0
- package/dist/components/StatusMessage.d.ts +20 -0
- package/dist/components/StatusMessage.js +13 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/lib/arkApiClient.d.ts +1 -2
- package/dist/lib/arkApiClient.js +5 -6
- package/dist/lib/config.d.ts +4 -0
- package/dist/lib/config.js +9 -0
- package/dist/lib/nextSteps.js +1 -1
- package/dist/lib/nextSteps.spec.js +1 -1
- package/dist/lib/security.js +4 -0
- package/dist/lib/startup.js +6 -2
- package/dist/lib/startup.spec.js +1 -1
- package/dist/lib/timeout.d.ts +1 -0
- package/dist/lib/timeout.js +20 -0
- package/dist/lib/timeout.spec.d.ts +1 -0
- package/dist/lib/timeout.spec.js +14 -0
- package/dist/lib/waitForReady.d.ts +8 -0
- package/dist/lib/waitForReady.js +32 -0
- package/dist/lib/waitForReady.spec.d.ts +1 -0
- package/dist/lib/waitForReady.spec.js +104 -0
- package/dist/types/arkService.d.ts +27 -0
- package/dist/types/arkService.js +1 -0
- package/dist/ui/asyncOperations/connectingToArk.d.ts +15 -0
- package/dist/ui/asyncOperations/connectingToArk.js +63 -0
- package/package.json +7 -5
- package/templates/agent/agent.template.yaml +27 -0
- package/templates/marketplace/.editorconfig +24 -0
- package/templates/marketplace/.github/.keep +11 -0
- package/templates/marketplace/.github/workflows/.keep +16 -0
- package/templates/marketplace/.helmignore +23 -0
- package/templates/marketplace/.prettierrc.json +20 -0
- package/templates/marketplace/.yamllint.yml +53 -0
- package/templates/marketplace/README.md +197 -0
- package/templates/marketplace/agents/.keep +29 -0
- package/templates/marketplace/docs/.keep +19 -0
- package/templates/marketplace/mcp-servers/.keep +32 -0
- package/templates/marketplace/models/.keep +23 -0
- package/templates/marketplace/projects/.keep +43 -0
- package/templates/marketplace/queries/.keep +25 -0
- package/templates/marketplace/teams/.keep +29 -0
- package/templates/marketplace/tools/.keep +32 -0
- package/templates/marketplace/tools/examples/.keep +17 -0
- package/templates/mcp-server/Dockerfile +133 -0
- package/templates/mcp-server/Makefile +186 -0
- package/templates/mcp-server/README.md +178 -0
- package/templates/mcp-server/build.sh +76 -0
- package/templates/mcp-server/chart/Chart.yaml +22 -0
- package/templates/mcp-server/chart/templates/_helpers.tpl +62 -0
- package/templates/mcp-server/chart/templates/deployment.yaml +80 -0
- package/templates/mcp-server/chart/templates/hpa.yaml +32 -0
- package/templates/mcp-server/chart/templates/mcpserver.yaml +21 -0
- package/templates/mcp-server/chart/templates/secret.yaml +11 -0
- package/templates/mcp-server/chart/templates/service.yaml +15 -0
- package/templates/mcp-server/chart/templates/serviceaccount.yaml +13 -0
- package/templates/mcp-server/chart/values.yaml +84 -0
- package/templates/mcp-server/example-values.yaml +74 -0
- package/templates/mcp-server/examples/{{ .Values.mcpServerName }}-agent.yaml +33 -0
- package/templates/mcp-server/examples/{{ .Values.mcpServerName }}-query.yaml +24 -0
- package/templates/models/azure.yaml +33 -0
- package/templates/models/claude.yaml +28 -0
- package/templates/models/gemini.yaml +28 -0
- package/templates/models/openai.yaml +39 -0
- package/templates/project/.editorconfig +24 -0
- package/templates/project/.helmignore +24 -0
- package/templates/project/.prettierrc.json +16 -0
- package/templates/project/.yamllint.yml +50 -0
- package/templates/project/Chart.yaml +19 -0
- package/templates/project/Makefile +360 -0
- package/templates/project/README.md +377 -0
- package/templates/project/agents/.keep +11 -0
- package/templates/project/docs/.keep +14 -0
- package/templates/project/mcp-servers/.keep +34 -0
- package/templates/project/models/.keep +17 -0
- package/templates/project/queries/.keep +11 -0
- package/templates/project/scripts/setup.sh +108 -0
- package/templates/project/teams/.keep +11 -0
- package/templates/project/templates/00-rbac.yaml +168 -0
- package/templates/project/templates/01-models.yaml +11 -0
- package/templates/project/templates/02-mcp-servers.yaml +22 -0
- package/templates/project/templates/03-tools.yaml +12 -0
- package/templates/project/templates/04-agents.yaml +12 -0
- package/templates/project/templates/05-teams.yaml +11 -0
- package/templates/project/templates/06-queries.yaml +11 -0
- package/templates/project/templates/_helpers.tpl +91 -0
- package/templates/project/tests/e2e/.keep +10 -0
- package/templates/project/tests/unit/.keep +10 -0
- package/templates/project/tools/.keep +25 -0
- package/templates/project/tools/example-tool.yaml.disabled +94 -0
- package/templates/project/tools/examples/data-tool/Dockerfile +32 -0
- package/templates/project/values.yaml +141 -0
- package/templates/query/query.template.yaml +13 -0
- package/templates/team/team.template.yaml +17 -0
- package/templates/tool/.python-version +1 -0
- package/templates/tool/Dockerfile +23 -0
- package/templates/tool/README.md +238 -0
- package/templates/tool/agent.yaml +19 -0
- package/templates/tool/deploy.sh +10 -0
- package/templates/tool/deployment/deployment.yaml +31 -0
- package/templates/tool/deployment/kustomization.yaml +7 -0
- package/templates/tool/deployment/mcpserver.yaml +12 -0
- package/templates/tool/deployment/service.yaml +12 -0
- package/templates/tool/deployment/serviceaccount.yaml +8 -0
- package/templates/tool/deployment/values.yaml +3 -0
- package/templates/tool/pyproject.toml +9 -0
- package/templates/tool/src/main.py +36 -0
- package/templates/tool/uv.lock +498 -0
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
export interface AsyncOperationConfig {
|
|
3
|
+
/** Message to display during operation */
|
|
4
|
+
message: string;
|
|
5
|
+
/** Async function to execute */
|
|
6
|
+
operation: (signal: AbortSignal) => Promise<void>;
|
|
7
|
+
/** Optional tip displayed below message */
|
|
8
|
+
tip?: string;
|
|
9
|
+
/** Show "(esc to interrupt)" hint */
|
|
10
|
+
showInterrupt?: boolean;
|
|
11
|
+
/** Hide UI on success */
|
|
12
|
+
hideOnSuccess?: boolean;
|
|
13
|
+
/** Called when operation fails */
|
|
14
|
+
onError?: (error: Error) => void;
|
|
15
|
+
/** Error menu options with callbacks */
|
|
16
|
+
errorOptions?: Array<{
|
|
17
|
+
label: string;
|
|
18
|
+
onSelect: () => void;
|
|
19
|
+
}>;
|
|
20
|
+
}
|
|
21
|
+
interface AsyncOperationState {
|
|
22
|
+
/** Current operation state - idle: not running/cleared, loading: executing, success: completed, error: failed */
|
|
23
|
+
status: 'idle' | 'loading' | 'success' | 'error';
|
|
24
|
+
/** Message shown during loading and success */
|
|
25
|
+
message: string;
|
|
26
|
+
/** Optional tip text shown below message during loading */
|
|
27
|
+
tip?: string;
|
|
28
|
+
/** Brief error message when status is error */
|
|
29
|
+
error?: string;
|
|
30
|
+
/** Full error details/stack trace when status is error */
|
|
31
|
+
errorDetails?: string;
|
|
32
|
+
/** Whether to show interrupt hint during loading */
|
|
33
|
+
showInterrupt: boolean;
|
|
34
|
+
/** Whether to hide UI after success */
|
|
35
|
+
hideOnSuccess: boolean;
|
|
36
|
+
/** Menu options shown when status is error */
|
|
37
|
+
errorOptions: Array<{
|
|
38
|
+
label: string;
|
|
39
|
+
onSelect: () => void;
|
|
40
|
+
}>;
|
|
41
|
+
}
|
|
42
|
+
export declare function useAsyncOperation(): {
|
|
43
|
+
state: AsyncOperationState;
|
|
44
|
+
run: (config: AsyncOperationConfig) => Promise<void>;
|
|
45
|
+
interrupt: () => void;
|
|
46
|
+
retry: () => void;
|
|
47
|
+
clear: () => void;
|
|
48
|
+
};
|
|
49
|
+
export type AsyncOperation = ReturnType<typeof useAsyncOperation>;
|
|
50
|
+
interface AsyncOperationStatusProps {
|
|
51
|
+
operation: AsyncOperation;
|
|
52
|
+
}
|
|
53
|
+
export declare const AsyncOperationStatus: React.FC<AsyncOperationStatusProps>;
|
|
54
|
+
export {};
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { useInput } from 'ink';
|
|
3
|
+
import * as React from 'react';
|
|
4
|
+
import { StatusMessage } from './StatusMessage.js';
|
|
5
|
+
import { SelectMenu } from './SelectMenu.js';
|
|
6
|
+
export function useAsyncOperation() {
|
|
7
|
+
const [state, setState] = React.useState({
|
|
8
|
+
status: 'idle',
|
|
9
|
+
message: '',
|
|
10
|
+
showInterrupt: false,
|
|
11
|
+
hideOnSuccess: false,
|
|
12
|
+
errorOptions: [],
|
|
13
|
+
});
|
|
14
|
+
const abortControllerRef = React.useRef(null);
|
|
15
|
+
const configRef = React.useRef(null);
|
|
16
|
+
const run = React.useCallback(async (config) => {
|
|
17
|
+
configRef.current = config;
|
|
18
|
+
const retry = () => {
|
|
19
|
+
if (configRef.current) {
|
|
20
|
+
run(configRef.current);
|
|
21
|
+
}
|
|
22
|
+
};
|
|
23
|
+
const errorOptions = config.errorOptions || [
|
|
24
|
+
{ label: 'Try again', onSelect: retry },
|
|
25
|
+
{ label: 'Quit', onSelect: () => process.exit(0) },
|
|
26
|
+
];
|
|
27
|
+
setState({
|
|
28
|
+
status: 'loading',
|
|
29
|
+
message: config.message,
|
|
30
|
+
tip: config.tip,
|
|
31
|
+
showInterrupt: config.showInterrupt ?? false,
|
|
32
|
+
hideOnSuccess: config.hideOnSuccess ?? false,
|
|
33
|
+
errorOptions,
|
|
34
|
+
});
|
|
35
|
+
const controller = new AbortController();
|
|
36
|
+
abortControllerRef.current = controller;
|
|
37
|
+
try {
|
|
38
|
+
await config.operation(controller.signal);
|
|
39
|
+
setState((prev) => ({ ...prev, status: 'success' }));
|
|
40
|
+
}
|
|
41
|
+
catch (err) {
|
|
42
|
+
if (err instanceof Error && err.name === 'AbortError') {
|
|
43
|
+
setState((prev) => ({ ...prev, status: 'idle' }));
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
const error = err instanceof Error ? err : new Error(String(err));
|
|
47
|
+
setState((prev) => ({
|
|
48
|
+
...prev,
|
|
49
|
+
status: 'error',
|
|
50
|
+
error: error.message,
|
|
51
|
+
errorDetails: error.stack,
|
|
52
|
+
}));
|
|
53
|
+
config.onError?.(error);
|
|
54
|
+
}
|
|
55
|
+
finally {
|
|
56
|
+
abortControllerRef.current = null;
|
|
57
|
+
}
|
|
58
|
+
}, []);
|
|
59
|
+
const interrupt = React.useCallback(() => {
|
|
60
|
+
abortControllerRef.current?.abort();
|
|
61
|
+
abortControllerRef.current = null;
|
|
62
|
+
setState((prev) => ({ ...prev, status: 'idle' }));
|
|
63
|
+
}, []);
|
|
64
|
+
const retry = React.useCallback(() => {
|
|
65
|
+
if (configRef.current) {
|
|
66
|
+
run(configRef.current);
|
|
67
|
+
}
|
|
68
|
+
}, [run]);
|
|
69
|
+
const clear = React.useCallback(() => {
|
|
70
|
+
setState({
|
|
71
|
+
status: 'idle',
|
|
72
|
+
message: '',
|
|
73
|
+
showInterrupt: false,
|
|
74
|
+
hideOnSuccess: false,
|
|
75
|
+
errorOptions: [],
|
|
76
|
+
});
|
|
77
|
+
}, []);
|
|
78
|
+
return {
|
|
79
|
+
state,
|
|
80
|
+
run,
|
|
81
|
+
interrupt,
|
|
82
|
+
retry,
|
|
83
|
+
clear,
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
export const AsyncOperationStatus = ({ operation, }) => {
|
|
87
|
+
const { state } = operation;
|
|
88
|
+
useInput((input, key) => {
|
|
89
|
+
if (state.status === 'loading' && state.showInterrupt && key.escape) {
|
|
90
|
+
operation.interrupt();
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
});
|
|
94
|
+
if (state.status === 'idle') {
|
|
95
|
+
return null;
|
|
96
|
+
}
|
|
97
|
+
if (state.status === 'success' && state.hideOnSuccess) {
|
|
98
|
+
return null;
|
|
99
|
+
}
|
|
100
|
+
if (state.status === 'loading') {
|
|
101
|
+
return (_jsx(StatusMessage, { status: "loading", message: state.message, hint: state.showInterrupt ? '(esc to interrupt)' : undefined, tip: state.tip }));
|
|
102
|
+
}
|
|
103
|
+
if (state.status === 'success') {
|
|
104
|
+
return _jsx(StatusMessage, { status: "success", message: state.message });
|
|
105
|
+
}
|
|
106
|
+
if (state.status === 'error') {
|
|
107
|
+
return (_jsx(StatusMessage, { status: "error", message: state.message, details: state.error, errorMessage: state.errorDetails, children: _jsx(SelectMenu, { items: state.errorOptions }) }));
|
|
108
|
+
}
|
|
109
|
+
return null;
|
|
110
|
+
};
|
|
@@ -2,16 +2,17 @@ import { jsxs as _jsxs, jsx as _jsx, Fragment as _Fragment } from "react/jsx-run
|
|
|
2
2
|
import { Box, Text, useInput, useApp } from 'ink';
|
|
3
3
|
import TextInput from 'ink-text-input';
|
|
4
4
|
import Spinner from 'ink-spinner';
|
|
5
|
-
import chalk from 'chalk';
|
|
6
5
|
import * as React from 'react';
|
|
7
6
|
import { marked } from 'marked';
|
|
8
7
|
// @ts-ignore - no types available
|
|
9
8
|
import TerminalRenderer from 'marked-terminal';
|
|
10
|
-
import {
|
|
9
|
+
import { APIError } from 'openai';
|
|
11
10
|
import { AgentSelector } from '../ui/AgentSelector.js';
|
|
12
11
|
import { ModelSelector } from '../ui/ModelSelector.js';
|
|
13
12
|
import { TeamSelector } from '../ui/TeamSelector.js';
|
|
14
13
|
import { ToolSelector } from '../ui/ToolSelector.js';
|
|
14
|
+
import { useAsyncOperation, AsyncOperationStatus } from './AsyncOperation.js';
|
|
15
|
+
import { createConnectingToArkOperation } from '../ui/asyncOperations/connectingToArk.js';
|
|
15
16
|
// Generate a unique ID for messages
|
|
16
17
|
let messageIdCounter = 0;
|
|
17
18
|
const generateMessageId = () => {
|
|
@@ -30,13 +31,13 @@ const configureMarkdown = () => {
|
|
|
30
31
|
};
|
|
31
32
|
const ChatUI = ({ initialTargetId, arkApiClient, arkApiProxy, config, }) => {
|
|
32
33
|
const { exit } = useApp();
|
|
34
|
+
const asyncOp = useAsyncOperation();
|
|
33
35
|
const [messages, setMessages] = React.useState([]);
|
|
34
36
|
const [input, setInput] = React.useState('');
|
|
35
37
|
const [isTyping, setIsTyping] = React.useState(false);
|
|
36
38
|
const [target, setTarget] = React.useState(null);
|
|
37
39
|
const [availableTargets, setAvailableTargets] = React.useState([]);
|
|
38
40
|
const [error, setError] = React.useState(null);
|
|
39
|
-
const [isLoading, setIsLoading] = React.useState(true);
|
|
40
41
|
const [targetIndex, setTargetIndex] = React.useState(0);
|
|
41
42
|
const [abortController, setAbortController] = React.useState(null);
|
|
42
43
|
const [showCommands, setShowCommands] = React.useState(false);
|
|
@@ -64,78 +65,26 @@ const ChatUI = ({ initialTargetId, arkApiClient, arkApiProxy, config, }) => {
|
|
|
64
65
|
}, [outputFormat]);
|
|
65
66
|
// Initialize chat client and fetch targets on mount
|
|
66
67
|
React.useEffect(() => {
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
68
|
+
asyncOp.run(createConnectingToArkOperation({
|
|
69
|
+
arkApiClient,
|
|
70
|
+
initialTargetId,
|
|
71
|
+
onSuccess: ({ client, targets, selectedTarget, selectedIndex }) => {
|
|
71
72
|
chatClientRef.current = client;
|
|
72
|
-
const targets = await client.getQueryTargets();
|
|
73
73
|
setAvailableTargets(targets);
|
|
74
|
-
if (
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
setTarget(matchedTarget);
|
|
80
|
-
setTargetIndex(matchedIndex >= 0 ? matchedIndex : 0);
|
|
81
|
-
setChatConfig((prev) => ({ ...prev, currentTarget: matchedTarget }));
|
|
82
|
-
setMessages([]);
|
|
83
|
-
}
|
|
84
|
-
else {
|
|
85
|
-
// If target not found, show error and exit
|
|
86
|
-
console.error(chalk.red('Error:'), `Target "${initialTargetId}" not found`);
|
|
87
|
-
console.error(chalk.gray('Use "ark targets list" to see available targets'));
|
|
88
|
-
if (arkApiProxy) {
|
|
89
|
-
arkApiProxy.stop();
|
|
90
|
-
}
|
|
91
|
-
exit();
|
|
92
|
-
}
|
|
93
|
-
}
|
|
94
|
-
else if (targets.length > 0) {
|
|
95
|
-
// No initial target specified - auto-select first available
|
|
96
|
-
// Priority: agents > models > tools
|
|
97
|
-
const agents = targets.filter((t) => t.type === 'agent');
|
|
98
|
-
const models = targets.filter((t) => t.type === 'model');
|
|
99
|
-
const tools = targets.filter((t) => t.type === 'tool');
|
|
100
|
-
let selectedTarget = null;
|
|
101
|
-
let selectedIndex = 0;
|
|
102
|
-
if (agents.length > 0) {
|
|
103
|
-
selectedTarget = agents[0];
|
|
104
|
-
selectedIndex = targets.findIndex((t) => t.id === agents[0].id);
|
|
105
|
-
}
|
|
106
|
-
else if (models.length > 0) {
|
|
107
|
-
selectedTarget = models[0];
|
|
108
|
-
selectedIndex = targets.findIndex((t) => t.id === models[0].id);
|
|
109
|
-
}
|
|
110
|
-
else if (tools.length > 0) {
|
|
111
|
-
selectedTarget = tools[0];
|
|
112
|
-
selectedIndex = targets.findIndex((t) => t.id === tools[0].id);
|
|
113
|
-
}
|
|
114
|
-
if (selectedTarget) {
|
|
115
|
-
setTarget(selectedTarget);
|
|
116
|
-
setTargetIndex(selectedIndex);
|
|
117
|
-
setChatConfig((prev) => ({ ...prev, currentTarget: selectedTarget }));
|
|
118
|
-
setMessages([]);
|
|
119
|
-
}
|
|
120
|
-
else {
|
|
121
|
-
setError('No targets available');
|
|
122
|
-
}
|
|
74
|
+
if (selectedTarget) {
|
|
75
|
+
setTarget(selectedTarget);
|
|
76
|
+
setTargetIndex(selectedIndex);
|
|
77
|
+
setChatConfig((prev) => ({ ...prev, currentTarget: selectedTarget }));
|
|
78
|
+
setMessages([]);
|
|
123
79
|
}
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
}
|
|
127
|
-
setIsLoading(false);
|
|
128
|
-
}
|
|
129
|
-
catch (err) {
|
|
130
|
-
const errorMessage = err instanceof Error ? err.message : 'Failed to initialize chat';
|
|
131
|
-
console.error(chalk.red('Error:'), errorMessage);
|
|
80
|
+
},
|
|
81
|
+
onQuit: () => {
|
|
132
82
|
if (arkApiProxy) {
|
|
133
83
|
arkApiProxy.stop();
|
|
134
84
|
}
|
|
135
85
|
exit();
|
|
136
|
-
}
|
|
137
|
-
};
|
|
138
|
-
initializeChat();
|
|
86
|
+
},
|
|
87
|
+
}));
|
|
139
88
|
// Cleanup function to close port forward when component unmounts
|
|
140
89
|
return () => {
|
|
141
90
|
if (arkApiProxy) {
|
|
@@ -548,7 +497,25 @@ const ChatUI = ({ initialTargetId, arkApiClient, arkApiProxy, config, }) => {
|
|
|
548
497
|
// Request was cancelled, message already updated by Esc handler
|
|
549
498
|
return;
|
|
550
499
|
}
|
|
551
|
-
|
|
500
|
+
let errorMessage = 'Failed to send message';
|
|
501
|
+
// OpenAI SDK errors include response body in .error property
|
|
502
|
+
if (err instanceof APIError) {
|
|
503
|
+
if (err.error && typeof err.error === 'object') {
|
|
504
|
+
const errorObj = err.error;
|
|
505
|
+
errorMessage = errorObj.message || JSON.stringify(err.error, null, 2);
|
|
506
|
+
}
|
|
507
|
+
else {
|
|
508
|
+
errorMessage = err.message;
|
|
509
|
+
}
|
|
510
|
+
}
|
|
511
|
+
// Standard JavaScript errors
|
|
512
|
+
else if (err instanceof Error) {
|
|
513
|
+
errorMessage = err.message;
|
|
514
|
+
}
|
|
515
|
+
// String errors from throw statements
|
|
516
|
+
else if (typeof err === 'string') {
|
|
517
|
+
errorMessage = err;
|
|
518
|
+
}
|
|
552
519
|
setError(errorMessage);
|
|
553
520
|
setIsTyping(false);
|
|
554
521
|
setAbortController(null);
|
|
@@ -631,9 +598,9 @@ const ChatUI = ({ initialTargetId, arkApiClient, arkApiProxy, config, }) => {
|
|
|
631
598
|
}
|
|
632
599
|
})() }) }))] }, toolIndex)))] }));
|
|
633
600
|
};
|
|
634
|
-
// Show
|
|
635
|
-
if (
|
|
636
|
-
return
|
|
601
|
+
// Show async operation status (connection, etc.)
|
|
602
|
+
if (asyncOp.state.status === 'loading' || asyncOp.state.status === 'error') {
|
|
603
|
+
return _jsx(AsyncOperationStatus, { operation: asyncOp });
|
|
637
604
|
}
|
|
638
605
|
// Show error if no targets available
|
|
639
606
|
if (!target && error) {
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
export interface SelectMenuItem {
|
|
3
|
+
/** Menu item label */
|
|
4
|
+
label: string;
|
|
5
|
+
/** Optional description shown in gray */
|
|
6
|
+
description?: string;
|
|
7
|
+
/** Called when item is selected */
|
|
8
|
+
onSelect: () => void;
|
|
9
|
+
}
|
|
10
|
+
interface SelectMenuProps {
|
|
11
|
+
/** Menu items to display */
|
|
12
|
+
items: SelectMenuItem[];
|
|
13
|
+
/** Initial selected index (default: 0) */
|
|
14
|
+
initialIndex?: number;
|
|
15
|
+
}
|
|
16
|
+
export declare const SelectMenu: React.FC<SelectMenuProps>;
|
|
17
|
+
export {};
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { Box, Text, useInput } from 'ink';
|
|
3
|
+
import * as React from 'react';
|
|
4
|
+
export const SelectMenu = ({ items, initialIndex = 0, }) => {
|
|
5
|
+
const [selectedIndex, setSelectedIndex] = React.useState(initialIndex);
|
|
6
|
+
useInput((input, key) => {
|
|
7
|
+
if (key.upArrow || input === 'k') {
|
|
8
|
+
setSelectedIndex((prev) => (prev > 0 ? prev - 1 : items.length - 1));
|
|
9
|
+
}
|
|
10
|
+
else if (key.downArrow || input === 'j') {
|
|
11
|
+
setSelectedIndex((prev) => (prev < items.length - 1 ? prev + 1 : 0));
|
|
12
|
+
}
|
|
13
|
+
else if (key.return) {
|
|
14
|
+
items[selectedIndex].onSelect();
|
|
15
|
+
}
|
|
16
|
+
});
|
|
17
|
+
return (_jsx(Box, { flexDirection: "column", children: items.map((item, index) => {
|
|
18
|
+
const isSelected = index === selectedIndex;
|
|
19
|
+
return (_jsxs(Box, { flexDirection: "row", children: [_jsx(Text, { color: isSelected ? 'green' : 'gray', children: isSelected ? '❯ ' : ' ' }), _jsx(Text, { color: isSelected ? 'green' : 'white', children: item.label }), item.description && (_jsxs(_Fragment, { children: [_jsx(Text, { children: " " }), _jsx(Text, { color: "gray", children: item.description })] }))] }, index));
|
|
20
|
+
}) }));
|
|
21
|
+
};
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
export type StatusType = 'loading' | 'success' | 'error' | 'info';
|
|
3
|
+
interface StatusMessageProps {
|
|
4
|
+
/** Status type determines icon and color */
|
|
5
|
+
status: StatusType;
|
|
6
|
+
/** Main message text (bold) */
|
|
7
|
+
message: string;
|
|
8
|
+
/** Optional hint shown in gray next to message */
|
|
9
|
+
hint?: string;
|
|
10
|
+
/** Optional details shown indented with ⎿ prefix */
|
|
11
|
+
details?: string;
|
|
12
|
+
/** Optional full error message shown below details */
|
|
13
|
+
errorMessage?: string;
|
|
14
|
+
/** Optional tip shown indented with ⎿ prefix */
|
|
15
|
+
tip?: string;
|
|
16
|
+
/** Optional content rendered below message */
|
|
17
|
+
children?: React.ReactNode;
|
|
18
|
+
}
|
|
19
|
+
export declare const StatusMessage: React.FC<StatusMessageProps>;
|
|
20
|
+
export {};
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { Box, Text } from 'ink';
|
|
3
|
+
import Spinner from 'ink-spinner';
|
|
4
|
+
export const StatusMessage = ({ status, message, hint, details, errorMessage, tip, children, }) => {
|
|
5
|
+
const statusConfig = {
|
|
6
|
+
loading: { icon: _jsx(Spinner, { type: "dots" }), color: 'yellow' },
|
|
7
|
+
success: { icon: '✓', color: 'green' },
|
|
8
|
+
error: { icon: '✗', color: 'red' },
|
|
9
|
+
info: { icon: '●', color: 'cyan' },
|
|
10
|
+
};
|
|
11
|
+
const config = statusConfig[status];
|
|
12
|
+
return (_jsxs(Box, { flexDirection: "column", marginBottom: 1, children: [_jsxs(Box, { children: [_jsx(Text, { color: config.color, children: typeof config.icon === 'string' ? config.icon : config.icon }), _jsx(Text, { children: " " }), _jsx(Text, { color: config.color, bold: true, children: message }), hint && _jsxs(Text, { color: "gray", children: [" ", hint] })] }), details && (_jsx(Box, { marginLeft: 2, children: _jsxs(Text, { color: "gray", children: ["\u23BF ", details] }) })), errorMessage && (_jsx(Box, { marginLeft: 2, children: _jsx(Text, { color: "gray", children: errorMessage }) })), tip && (_jsx(Box, { marginLeft: 2, children: _jsxs(Text, { color: "gray", children: ["\u23BF ", tip] }) })), children && _jsx(Box, { marginLeft: 2, children: children })] }));
|
|
13
|
+
};
|
package/dist/index.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
#!/usr/bin/env
|
|
1
|
+
#!/usr/bin/env node
|
|
2
2
|
export {};
|
package/dist/index.js
CHANGED
|
@@ -40,8 +40,7 @@ export interface Team {
|
|
|
40
40
|
export declare class ArkApiClient {
|
|
41
41
|
private openai;
|
|
42
42
|
private baseUrl;
|
|
43
|
-
|
|
44
|
-
constructor(arkApiUrl: string, namespace?: string);
|
|
43
|
+
constructor(arkApiUrl: string);
|
|
45
44
|
getBaseUrl(): string;
|
|
46
45
|
getQueryTargets(): Promise<QueryTarget[]>;
|
|
47
46
|
getAgents(): Promise<Agent[]>;
|
package/dist/lib/arkApiClient.js
CHANGED
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
import OpenAI from 'openai';
|
|
2
2
|
export class ArkApiClient {
|
|
3
|
-
constructor(arkApiUrl
|
|
3
|
+
constructor(arkApiUrl) {
|
|
4
4
|
this.baseUrl = arkApiUrl;
|
|
5
|
-
this.namespace = namespace;
|
|
6
5
|
this.openai = new OpenAI({
|
|
7
6
|
baseURL: `${arkApiUrl}/openai/v1`,
|
|
8
7
|
apiKey: 'dummy', // ark-api doesn't require an API key
|
|
@@ -34,7 +33,7 @@ export class ArkApiClient {
|
|
|
34
33
|
}
|
|
35
34
|
async getAgents() {
|
|
36
35
|
try {
|
|
37
|
-
const response = await fetch(`${this.baseUrl}/v1/
|
|
36
|
+
const response = await fetch(`${this.baseUrl}/v1/agents`);
|
|
38
37
|
if (!response.ok) {
|
|
39
38
|
throw new Error(`HTTP error! status: ${response.status}`);
|
|
40
39
|
}
|
|
@@ -47,7 +46,7 @@ export class ArkApiClient {
|
|
|
47
46
|
}
|
|
48
47
|
async getModels() {
|
|
49
48
|
try {
|
|
50
|
-
const response = await fetch(`${this.baseUrl}/v1/
|
|
49
|
+
const response = await fetch(`${this.baseUrl}/v1/models`);
|
|
51
50
|
if (!response.ok) {
|
|
52
51
|
throw new Error(`HTTP error! status: ${response.status}`);
|
|
53
52
|
}
|
|
@@ -60,7 +59,7 @@ export class ArkApiClient {
|
|
|
60
59
|
}
|
|
61
60
|
async getTools() {
|
|
62
61
|
try {
|
|
63
|
-
const response = await fetch(`${this.baseUrl}/v1/
|
|
62
|
+
const response = await fetch(`${this.baseUrl}/v1/tools`);
|
|
64
63
|
if (!response.ok) {
|
|
65
64
|
throw new Error(`HTTP error! status: ${response.status}`);
|
|
66
65
|
}
|
|
@@ -73,7 +72,7 @@ export class ArkApiClient {
|
|
|
73
72
|
}
|
|
74
73
|
async getTeams() {
|
|
75
74
|
try {
|
|
76
|
-
const response = await fetch(`${this.baseUrl}/v1/
|
|
75
|
+
const response = await fetch(`${this.baseUrl}/v1/teams`);
|
|
77
76
|
if (!response.ok) {
|
|
78
77
|
throw new Error(`HTTP error! status: ${response.status}`);
|
|
79
78
|
}
|
package/dist/lib/config.d.ts
CHANGED
|
@@ -1,10 +1,14 @@
|
|
|
1
1
|
import type { ClusterInfo } from './cluster.js';
|
|
2
|
+
import type { ArkService } from '../types/arkService.js';
|
|
2
3
|
export interface ChatConfig {
|
|
3
4
|
streaming?: boolean;
|
|
4
5
|
outputFormat?: 'text' | 'markdown';
|
|
5
6
|
}
|
|
6
7
|
export interface ArkConfig {
|
|
7
8
|
chat?: ChatConfig;
|
|
9
|
+
services?: {
|
|
10
|
+
[serviceName: string]: Partial<ArkService>;
|
|
11
|
+
};
|
|
8
12
|
clusterInfo?: ClusterInfo;
|
|
9
13
|
}
|
|
10
14
|
/**
|
package/dist/lib/config.js
CHANGED
|
@@ -70,6 +70,15 @@ function mergeConfig(target, source) {
|
|
|
70
70
|
target.chat.outputFormat = source.chat.outputFormat;
|
|
71
71
|
}
|
|
72
72
|
}
|
|
73
|
+
if (source.services) {
|
|
74
|
+
target.services = target.services || {};
|
|
75
|
+
for (const [serviceName, overrides] of Object.entries(source.services)) {
|
|
76
|
+
target.services[serviceName] = {
|
|
77
|
+
...target.services[serviceName],
|
|
78
|
+
...overrides,
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
}
|
|
73
82
|
}
|
|
74
83
|
/**
|
|
75
84
|
* Get the paths checked for config files
|
package/dist/lib/nextSteps.js
CHANGED
|
@@ -4,7 +4,7 @@ import chalk from 'chalk';
|
|
|
4
4
|
*/
|
|
5
5
|
export function printNextSteps() {
|
|
6
6
|
console.log();
|
|
7
|
-
console.log(chalk.green.bold('✓
|
|
7
|
+
console.log(chalk.green.bold('✓ Installation complete'));
|
|
8
8
|
console.log();
|
|
9
9
|
console.log(chalk.gray('Next steps:'));
|
|
10
10
|
console.log();
|
|
@@ -15,7 +15,7 @@ describe('printNextSteps', () => {
|
|
|
15
15
|
it('prints successful installation message', () => {
|
|
16
16
|
printNextSteps();
|
|
17
17
|
const fullOutput = output.join('\n');
|
|
18
|
-
expect(fullOutput).toContain('
|
|
18
|
+
expect(fullOutput).toContain('✓ Installation complete');
|
|
19
19
|
});
|
|
20
20
|
it('includes all required commands', () => {
|
|
21
21
|
printNextSteps();
|
package/dist/lib/security.js
CHANGED
|
@@ -9,6 +9,10 @@ export class SecurityUtils {
|
|
|
9
9
|
* Validate that a path is safe and doesn't contain directory traversal attempts
|
|
10
10
|
*/
|
|
11
11
|
static validatePath(filePath, context = 'path') {
|
|
12
|
+
// Skip validation for internal template paths - they're always safe
|
|
13
|
+
if (context === 'template path') {
|
|
14
|
+
return;
|
|
15
|
+
}
|
|
12
16
|
if (!filePath || typeof filePath !== 'string') {
|
|
13
17
|
throw new ValidationError(`Invalid ${context}: path must be a non-empty string`, 'path', ['Provide a valid file path']);
|
|
14
18
|
}
|
package/dist/lib/startup.js
CHANGED
|
@@ -52,7 +52,9 @@ export function showNoClusterError() {
|
|
|
52
52
|
*/
|
|
53
53
|
async function hasKubernetesContext() {
|
|
54
54
|
try {
|
|
55
|
-
const { stdout } = await execa('kubectl', ['config', 'current-context']
|
|
55
|
+
const { stdout } = await execa('kubectl', ['config', 'current-context'], {
|
|
56
|
+
timeout: 5000,
|
|
57
|
+
});
|
|
56
58
|
return stdout.trim().length > 0;
|
|
57
59
|
}
|
|
58
60
|
catch {
|
|
@@ -72,7 +74,9 @@ export async function startup() {
|
|
|
72
74
|
const hasContext = await hasKubernetesContext();
|
|
73
75
|
if (hasContext) {
|
|
74
76
|
try {
|
|
75
|
-
const { stdout } = await execa('kubectl', ['config', 'current-context']
|
|
77
|
+
const { stdout } = await execa('kubectl', ['config', 'current-context'], {
|
|
78
|
+
timeout: 5000,
|
|
79
|
+
});
|
|
76
80
|
config.clusterInfo = {
|
|
77
81
|
type: 'unknown', // We don't detect cluster type here - too slow
|
|
78
82
|
context: stdout.trim(),
|
package/dist/lib/startup.spec.js
CHANGED
|
@@ -138,7 +138,7 @@ describe('startup', () => {
|
|
|
138
138
|
type: 'unknown',
|
|
139
139
|
context: 'minikube',
|
|
140
140
|
});
|
|
141
|
-
expect(mockExeca).toHaveBeenCalledWith('kubectl', ['config', 'current-context']);
|
|
141
|
+
expect(mockExeca).toHaveBeenCalledWith('kubectl', ['config', 'current-context'], { timeout: 5000 });
|
|
142
142
|
});
|
|
143
143
|
it('handles missing kubectl context gracefully', async () => {
|
|
144
144
|
mockCheckCommandExists.mockResolvedValue(true);
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function parseTimeoutToSeconds(value: string): number;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
export function parseTimeoutToSeconds(value) {
|
|
2
|
+
const match = value.match(/^(\d+)([smh])?$/);
|
|
3
|
+
if (!match) {
|
|
4
|
+
throw new Error('Invalid timeout format. Use format like 30s, 2m, or 1h');
|
|
5
|
+
}
|
|
6
|
+
const num = parseInt(match[1], 10);
|
|
7
|
+
const unit = match[2];
|
|
8
|
+
if (!unit)
|
|
9
|
+
return num;
|
|
10
|
+
switch (unit) {
|
|
11
|
+
case 's':
|
|
12
|
+
return num;
|
|
13
|
+
case 'm':
|
|
14
|
+
return num * 60;
|
|
15
|
+
case 'h':
|
|
16
|
+
return num * 3600;
|
|
17
|
+
default:
|
|
18
|
+
return num;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { describe, it, expect } from '@jest/globals';
|
|
2
|
+
import { parseTimeoutToSeconds } from './timeout.js';
|
|
3
|
+
describe('parseTimeoutToSeconds', () => {
|
|
4
|
+
it('should parse time units correctly', () => {
|
|
5
|
+
expect(parseTimeoutToSeconds('30s')).toBe(30);
|
|
6
|
+
expect(parseTimeoutToSeconds('2m')).toBe(120);
|
|
7
|
+
expect(parseTimeoutToSeconds('1h')).toBe(3600);
|
|
8
|
+
expect(parseTimeoutToSeconds('60')).toBe(60);
|
|
9
|
+
});
|
|
10
|
+
it('should throw error for invalid formats', () => {
|
|
11
|
+
expect(() => parseTimeoutToSeconds('abc')).toThrow('Invalid timeout format');
|
|
12
|
+
expect(() => parseTimeoutToSeconds('-5s')).toThrow('Invalid timeout format');
|
|
13
|
+
});
|
|
14
|
+
});
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { ArkService } from '../types/arkService.js';
|
|
2
|
+
export interface WaitProgress {
|
|
3
|
+
serviceName: string;
|
|
4
|
+
ready: boolean;
|
|
5
|
+
error?: string;
|
|
6
|
+
}
|
|
7
|
+
export declare function waitForDeploymentReady(deploymentName: string, namespace: string, timeoutSeconds: number): Promise<boolean>;
|
|
8
|
+
export declare function waitForServicesReady(services: ArkService[], timeoutSeconds: number, onProgress?: (progress: WaitProgress) => void): Promise<boolean>;
|