@agents-at-scale/ark 0.1.31
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 +95 -0
- package/dist/commands/cluster/get-ip.d.ts +2 -0
- package/dist/commands/cluster/get-ip.js +32 -0
- package/dist/commands/cluster/get-type.d.ts +2 -0
- package/dist/commands/cluster/get-type.js +26 -0
- package/dist/commands/cluster/index.d.ts +2 -0
- package/dist/commands/cluster/index.js +10 -0
- package/dist/commands/completion.d.ts +2 -0
- package/dist/commands/completion.js +108 -0
- package/dist/commands/config.d.ts +5 -0
- package/dist/commands/config.js +327 -0
- package/dist/commands/generate/config.d.ts +145 -0
- package/dist/commands/generate/config.js +253 -0
- package/dist/commands/generate/generators/agent.d.ts +2 -0
- package/dist/commands/generate/generators/agent.js +156 -0
- package/dist/commands/generate/generators/index.d.ts +6 -0
- package/dist/commands/generate/generators/index.js +6 -0
- package/dist/commands/generate/generators/marketplace.d.ts +2 -0
- package/dist/commands/generate/generators/marketplace.js +304 -0
- package/dist/commands/generate/generators/mcpserver.d.ts +25 -0
- package/dist/commands/generate/generators/mcpserver.js +350 -0
- package/dist/commands/generate/generators/project.d.ts +2 -0
- package/dist/commands/generate/generators/project.js +784 -0
- package/dist/commands/generate/generators/query.d.ts +2 -0
- package/dist/commands/generate/generators/query.js +213 -0
- package/dist/commands/generate/generators/team.d.ts +2 -0
- package/dist/commands/generate/generators/team.js +407 -0
- package/dist/commands/generate/index.d.ts +24 -0
- package/dist/commands/generate/index.js +357 -0
- package/dist/commands/generate/templateDiscovery.d.ts +30 -0
- package/dist/commands/generate/templateDiscovery.js +94 -0
- package/dist/commands/generate/templateEngine.d.ts +78 -0
- package/dist/commands/generate/templateEngine.js +368 -0
- package/dist/commands/generate/utils/nameUtils.d.ts +35 -0
- package/dist/commands/generate/utils/nameUtils.js +110 -0
- package/dist/commands/generate/utils/projectUtils.d.ts +28 -0
- package/dist/commands/generate/utils/projectUtils.js +133 -0
- package/dist/components/DashboardCLI.d.ts +3 -0
- package/dist/components/DashboardCLI.js +149 -0
- package/dist/components/GeneratorUI.d.ts +3 -0
- package/dist/components/GeneratorUI.js +167 -0
- package/dist/components/statusChecker.d.ts +48 -0
- package/dist/components/statusChecker.js +251 -0
- package/dist/config.d.ts +42 -0
- package/dist/config.js +243 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +67 -0
- package/dist/lib/arkClient.d.ts +32 -0
- package/dist/lib/arkClient.js +43 -0
- package/dist/lib/cluster.d.ts +8 -0
- package/dist/lib/cluster.js +134 -0
- package/dist/lib/config.d.ts +82 -0
- package/dist/lib/config.js +223 -0
- package/dist/lib/consts.d.ts +10 -0
- package/dist/lib/consts.js +15 -0
- package/dist/lib/errors.d.ts +56 -0
- package/dist/lib/errors.js +208 -0
- package/dist/lib/exec.d.ts +5 -0
- package/dist/lib/exec.js +20 -0
- package/dist/lib/gatewayManager.d.ts +24 -0
- package/dist/lib/gatewayManager.js +85 -0
- package/dist/lib/kubernetes.d.ts +28 -0
- package/dist/lib/kubernetes.js +122 -0
- package/dist/lib/progress.d.ts +128 -0
- package/dist/lib/progress.js +273 -0
- package/dist/lib/security.d.ts +37 -0
- package/dist/lib/security.js +295 -0
- package/dist/lib/types.d.ts +37 -0
- package/dist/lib/types.js +1 -0
- package/dist/lib/wrappers/git.d.ts +2 -0
- package/dist/lib/wrappers/git.js +43 -0
- package/dist/ui/MainMenu.d.ts +3 -0
- package/dist/ui/MainMenu.js +116 -0
- package/dist/ui/statusFormatter.d.ts +9 -0
- package/dist/ui/statusFormatter.js +47 -0
- package/package.json +62 -0
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
3
|
+
import fs from 'fs';
|
|
4
|
+
import path, { dirname } from 'path';
|
|
5
|
+
import { fileURLToPath } from 'url';
|
|
6
|
+
import axios from 'axios';
|
|
7
|
+
import { execa } from 'execa';
|
|
8
|
+
import { Text } from 'ink';
|
|
9
|
+
import open from 'open';
|
|
10
|
+
import { useState, useEffect } from 'react';
|
|
11
|
+
import YAML from 'yaml';
|
|
12
|
+
import { DEFAULT_ADDRESS_ARK_API, DEFAULT_ARK_DASHBOARD_URL, } from '../lib/consts.js';
|
|
13
|
+
const DashboardCLI = () => {
|
|
14
|
+
const [currentStep, setCurrentStep] = useState(0);
|
|
15
|
+
const [status, setStatus] = useState('Running');
|
|
16
|
+
const [dotsIndex, setDotsIndex] = useState(0);
|
|
17
|
+
const dotFrames = ['.', '..', '...'];
|
|
18
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
19
|
+
const __dirname = dirname(__filename);
|
|
20
|
+
const rootDir = path.resolve(__dirname, '../../../');
|
|
21
|
+
const steps = [
|
|
22
|
+
{
|
|
23
|
+
label: 'Building backend',
|
|
24
|
+
path: `${rootDir}/ark-api`,
|
|
25
|
+
checkUrl: `${DEFAULT_ADDRESS_ARK_API}/health`,
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
label: 'Starting frontend',
|
|
29
|
+
path: `${rootDir}/ark-dashboard`,
|
|
30
|
+
checkUrl: DEFAULT_ARK_DASHBOARD_URL,
|
|
31
|
+
},
|
|
32
|
+
];
|
|
33
|
+
async function isServiceUp(url) {
|
|
34
|
+
try {
|
|
35
|
+
const res = await axios.get(url);
|
|
36
|
+
return res.status >= 200 && res.status < 400;
|
|
37
|
+
}
|
|
38
|
+
catch {
|
|
39
|
+
return false;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
async function runCommands(commands, cwd) {
|
|
43
|
+
for (const cmd of commands) {
|
|
44
|
+
// Run each command in the specified working directory
|
|
45
|
+
await execa(cmd, {
|
|
46
|
+
cwd: cwd,
|
|
47
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
48
|
+
shell: true,
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
function loadServiceManifest(servicePath) {
|
|
53
|
+
const manifestPath = path.join(servicePath, 'manifest.yaml');
|
|
54
|
+
const raw = fs.readFileSync(manifestPath, 'utf-8');
|
|
55
|
+
return YAML.parse(raw);
|
|
56
|
+
}
|
|
57
|
+
async function runStep(stepIndex) {
|
|
58
|
+
const step = steps[stepIndex];
|
|
59
|
+
const manifest = loadServiceManifest(step.path);
|
|
60
|
+
// 1. Check if service is already up
|
|
61
|
+
const alreadyUp = await isServiceUp(step.checkUrl);
|
|
62
|
+
if (alreadyUp) {
|
|
63
|
+
setCurrentStep((prev) => prev + 1);
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
// 2. Install the dependencies
|
|
67
|
+
if (manifest.commands?.install) {
|
|
68
|
+
try {
|
|
69
|
+
await runCommands(manifest.commands.install, step.path);
|
|
70
|
+
}
|
|
71
|
+
catch (err) {
|
|
72
|
+
setStatus('Failed');
|
|
73
|
+
console.error(`Error running install commands for ${step.label}`, err);
|
|
74
|
+
process.exit(1);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
// 3. Start the service
|
|
78
|
+
const startCommand = manifest.commands?.dev;
|
|
79
|
+
if (startCommand) {
|
|
80
|
+
const devCommandStr = Array.isArray(startCommand)
|
|
81
|
+
? startCommand.join(' && ')
|
|
82
|
+
: startCommand;
|
|
83
|
+
const serviceProcess = execa(devCommandStr, {
|
|
84
|
+
cwd: step.path,
|
|
85
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
86
|
+
shell: true,
|
|
87
|
+
});
|
|
88
|
+
// 4. Poll until service is ready
|
|
89
|
+
return new Promise((resolve, reject) => {
|
|
90
|
+
let attempts = 0;
|
|
91
|
+
const maxAttempts = 30;
|
|
92
|
+
const interval = 3000;
|
|
93
|
+
const poll = async () => {
|
|
94
|
+
attempts++;
|
|
95
|
+
if (await isServiceUp(step.checkUrl)) {
|
|
96
|
+
setCurrentStep((prev) => prev + 1);
|
|
97
|
+
resolve();
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
if (attempts >= maxAttempts) {
|
|
101
|
+
reject(new Error(`${step.label} did not start in time.`));
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
setTimeout(poll, interval);
|
|
105
|
+
};
|
|
106
|
+
poll();
|
|
107
|
+
serviceProcess.catch((err) => reject(err));
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
async function startDashboardServices() {
|
|
112
|
+
try {
|
|
113
|
+
for (let i = 0; i < steps.length; i++) {
|
|
114
|
+
await runStep(i);
|
|
115
|
+
}
|
|
116
|
+
setStatus('Done');
|
|
117
|
+
await open(steps[steps.length - 1].checkUrl);
|
|
118
|
+
}
|
|
119
|
+
catch (err) {
|
|
120
|
+
setStatus('Failed');
|
|
121
|
+
console.error('Error:', err);
|
|
122
|
+
process.exit(1);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
useEffect(() => {
|
|
126
|
+
startDashboardServices();
|
|
127
|
+
}, []);
|
|
128
|
+
useEffect(() => {
|
|
129
|
+
if (status !== 'Running') {
|
|
130
|
+
return;
|
|
131
|
+
}
|
|
132
|
+
const interval = setInterval(() => {
|
|
133
|
+
setDotsIndex((prev) => (prev + 1) % dotFrames.length);
|
|
134
|
+
}, 300);
|
|
135
|
+
return () => clearInterval(interval);
|
|
136
|
+
}, [status]);
|
|
137
|
+
return (_jsxs(_Fragment, { children: [_jsx(Text, { color: "yellow", children: "\u23F3 Starting Dashboard" }), steps.map((step, i) => {
|
|
138
|
+
if (i < currentStep) {
|
|
139
|
+
return (_jsxs(Text, { color: "green", children: ["\u2705 Step ", i + 1, ": ", step.label] }, i));
|
|
140
|
+
}
|
|
141
|
+
else if (i === currentStep && status === 'Running') {
|
|
142
|
+
return (_jsxs(Text, { children: ["Step ", i + 1, ": ", step.label, dotFrames[dotsIndex]] }, i));
|
|
143
|
+
}
|
|
144
|
+
else {
|
|
145
|
+
return null;
|
|
146
|
+
}
|
|
147
|
+
}), status === 'Done' && (_jsx(Text, { color: "green", children: "\uD83C\uDF89 Dashboard is up and running!" })), status === 'Failed' && (_jsx(Text, { color: "red", children: "\u274C Failed to start Dashboard" }))] }));
|
|
148
|
+
};
|
|
149
|
+
export default DashboardCLI;
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
import { jsxs as _jsxs, jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { Text, Box, useInput, useApp } from 'ink';
|
|
3
|
+
import SelectInput from 'ink-select-input';
|
|
4
|
+
import * as React from 'react';
|
|
5
|
+
import { execa } from 'execa';
|
|
6
|
+
import { getUIGeneratorChoices, UI_CONFIG } from '../commands/generate/config.js';
|
|
7
|
+
const GeneratorUI = () => {
|
|
8
|
+
const [state, setState] = React.useState({
|
|
9
|
+
error: null,
|
|
10
|
+
});
|
|
11
|
+
const { exit } = useApp();
|
|
12
|
+
const generatorChoices = getUIGeneratorChoices();
|
|
13
|
+
useInput((input, key) => {
|
|
14
|
+
if (key.escape || input === 'q') {
|
|
15
|
+
// Exit the UI
|
|
16
|
+
exit();
|
|
17
|
+
}
|
|
18
|
+
});
|
|
19
|
+
const handleGeneratorTypeSelect = async (choice) => {
|
|
20
|
+
if (choice.value === 'back') {
|
|
21
|
+
// Exit the UI
|
|
22
|
+
exit();
|
|
23
|
+
return;
|
|
24
|
+
}
|
|
25
|
+
if (choice.value === 'project') {
|
|
26
|
+
try {
|
|
27
|
+
// Exit the UI and run the CLI command
|
|
28
|
+
exit();
|
|
29
|
+
// Run the CLI command
|
|
30
|
+
// We need to spawn this in a way that doesn't interfere with the current process
|
|
31
|
+
setTimeout(() => {
|
|
32
|
+
execa('ark', ['generate', 'project'], {
|
|
33
|
+
stdio: 'inherit',
|
|
34
|
+
cwd: process.cwd(),
|
|
35
|
+
}).catch((error) => {
|
|
36
|
+
console.error('Failed to run generator:', error.message);
|
|
37
|
+
process.exit(1);
|
|
38
|
+
});
|
|
39
|
+
}, 100); // Small delay to let UI exit cleanly
|
|
40
|
+
}
|
|
41
|
+
catch (error) {
|
|
42
|
+
const errorMessage = error instanceof Error ? error.message : 'Failed to launch generator';
|
|
43
|
+
setState((prev) => ({
|
|
44
|
+
...prev,
|
|
45
|
+
error: errorMessage,
|
|
46
|
+
}));
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
if (choice.value === 'agent') {
|
|
50
|
+
try {
|
|
51
|
+
// Exit the UI and run the CLI command
|
|
52
|
+
exit();
|
|
53
|
+
// Run the CLI command
|
|
54
|
+
setTimeout(() => {
|
|
55
|
+
execa('ark', ['generate', 'agent'], {
|
|
56
|
+
stdio: 'inherit',
|
|
57
|
+
cwd: process.cwd(),
|
|
58
|
+
}).catch((error) => {
|
|
59
|
+
console.error('Failed to run generator:', error.message);
|
|
60
|
+
process.exit(1);
|
|
61
|
+
});
|
|
62
|
+
}, 100); // Small delay to let UI exit cleanly
|
|
63
|
+
}
|
|
64
|
+
catch (error) {
|
|
65
|
+
const errorMessage = error instanceof Error ? error.message : 'Failed to launch generator';
|
|
66
|
+
setState((prev) => ({
|
|
67
|
+
...prev,
|
|
68
|
+
error: errorMessage,
|
|
69
|
+
}));
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
if (choice.value === 'team') {
|
|
73
|
+
try {
|
|
74
|
+
// Exit the UI and run the CLI command
|
|
75
|
+
exit();
|
|
76
|
+
// Run the CLI command
|
|
77
|
+
setTimeout(() => {
|
|
78
|
+
execa('ark', ['generate', 'team'], {
|
|
79
|
+
stdio: 'inherit',
|
|
80
|
+
cwd: process.cwd(),
|
|
81
|
+
}).catch((error) => {
|
|
82
|
+
console.error('Failed to run generator:', error.message);
|
|
83
|
+
process.exit(1);
|
|
84
|
+
});
|
|
85
|
+
}, 100); // Small delay to let UI exit cleanly
|
|
86
|
+
}
|
|
87
|
+
catch (error) {
|
|
88
|
+
const errorMessage = error instanceof Error ? error.message : 'Failed to launch generator';
|
|
89
|
+
setState((prev) => ({
|
|
90
|
+
...prev,
|
|
91
|
+
error: errorMessage,
|
|
92
|
+
}));
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
if (choice.value === 'query') {
|
|
96
|
+
try {
|
|
97
|
+
// Exit the UI and run the CLI command
|
|
98
|
+
exit();
|
|
99
|
+
// Run the CLI command
|
|
100
|
+
setTimeout(() => {
|
|
101
|
+
execa('ark', ['generate', 'query'], {
|
|
102
|
+
stdio: 'inherit',
|
|
103
|
+
cwd: process.cwd(),
|
|
104
|
+
}).catch((error) => {
|
|
105
|
+
console.error('Failed to run generator:', error.message);
|
|
106
|
+
process.exit(1);
|
|
107
|
+
});
|
|
108
|
+
}, 100); // Small delay to let UI exit cleanly
|
|
109
|
+
}
|
|
110
|
+
catch (error) {
|
|
111
|
+
const errorMessage = error instanceof Error ? error.message : 'Failed to launch generator';
|
|
112
|
+
setState((prev) => ({
|
|
113
|
+
...prev,
|
|
114
|
+
error: errorMessage,
|
|
115
|
+
}));
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
if (choice.value === 'mcp-server') {
|
|
119
|
+
try {
|
|
120
|
+
// Exit the UI and run the CLI command
|
|
121
|
+
exit();
|
|
122
|
+
// Run the CLI command
|
|
123
|
+
setTimeout(() => {
|
|
124
|
+
execa('ark', ['generate', 'mcp-server'], {
|
|
125
|
+
stdio: 'inherit',
|
|
126
|
+
cwd: process.cwd(),
|
|
127
|
+
}).catch((error) => {
|
|
128
|
+
console.error('Failed to run generator:', error.message);
|
|
129
|
+
process.exit(1);
|
|
130
|
+
});
|
|
131
|
+
}, 100); // Small delay to let UI exit cleanly
|
|
132
|
+
}
|
|
133
|
+
catch (error) {
|
|
134
|
+
const errorMessage = error instanceof Error ? error.message : 'Failed to launch generator';
|
|
135
|
+
setState((prev) => ({
|
|
136
|
+
...prev,
|
|
137
|
+
error: errorMessage,
|
|
138
|
+
}));
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
if (choice.value === 'marketplace') {
|
|
142
|
+
try {
|
|
143
|
+
// Exit the UI and run the CLI command
|
|
144
|
+
exit();
|
|
145
|
+
// Run the CLI command
|
|
146
|
+
setTimeout(() => {
|
|
147
|
+
execa('ark', ['generate', 'marketplace'], {
|
|
148
|
+
stdio: 'inherit',
|
|
149
|
+
cwd: process.cwd(),
|
|
150
|
+
}).catch((error) => {
|
|
151
|
+
console.error('Failed to run generator:', error.message);
|
|
152
|
+
process.exit(1);
|
|
153
|
+
});
|
|
154
|
+
}, 100); // Small delay to let UI exit cleanly
|
|
155
|
+
}
|
|
156
|
+
catch (error) {
|
|
157
|
+
const errorMessage = error instanceof Error ? error.message : 'Failed to launch generator';
|
|
158
|
+
setState((prev) => ({
|
|
159
|
+
...prev,
|
|
160
|
+
error: errorMessage,
|
|
161
|
+
}));
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
};
|
|
165
|
+
return (_jsxs(Box, { flexDirection: "column", children: [_jsxs(Text, { color: UI_CONFIG.colors.primary, bold: true, children: [UI_CONFIG.icons.generator, " Generator - Create new ARK resources"] }), _jsx(Box, { marginBottom: 1, children: _jsx(Text, { color: UI_CONFIG.colors.secondary, children: UI_CONFIG.messages.generatorTypePrompt }) }), state.error && (_jsx(Box, { marginBottom: 1, children: _jsxs(Text, { color: UI_CONFIG.colors.error, children: [UI_CONFIG.icons.error, " ", state.error] }) })), _jsx(SelectInput, { items: generatorChoices, onSelect: handleGeneratorTypeSelect }), _jsx(Box, { marginTop: 1, children: _jsx(Text, { color: "gray", children: "Press ESC or 'q' to exit" }) })] }));
|
|
166
|
+
};
|
|
167
|
+
export default GeneratorUI;
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { StatusData, CommandVersionConfig } from '../lib/types.js';
|
|
2
|
+
import { ArkClient } from '../lib/arkClient.js';
|
|
3
|
+
export declare const getNodeVersion: () => CommandVersionConfig;
|
|
4
|
+
export declare const getNpmVersion: () => CommandVersionConfig;
|
|
5
|
+
export declare const getKubectlVersion: () => CommandVersionConfig;
|
|
6
|
+
export declare const getDockerVersion: () => CommandVersionConfig;
|
|
7
|
+
export declare const getHelmVersion: () => CommandVersionConfig;
|
|
8
|
+
export declare class StatusChecker {
|
|
9
|
+
private arkClient;
|
|
10
|
+
private kubernetesManager;
|
|
11
|
+
constructor(arkClient: ArkClient);
|
|
12
|
+
/**
|
|
13
|
+
* Check if a command is available in the system
|
|
14
|
+
*/
|
|
15
|
+
private isCommandAvailable;
|
|
16
|
+
/**
|
|
17
|
+
* Get version of a command
|
|
18
|
+
*/
|
|
19
|
+
private getCommandVersion;
|
|
20
|
+
/**
|
|
21
|
+
* Check health of a service by URL
|
|
22
|
+
*/
|
|
23
|
+
private checkServiceHealth;
|
|
24
|
+
/**
|
|
25
|
+
* Check if ark-api is running and healthy
|
|
26
|
+
*/
|
|
27
|
+
private checkArkApi;
|
|
28
|
+
/**
|
|
29
|
+
* Return a "not installed" status for a service
|
|
30
|
+
*/
|
|
31
|
+
private createNotInstalledStatus;
|
|
32
|
+
/**
|
|
33
|
+
* Check Kubernetes service health via pods and endpoints
|
|
34
|
+
*/
|
|
35
|
+
private checkKubernetesService;
|
|
36
|
+
/**
|
|
37
|
+
* Check system dependencies
|
|
38
|
+
*/
|
|
39
|
+
private checkDependencies;
|
|
40
|
+
/**
|
|
41
|
+
* Run all checks and return results
|
|
42
|
+
*/
|
|
43
|
+
checkAll(serviceUrls?: Record<string, string>, arkApiUrl?: string): Promise<StatusData>;
|
|
44
|
+
/**
|
|
45
|
+
* Get appropriate health check path for different service types
|
|
46
|
+
*/
|
|
47
|
+
private getHealthPath;
|
|
48
|
+
}
|
|
@@ -0,0 +1,251 @@
|
|
|
1
|
+
import { exec } from 'child_process';
|
|
2
|
+
import { promisify } from 'util';
|
|
3
|
+
import { KubernetesConfigManager } from '../lib/kubernetes.js';
|
|
4
|
+
import * as k8s from '@kubernetes/client-node';
|
|
5
|
+
import axios from 'axios';
|
|
6
|
+
const execAsync = promisify(exec);
|
|
7
|
+
export const getNodeVersion = () => ({
|
|
8
|
+
command: 'node',
|
|
9
|
+
versionArgs: '--version',
|
|
10
|
+
versionExtract: (output) => output.trim(),
|
|
11
|
+
});
|
|
12
|
+
export const getNpmVersion = () => ({
|
|
13
|
+
command: 'npm',
|
|
14
|
+
versionArgs: '--version',
|
|
15
|
+
versionExtract: (output) => output.trim(),
|
|
16
|
+
});
|
|
17
|
+
export const getKubectlVersion = () => ({
|
|
18
|
+
command: 'kubectl',
|
|
19
|
+
versionArgs: 'version --client --output=json',
|
|
20
|
+
versionExtract: (output) => {
|
|
21
|
+
try {
|
|
22
|
+
const versionInfo = JSON.parse(output);
|
|
23
|
+
if (versionInfo.clientVersion) {
|
|
24
|
+
return `v${versionInfo.clientVersion.major}.${versionInfo.clientVersion.minor}`;
|
|
25
|
+
}
|
|
26
|
+
throw new Error('kubectl version output missing clientVersion field');
|
|
27
|
+
}
|
|
28
|
+
catch (e) {
|
|
29
|
+
throw new Error(`Failed to parse kubectl version JSON: ${e instanceof Error ? e.message : 'Unknown error'}`);
|
|
30
|
+
}
|
|
31
|
+
},
|
|
32
|
+
});
|
|
33
|
+
export const getDockerVersion = () => ({
|
|
34
|
+
command: 'docker',
|
|
35
|
+
versionArgs: '--version',
|
|
36
|
+
versionExtract: (output) => output.trim(),
|
|
37
|
+
});
|
|
38
|
+
export const getHelmVersion = () => ({
|
|
39
|
+
command: 'helm',
|
|
40
|
+
versionArgs: 'version --short',
|
|
41
|
+
versionExtract: (output) => output.trim(),
|
|
42
|
+
});
|
|
43
|
+
function createErrorServiceStatus(name, url, error, defaultStatus = 'unhealthy', defaultDetails) {
|
|
44
|
+
const errorMessage = error instanceof Error ? error.message : 'Unknown error occurred';
|
|
45
|
+
return {
|
|
46
|
+
name,
|
|
47
|
+
status: defaultStatus,
|
|
48
|
+
url,
|
|
49
|
+
details: defaultDetails || `Error: ${errorMessage}`,
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
export class StatusChecker {
|
|
53
|
+
constructor(arkClient) {
|
|
54
|
+
this.arkClient = arkClient;
|
|
55
|
+
this.kubernetesManager = new KubernetesConfigManager();
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Check if a command is available in the system
|
|
59
|
+
*/
|
|
60
|
+
async isCommandAvailable(command) {
|
|
61
|
+
try {
|
|
62
|
+
const checkCommand = process.platform === 'win32'
|
|
63
|
+
? `where ${command}`
|
|
64
|
+
: `command -v ${command}`;
|
|
65
|
+
await execAsync(checkCommand);
|
|
66
|
+
return true;
|
|
67
|
+
}
|
|
68
|
+
catch (_error) {
|
|
69
|
+
return false;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Get version of a command
|
|
74
|
+
*/
|
|
75
|
+
async getCommandVersion(config) {
|
|
76
|
+
try {
|
|
77
|
+
const cmd = `${config.command} ${config.versionArgs}`;
|
|
78
|
+
const { stdout } = await execAsync(cmd);
|
|
79
|
+
return config.versionExtract(stdout);
|
|
80
|
+
}
|
|
81
|
+
catch (error) {
|
|
82
|
+
throw new Error(`Failed to get ${config.command} version: ${error instanceof Error ? error.message : 'Unknown error'}`);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Check health of a service by URL
|
|
87
|
+
*/
|
|
88
|
+
async checkServiceHealth(serviceName, serviceUrl, successMessage, healthPath = '') {
|
|
89
|
+
const fullUrl = `${serviceUrl}${healthPath}`;
|
|
90
|
+
try {
|
|
91
|
+
await axios.get(fullUrl, { timeout: 5000 });
|
|
92
|
+
return {
|
|
93
|
+
name: serviceName,
|
|
94
|
+
status: 'healthy',
|
|
95
|
+
url: serviceUrl,
|
|
96
|
+
details: successMessage,
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
catch (error) {
|
|
100
|
+
return createErrorServiceStatus(serviceName, serviceUrl, error, 'unhealthy', `${serviceName} is not running or not accessible`);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* Check if ark-api is running and healthy
|
|
105
|
+
*/
|
|
106
|
+
async checkArkApi(customUrl) {
|
|
107
|
+
const url = customUrl || this.arkClient.getBaseURL();
|
|
108
|
+
return this.checkServiceHealth('ark-api', url, 'ARK API is running', '/health');
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* Return a "not installed" status for a service
|
|
112
|
+
*/
|
|
113
|
+
createNotInstalledStatus(serviceName) {
|
|
114
|
+
return {
|
|
115
|
+
name: serviceName,
|
|
116
|
+
status: 'not installed',
|
|
117
|
+
details: `${serviceName} is not configured or not part of this deployment`,
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* Check Kubernetes service health via pods and endpoints
|
|
122
|
+
*/
|
|
123
|
+
async checkKubernetesService(serviceName, kubernetesServiceName, namespace = 'default') {
|
|
124
|
+
try {
|
|
125
|
+
await this.kubernetesManager.initializeConfig();
|
|
126
|
+
const kc = this.kubernetesManager.getKubeConfig();
|
|
127
|
+
const k8sApi = kc.makeApiClient(k8s.CoreV1Api);
|
|
128
|
+
// Check if service exists and has endpoints
|
|
129
|
+
const service = await k8sApi.readNamespacedService({
|
|
130
|
+
name: kubernetesServiceName,
|
|
131
|
+
namespace,
|
|
132
|
+
});
|
|
133
|
+
const endpoints = await k8sApi.readNamespacedEndpoints({
|
|
134
|
+
name: kubernetesServiceName,
|
|
135
|
+
namespace,
|
|
136
|
+
});
|
|
137
|
+
// Check if service has ready endpoints
|
|
138
|
+
const readyAddresses = endpoints.subsets?.reduce((total, subset) => {
|
|
139
|
+
return total + (subset.addresses?.length || 0);
|
|
140
|
+
}, 0) || 0;
|
|
141
|
+
if (readyAddresses > 0) {
|
|
142
|
+
const serviceIP = service.spec?.clusterIP;
|
|
143
|
+
const servicePort = service.spec?.ports?.[0]?.port;
|
|
144
|
+
return {
|
|
145
|
+
name: serviceName,
|
|
146
|
+
status: 'healthy',
|
|
147
|
+
url: `cluster://${serviceIP}:${servicePort}`,
|
|
148
|
+
details: `${serviceName} running in cluster (${readyAddresses} ready endpoints)`,
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
else {
|
|
152
|
+
return {
|
|
153
|
+
name: serviceName,
|
|
154
|
+
status: 'unhealthy',
|
|
155
|
+
details: `${serviceName} service exists but has no ready endpoints`,
|
|
156
|
+
};
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
catch (error) {
|
|
160
|
+
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
|
|
161
|
+
// If service not found, it's not installed
|
|
162
|
+
if (errorMessage.includes('not found')) {
|
|
163
|
+
return this.createNotInstalledStatus(serviceName);
|
|
164
|
+
}
|
|
165
|
+
// Other errors indicate unhealthy
|
|
166
|
+
return {
|
|
167
|
+
name: serviceName,
|
|
168
|
+
status: 'unhealthy',
|
|
169
|
+
details: `Failed to check ${serviceName}: ${errorMessage}`,
|
|
170
|
+
};
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
/**
|
|
174
|
+
* Check system dependencies
|
|
175
|
+
*/
|
|
176
|
+
async checkDependencies() {
|
|
177
|
+
const dependencies = [
|
|
178
|
+
{ name: 'node', ...getNodeVersion() },
|
|
179
|
+
{ name: 'npm', ...getNpmVersion() },
|
|
180
|
+
{ name: 'kubectl', ...getKubectlVersion() },
|
|
181
|
+
{ name: 'docker', ...getDockerVersion() },
|
|
182
|
+
{ name: 'helm', ...getHelmVersion() },
|
|
183
|
+
];
|
|
184
|
+
const results = [];
|
|
185
|
+
for (const dep of dependencies) {
|
|
186
|
+
const installed = await this.isCommandAvailable(dep.command);
|
|
187
|
+
const version = installed
|
|
188
|
+
? await this.getCommandVersion({
|
|
189
|
+
command: dep.command,
|
|
190
|
+
versionArgs: dep.versionArgs,
|
|
191
|
+
versionExtract: dep.versionExtract,
|
|
192
|
+
})
|
|
193
|
+
: undefined;
|
|
194
|
+
results.push({
|
|
195
|
+
name: dep.name,
|
|
196
|
+
installed,
|
|
197
|
+
version,
|
|
198
|
+
details: installed
|
|
199
|
+
? `Found ${dep.name} ${version}`
|
|
200
|
+
: `${dep.name} not found in PATH`,
|
|
201
|
+
});
|
|
202
|
+
}
|
|
203
|
+
return results;
|
|
204
|
+
}
|
|
205
|
+
/**
|
|
206
|
+
* Run all checks and return results
|
|
207
|
+
*/
|
|
208
|
+
async checkAll(serviceUrls = {}, arkApiUrl) {
|
|
209
|
+
// Always check ark-api if provided
|
|
210
|
+
const serviceChecks = [];
|
|
211
|
+
if (arkApiUrl) {
|
|
212
|
+
serviceChecks.push(this.checkArkApi(arkApiUrl));
|
|
213
|
+
}
|
|
214
|
+
// Dynamically check all discovered services
|
|
215
|
+
for (const [serviceName, serviceUrl] of Object.entries(serviceUrls)) {
|
|
216
|
+
if (serviceName === 'ark-api' && arkApiUrl) {
|
|
217
|
+
// Skip if we already added ark-api above
|
|
218
|
+
continue;
|
|
219
|
+
}
|
|
220
|
+
serviceChecks.push(this.checkServiceHealth(serviceName, serviceUrl, `${serviceName} is running`, this.getHealthPath(serviceName)));
|
|
221
|
+
}
|
|
222
|
+
// Always check dependencies
|
|
223
|
+
const dependenciesCheck = this.checkDependencies();
|
|
224
|
+
const [dependencies, ...serviceStatuses] = await Promise.all([
|
|
225
|
+
dependenciesCheck,
|
|
226
|
+
...serviceChecks,
|
|
227
|
+
]);
|
|
228
|
+
return {
|
|
229
|
+
services: serviceStatuses,
|
|
230
|
+
dependencies,
|
|
231
|
+
};
|
|
232
|
+
}
|
|
233
|
+
/**
|
|
234
|
+
* Get appropriate health check path for different service types
|
|
235
|
+
*/
|
|
236
|
+
getHealthPath(serviceName) {
|
|
237
|
+
// Some services might need specific health check paths
|
|
238
|
+
switch (serviceName) {
|
|
239
|
+
case 'ark-api':
|
|
240
|
+
return '/health';
|
|
241
|
+
case 'ark-api-a2a':
|
|
242
|
+
return '/health'; // ark-api-a2a has a working /health endpoint
|
|
243
|
+
case 'ark-dashboard':
|
|
244
|
+
return ''; // Dashboard typically responds to root
|
|
245
|
+
case 'langfuse':
|
|
246
|
+
return ''; // Langfuse responds to root path
|
|
247
|
+
default:
|
|
248
|
+
return ''; // Default to root path
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
}
|
package/dist/config.d.ts
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { ArkConfig, KubernetesConfig } from './lib/types.js';
|
|
2
|
+
/**
|
|
3
|
+
* ConfigManager handles ARK CLI configuration with automatic service discovery
|
|
4
|
+
* and multiple fallback mechanisms. Complex discovery logic can be debugged by
|
|
5
|
+
* setting DEBUG=ark:config or DEBUG=ark:* environment variable.
|
|
6
|
+
*
|
|
7
|
+
* Example usage:
|
|
8
|
+
* DEBUG=ark:config ark check status
|
|
9
|
+
* DEBUG=ark:* ark dashboard
|
|
10
|
+
*/
|
|
11
|
+
export declare class ConfigManager {
|
|
12
|
+
private configDir;
|
|
13
|
+
private configFile;
|
|
14
|
+
private kubernetesManager;
|
|
15
|
+
private gatewayManager;
|
|
16
|
+
private kubeConfig;
|
|
17
|
+
constructor();
|
|
18
|
+
ensureConfigDir(): Promise<void>;
|
|
19
|
+
loadConfig(): Promise<ArkConfig>;
|
|
20
|
+
saveConfig(config: ArkConfig): Promise<void>;
|
|
21
|
+
updateConfig(updates: Partial<ArkConfig>): Promise<ArkConfig>;
|
|
22
|
+
private getDefaultConfig;
|
|
23
|
+
initializeConfig(): Promise<ArkConfig>;
|
|
24
|
+
getConfigPath(): string;
|
|
25
|
+
getApiBaseUrl(): Promise<string>;
|
|
26
|
+
/**
|
|
27
|
+
* Check if localhost-gateway is running by testing port 8080
|
|
28
|
+
*/
|
|
29
|
+
private isLocalhostGatewayRunning;
|
|
30
|
+
/**
|
|
31
|
+
* Construct standard localhost-gateway URLs for known ARK services
|
|
32
|
+
*/
|
|
33
|
+
private getLocalhostGatewayUrls;
|
|
34
|
+
private initKubernetesConfig;
|
|
35
|
+
getKubernetesConfig(): Promise<KubernetesConfig | null>;
|
|
36
|
+
testClusterAccess(): Promise<boolean>;
|
|
37
|
+
/**
|
|
38
|
+
* Discover service URLs from ark-api service discovery
|
|
39
|
+
*/
|
|
40
|
+
private discoverServicesFromApi;
|
|
41
|
+
getServiceUrls(): Promise<Record<string, string>>;
|
|
42
|
+
}
|