@agents-at-scale/ark 0.1.35 → 0.1.36
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.d.ts +42 -0
- package/dist/arkServices.js +138 -0
- package/dist/arkServices.spec.d.ts +1 -0
- package/dist/arkServices.spec.js +24 -0
- package/dist/commands/agents/index.d.ts +3 -0
- package/dist/commands/agents/index.js +65 -0
- package/dist/commands/agents/index.spec.d.ts +1 -0
- package/dist/commands/agents/index.spec.js +67 -0
- package/dist/commands/chat/index.d.ts +3 -0
- package/dist/commands/chat/index.js +29 -0
- package/dist/commands/cluster/get.d.ts +2 -0
- package/dist/commands/cluster/get.js +39 -0
- package/dist/commands/cluster/get.spec.d.ts +1 -0
- package/dist/commands/cluster/get.spec.js +92 -0
- package/dist/commands/cluster/index.d.ts +2 -1
- package/dist/commands/cluster/index.js +3 -5
- package/dist/commands/cluster/index.spec.d.ts +1 -0
- package/dist/commands/cluster/index.spec.js +24 -0
- package/dist/commands/completion/index.d.ts +3 -0
- package/dist/commands/completion/index.js +230 -0
- package/dist/commands/completion/index.spec.d.ts +1 -0
- package/dist/commands/completion/index.spec.js +34 -0
- package/dist/commands/config/index.d.ts +3 -0
- package/dist/commands/config/index.js +42 -0
- package/dist/commands/config/index.spec.d.ts +1 -0
- package/dist/commands/config/index.spec.js +78 -0
- package/dist/commands/dashboard/index.d.ts +4 -0
- package/dist/commands/dashboard/index.js +39 -0
- package/dist/commands/docs/index.d.ts +4 -0
- package/dist/commands/docs/index.js +18 -0
- package/dist/commands/generate/config.js +5 -24
- package/dist/commands/generate/generators/mcpserver.d.ts +2 -1
- package/dist/commands/generate/generators/mcpserver.js +26 -5
- package/dist/commands/generate/generators/project.js +22 -41
- package/dist/commands/generate/index.d.ts +2 -1
- package/dist/commands/generate/index.js +1 -1
- package/dist/commands/install/index.d.ts +8 -0
- package/dist/commands/install/index.js +295 -0
- package/dist/commands/install/index.spec.d.ts +1 -0
- package/dist/commands/install/index.spec.js +143 -0
- package/dist/commands/models/create.d.ts +1 -0
- package/dist/commands/models/create.js +213 -0
- package/dist/commands/models/create.spec.d.ts +1 -0
- package/dist/commands/models/create.spec.js +125 -0
- package/dist/commands/models/index.d.ts +3 -0
- package/dist/commands/models/index.js +75 -0
- package/dist/commands/models/index.spec.d.ts +1 -0
- package/dist/commands/models/index.spec.js +96 -0
- package/dist/commands/query/index.d.ts +3 -0
- package/dist/commands/query/index.js +24 -0
- package/dist/commands/query/index.spec.d.ts +1 -0
- package/dist/commands/query/index.spec.js +53 -0
- package/dist/commands/routes/index.d.ts +3 -0
- package/dist/commands/routes/index.js +93 -0
- package/dist/commands/status/index.d.ts +3 -0
- package/dist/commands/status/index.js +281 -0
- package/dist/commands/targets/index.d.ts +3 -0
- package/dist/commands/targets/index.js +72 -0
- package/dist/commands/targets/index.spec.d.ts +1 -0
- package/dist/commands/targets/index.spec.js +154 -0
- package/dist/commands/teams/index.d.ts +3 -0
- package/dist/commands/teams/index.js +64 -0
- package/dist/commands/teams/index.spec.d.ts +1 -0
- package/dist/commands/teams/index.spec.js +70 -0
- package/dist/commands/tools/index.d.ts +3 -0
- package/dist/commands/tools/index.js +49 -0
- package/dist/commands/tools/index.spec.d.ts +1 -0
- package/dist/commands/tools/index.spec.js +70 -0
- package/dist/commands/uninstall/index.d.ts +3 -0
- package/dist/commands/uninstall/index.js +101 -0
- package/dist/commands/uninstall/index.spec.d.ts +1 -0
- package/dist/commands/uninstall/index.spec.js +125 -0
- package/dist/components/ChatUI.d.ts +16 -0
- package/dist/components/ChatUI.js +801 -0
- package/dist/components/statusChecker.d.ts +14 -24
- package/dist/components/statusChecker.js +295 -129
- package/dist/index.d.ts +1 -1
- package/dist/index.js +42 -42
- package/dist/lib/arkApiClient.d.ts +53 -0
- package/dist/lib/arkApiClient.js +102 -0
- package/dist/lib/arkApiProxy.d.ts +9 -0
- package/dist/lib/arkApiProxy.js +22 -0
- package/dist/lib/arkServiceProxy.d.ts +14 -0
- package/dist/lib/arkServiceProxy.js +95 -0
- package/dist/lib/arkStatus.d.ts +10 -0
- package/dist/lib/arkStatus.js +79 -0
- package/dist/lib/arkStatus.spec.d.ts +1 -0
- package/dist/lib/arkStatus.spec.js +49 -0
- package/dist/lib/chatClient.d.ts +33 -0
- package/dist/lib/chatClient.js +93 -0
- package/dist/lib/cluster.d.ts +2 -1
- package/dist/lib/cluster.js +37 -16
- package/dist/lib/cluster.spec.d.ts +1 -0
- package/dist/lib/cluster.spec.js +338 -0
- package/dist/lib/commands.d.ts +16 -0
- package/dist/lib/commands.js +29 -0
- package/dist/lib/commands.spec.d.ts +1 -0
- package/dist/lib/commands.spec.js +146 -0
- package/dist/lib/config.d.ts +26 -80
- package/dist/lib/config.js +70 -205
- package/dist/lib/config.spec.d.ts +1 -0
- package/dist/lib/config.spec.js +99 -0
- package/dist/lib/errors.js +1 -1
- package/dist/lib/errors.spec.d.ts +1 -0
- package/dist/lib/errors.spec.js +221 -0
- package/dist/lib/executeQuery.d.ts +20 -0
- package/dist/lib/executeQuery.js +135 -0
- package/dist/lib/executeQuery.spec.d.ts +1 -0
- package/dist/lib/executeQuery.spec.js +170 -0
- package/dist/lib/nextSteps.d.ts +4 -0
- package/dist/lib/nextSteps.js +20 -0
- package/dist/lib/nextSteps.spec.d.ts +1 -0
- package/dist/lib/nextSteps.spec.js +59 -0
- package/dist/lib/output.d.ts +36 -0
- package/dist/lib/output.js +89 -0
- package/dist/lib/output.spec.d.ts +1 -0
- package/dist/lib/output.spec.js +123 -0
- package/dist/lib/startup.d.ts +9 -0
- package/dist/lib/startup.js +87 -0
- package/dist/lib/startup.spec.d.ts +1 -0
- package/dist/lib/startup.spec.js +152 -0
- package/dist/lib/types.d.ts +87 -3
- package/dist/lib/versions.d.ts +23 -0
- package/dist/lib/versions.js +51 -0
- package/dist/types/types.d.ts +40 -0
- package/dist/types/types.js +1 -0
- package/dist/ui/AgentSelector.d.ts +8 -0
- package/dist/ui/AgentSelector.js +53 -0
- package/dist/ui/MainMenu.d.ts +5 -1
- package/dist/ui/MainMenu.js +226 -91
- package/dist/ui/ModelSelector.d.ts +8 -0
- package/dist/ui/ModelSelector.js +53 -0
- package/dist/ui/TeamSelector.d.ts +8 -0
- package/dist/ui/TeamSelector.js +55 -0
- package/dist/ui/ToolSelector.d.ts +8 -0
- package/dist/ui/ToolSelector.js +53 -0
- package/dist/ui/statusFormatter.d.ts +22 -7
- package/dist/ui/statusFormatter.js +39 -39
- package/dist/ui/statusFormatter.spec.d.ts +1 -0
- package/dist/ui/statusFormatter.spec.js +58 -0
- package/package.json +16 -5
- package/dist/commands/cluster/get-ip.d.ts +0 -2
- package/dist/commands/cluster/get-ip.js +0 -32
- package/dist/commands/cluster/get-type.d.ts +0 -2
- package/dist/commands/cluster/get-type.js +0 -26
- package/dist/commands/completion.d.ts +0 -2
- package/dist/commands/completion.js +0 -108
- package/dist/commands/config.d.ts +0 -5
- package/dist/commands/config.js +0 -327
- package/dist/components/DashboardCLI.d.ts +0 -3
- package/dist/components/DashboardCLI.js +0 -149
- package/dist/config.d.ts +0 -42
- package/dist/config.js +0 -243
- package/dist/lib/arkClient.d.ts +0 -32
- package/dist/lib/arkClient.js +0 -43
- package/dist/lib/consts.d.ts +0 -10
- package/dist/lib/consts.js +0 -15
- package/dist/lib/exec.d.ts +0 -5
- package/dist/lib/exec.js +0 -20
- package/dist/lib/gatewayManager.d.ts +0 -24
- package/dist/lib/gatewayManager.js +0 -85
- package/dist/lib/kubernetes.d.ts +0 -28
- package/dist/lib/kubernetes.js +0 -122
- package/dist/lib/progress.d.ts +0 -128
- package/dist/lib/progress.js +0 -273
- package/dist/lib/wrappers/git.d.ts +0 -2
- package/dist/lib/wrappers/git.js +0 -43
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import { Command } from 'commander';
|
|
2
|
+
import chalk from 'chalk';
|
|
3
|
+
import { execa } from 'execa';
|
|
4
|
+
import output from '../../lib/output.js';
|
|
5
|
+
async function listRoutes() {
|
|
6
|
+
const namespace = 'ark-system';
|
|
7
|
+
const port = 8080;
|
|
8
|
+
const portSuffix = `:${port}`;
|
|
9
|
+
try {
|
|
10
|
+
// Check if localhost-gateway is installed
|
|
11
|
+
const { stdout: gatewayCheck } = await execa('kubectl', ['get', 'gateway', 'localhost-gateway', '-n', namespace], { reject: false });
|
|
12
|
+
if (!gatewayCheck) {
|
|
13
|
+
output.error("localhost-gateway not installed in namespace 'ark-system'");
|
|
14
|
+
output.info("run 'ark install' first");
|
|
15
|
+
process.exit(1);
|
|
16
|
+
}
|
|
17
|
+
// Get HTTPRoutes
|
|
18
|
+
const { stdout: routeOutput } = await execa('kubectl', [
|
|
19
|
+
'get',
|
|
20
|
+
'httproutes',
|
|
21
|
+
'-A',
|
|
22
|
+
'-o',
|
|
23
|
+
'custom-columns=NAMESPACE:.metadata.namespace,NAME:.metadata.name,HOSTNAMES:.spec.hostnames',
|
|
24
|
+
'--no-headers',
|
|
25
|
+
], { reject: false });
|
|
26
|
+
if (!routeOutput || routeOutput.trim() === '') {
|
|
27
|
+
console.log(chalk.white('available localhost gateway routes: 0'));
|
|
28
|
+
output.info('no httproutes found. install services to see routes here.');
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
// Parse routes
|
|
32
|
+
const lines = routeOutput.trim().split('\n');
|
|
33
|
+
const routes = [];
|
|
34
|
+
lines.forEach((line) => {
|
|
35
|
+
const parts = line.split(/\s+/);
|
|
36
|
+
if (parts.length >= 3) {
|
|
37
|
+
const name = parts[1];
|
|
38
|
+
// Remove brackets and split hostnames
|
|
39
|
+
const hostnamesStr = parts.slice(2).join(' ').replace(/\[|\]/g, '');
|
|
40
|
+
const hostnames = hostnamesStr
|
|
41
|
+
.split(',')
|
|
42
|
+
.map((h) => h.trim())
|
|
43
|
+
.filter((h) => h && h !== '<none>');
|
|
44
|
+
if (hostnames.length > 0) {
|
|
45
|
+
routes.push({ name, hostnames });
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
});
|
|
49
|
+
// Count total routes (each hostname counts as a route)
|
|
50
|
+
const routeCount = routes.reduce((count, r) => count + r.hostnames.length, 0);
|
|
51
|
+
console.log(chalk.white(`available localhost gateway routes: ${routeCount}`));
|
|
52
|
+
// Check port-forward status
|
|
53
|
+
const { stdout: psOutput } = await execa('pgrep', ['-f', `kubectl.*port-forward.*${port}:80`], { reject: false });
|
|
54
|
+
const portForwardActive = !!psOutput;
|
|
55
|
+
if (portForwardActive) {
|
|
56
|
+
output.info(`port-forward active on localhost${portSuffix}`);
|
|
57
|
+
}
|
|
58
|
+
else {
|
|
59
|
+
output.error(`port-forward not running on localhost${portSuffix} - routes are not exposed`);
|
|
60
|
+
console.log(chalk.blue('run:'), `kubectl port-forward -n ${namespace} service/localhost-gateway-nginx ${port}:80 > /dev/null 2>&1 &`);
|
|
61
|
+
}
|
|
62
|
+
console.log();
|
|
63
|
+
// Display routes
|
|
64
|
+
if (routes.length > 0) {
|
|
65
|
+
const maxLength = Math.max(...routes.map((r) => r.name.length));
|
|
66
|
+
routes.forEach((route) => {
|
|
67
|
+
route.hostnames.forEach((hostname) => {
|
|
68
|
+
const url = `http://${hostname}${portSuffix}/`;
|
|
69
|
+
const padding = ' '.repeat(maxLength - route.name.length);
|
|
70
|
+
if (portForwardActive) {
|
|
71
|
+
console.log(` ${route.name}${padding}: ${chalk.blue(url)}`);
|
|
72
|
+
}
|
|
73
|
+
else {
|
|
74
|
+
console.log(` ${route.name}${padding}: ${chalk.blue(url)} ${chalk.red('(unavailable)')}`);
|
|
75
|
+
}
|
|
76
|
+
});
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
catch (error) {
|
|
81
|
+
output.error('failed to fetch routes:', error instanceof Error ? error.message : 'Unknown error');
|
|
82
|
+
process.exit(1);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
export function createRoutesCommand(_) {
|
|
86
|
+
const command = new Command('routes');
|
|
87
|
+
command
|
|
88
|
+
.description('show available gateway routes and their urls')
|
|
89
|
+
.action(async () => {
|
|
90
|
+
await listRoutes();
|
|
91
|
+
});
|
|
92
|
+
return command;
|
|
93
|
+
}
|
|
@@ -0,0 +1,281 @@
|
|
|
1
|
+
import { Command } from 'commander';
|
|
2
|
+
import chalk from 'chalk';
|
|
3
|
+
import ora from 'ora';
|
|
4
|
+
import { StatusChecker } from '../../components/statusChecker.js';
|
|
5
|
+
import { StatusFormatter, } from '../../ui/statusFormatter.js';
|
|
6
|
+
import { fetchVersionInfo } from '../../lib/versions.js';
|
|
7
|
+
/**
|
|
8
|
+
* Enrich service with formatted details including version/revision
|
|
9
|
+
*/
|
|
10
|
+
function enrichServiceDetails(service) {
|
|
11
|
+
const statusMap = {
|
|
12
|
+
healthy: { icon: '✓', text: 'healthy', color: 'green' },
|
|
13
|
+
unhealthy: { icon: '✗', text: 'unhealthy', color: 'red' },
|
|
14
|
+
warning: { icon: '⚠', text: 'warning', color: 'yellow' },
|
|
15
|
+
'not ready': { icon: '○', text: 'not ready', color: 'yellow' },
|
|
16
|
+
'not installed': { icon: '?', text: 'not installed', color: 'yellow' },
|
|
17
|
+
};
|
|
18
|
+
const statusInfo = statusMap[service.status] || {
|
|
19
|
+
icon: '?',
|
|
20
|
+
text: service.status,
|
|
21
|
+
color: 'yellow',
|
|
22
|
+
};
|
|
23
|
+
// Build details array
|
|
24
|
+
const details = [];
|
|
25
|
+
if (service.status === 'healthy') {
|
|
26
|
+
if (service.version)
|
|
27
|
+
details.push(service.version);
|
|
28
|
+
if (service.revision)
|
|
29
|
+
details.push(`revision ${service.revision}`);
|
|
30
|
+
}
|
|
31
|
+
if (service.details)
|
|
32
|
+
details.push(service.details);
|
|
33
|
+
// Build display name with formatting
|
|
34
|
+
let displayName = chalk.bold(service.name);
|
|
35
|
+
if (service.namespace) {
|
|
36
|
+
displayName += ` ${chalk.blue(service.namespace)}`;
|
|
37
|
+
}
|
|
38
|
+
if (service.isDev) {
|
|
39
|
+
displayName += ' (dev)';
|
|
40
|
+
}
|
|
41
|
+
return {
|
|
42
|
+
statusInfo,
|
|
43
|
+
displayName,
|
|
44
|
+
details: details.join(', '),
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
function buildStatusSections(data, versionInfo) {
|
|
48
|
+
const sections = [];
|
|
49
|
+
// Dependencies section
|
|
50
|
+
sections.push({
|
|
51
|
+
title: 'system dependencies:',
|
|
52
|
+
lines: data.dependencies.map((dep) => ({
|
|
53
|
+
icon: dep.installed ? '✓' : '✗',
|
|
54
|
+
iconColor: (dep.installed ? 'green' : 'red'),
|
|
55
|
+
status: dep.installed ? 'installed' : 'missing',
|
|
56
|
+
statusColor: (dep.installed ? 'green' : 'red'),
|
|
57
|
+
name: chalk.bold(dep.name),
|
|
58
|
+
details: dep.version || '',
|
|
59
|
+
subtext: dep.installed ? undefined : dep.details,
|
|
60
|
+
})),
|
|
61
|
+
});
|
|
62
|
+
// Cluster access section
|
|
63
|
+
const clusterLines = [];
|
|
64
|
+
if (data.clusterAccess) {
|
|
65
|
+
const contextName = data.clusterInfo?.context || 'kubernetes cluster';
|
|
66
|
+
const namespace = data.clusterInfo?.namespace || 'default';
|
|
67
|
+
// Add bold context name with blue namespace
|
|
68
|
+
const name = `${chalk.bold(contextName)} ${chalk.blue(namespace)}`;
|
|
69
|
+
const details = [];
|
|
70
|
+
if (data.clusterInfo?.type && data.clusterInfo.type !== 'unknown') {
|
|
71
|
+
details.push(data.clusterInfo.type);
|
|
72
|
+
}
|
|
73
|
+
if (data.clusterInfo?.ip) {
|
|
74
|
+
details.push(data.clusterInfo.ip);
|
|
75
|
+
}
|
|
76
|
+
clusterLines.push({
|
|
77
|
+
icon: '✓',
|
|
78
|
+
iconColor: 'green',
|
|
79
|
+
status: 'accessible',
|
|
80
|
+
statusColor: 'green',
|
|
81
|
+
name,
|
|
82
|
+
details: details.join(', '),
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
else {
|
|
86
|
+
clusterLines.push({
|
|
87
|
+
icon: '✗',
|
|
88
|
+
iconColor: 'red',
|
|
89
|
+
status: 'unreachable',
|
|
90
|
+
statusColor: 'red',
|
|
91
|
+
name: 'kubernetes cluster',
|
|
92
|
+
subtext: 'Install minikube: https://minikube.sigs.k8s.io/docs/start',
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
sections.push({ title: 'cluster access:', lines: clusterLines });
|
|
96
|
+
// Ark services section
|
|
97
|
+
if (data.clusterAccess) {
|
|
98
|
+
const serviceLines = data.services
|
|
99
|
+
.filter((s) => s.name !== 'ark-controller')
|
|
100
|
+
.map((service) => {
|
|
101
|
+
const { statusInfo, displayName, details } = enrichServiceDetails(service);
|
|
102
|
+
return {
|
|
103
|
+
icon: statusInfo.icon,
|
|
104
|
+
iconColor: statusInfo.color,
|
|
105
|
+
status: statusInfo.text,
|
|
106
|
+
statusColor: statusInfo.color,
|
|
107
|
+
name: displayName,
|
|
108
|
+
details: details,
|
|
109
|
+
};
|
|
110
|
+
});
|
|
111
|
+
sections.push({ title: 'ark services:', lines: serviceLines });
|
|
112
|
+
}
|
|
113
|
+
else {
|
|
114
|
+
sections.push({
|
|
115
|
+
title: 'ark services:',
|
|
116
|
+
lines: [
|
|
117
|
+
{
|
|
118
|
+
icon: '',
|
|
119
|
+
status: '',
|
|
120
|
+
name: 'Cannot check ARK services - cluster not accessible',
|
|
121
|
+
},
|
|
122
|
+
],
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
// Ark status section
|
|
126
|
+
const arkStatusLines = [];
|
|
127
|
+
if (!data.clusterAccess) {
|
|
128
|
+
arkStatusLines.push({
|
|
129
|
+
icon: '✗',
|
|
130
|
+
iconColor: 'red',
|
|
131
|
+
status: 'no cluster access',
|
|
132
|
+
statusColor: 'red',
|
|
133
|
+
name: '',
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
else {
|
|
137
|
+
const controller = data.services?.find((s) => s.name === 'ark-controller');
|
|
138
|
+
if (!controller) {
|
|
139
|
+
arkStatusLines.push({
|
|
140
|
+
icon: '○',
|
|
141
|
+
iconColor: 'yellow',
|
|
142
|
+
status: 'not ready',
|
|
143
|
+
statusColor: 'yellow',
|
|
144
|
+
name: 'ark-controller',
|
|
145
|
+
});
|
|
146
|
+
}
|
|
147
|
+
else {
|
|
148
|
+
const { statusInfo, displayName, details } = enrichServiceDetails(controller);
|
|
149
|
+
// Map service status to ark status display
|
|
150
|
+
const statusText = controller.status === 'healthy'
|
|
151
|
+
? 'ready'
|
|
152
|
+
: controller.status === 'not installed'
|
|
153
|
+
? 'not ready'
|
|
154
|
+
: controller.status;
|
|
155
|
+
arkStatusLines.push({
|
|
156
|
+
icon: statusInfo.icon,
|
|
157
|
+
iconColor: statusInfo.color,
|
|
158
|
+
status: statusText,
|
|
159
|
+
statusColor: statusInfo.color,
|
|
160
|
+
name: displayName,
|
|
161
|
+
details: details,
|
|
162
|
+
});
|
|
163
|
+
// Add version update status as separate line
|
|
164
|
+
if (controller.status === 'healthy' && versionInfo) {
|
|
165
|
+
const currentVersion = versionInfo.current || controller.version;
|
|
166
|
+
if (!currentVersion) {
|
|
167
|
+
// Version is unknown
|
|
168
|
+
arkStatusLines.push({
|
|
169
|
+
icon: '?',
|
|
170
|
+
iconColor: 'yellow',
|
|
171
|
+
status: 'version unknown',
|
|
172
|
+
statusColor: 'yellow',
|
|
173
|
+
name: '',
|
|
174
|
+
details: versionInfo.latest
|
|
175
|
+
? `latest: ${versionInfo.latest}`
|
|
176
|
+
: 'unable to determine version',
|
|
177
|
+
});
|
|
178
|
+
}
|
|
179
|
+
else if (versionInfo.latest === undefined) {
|
|
180
|
+
// Have current version but couldn't check for updates
|
|
181
|
+
arkStatusLines.push({
|
|
182
|
+
icon: '?',
|
|
183
|
+
iconColor: 'yellow',
|
|
184
|
+
status: `version ${currentVersion}`,
|
|
185
|
+
statusColor: 'yellow',
|
|
186
|
+
name: '',
|
|
187
|
+
details: 'unable to check for updates',
|
|
188
|
+
});
|
|
189
|
+
}
|
|
190
|
+
else {
|
|
191
|
+
// Have both current and latest versions
|
|
192
|
+
if (currentVersion === versionInfo.latest) {
|
|
193
|
+
arkStatusLines.push({
|
|
194
|
+
icon: '✓',
|
|
195
|
+
iconColor: 'green',
|
|
196
|
+
status: 'up to date',
|
|
197
|
+
statusColor: 'green',
|
|
198
|
+
name: '',
|
|
199
|
+
details: versionInfo.latest,
|
|
200
|
+
});
|
|
201
|
+
}
|
|
202
|
+
else {
|
|
203
|
+
arkStatusLines.push({
|
|
204
|
+
icon: '↑',
|
|
205
|
+
iconColor: 'yellow',
|
|
206
|
+
status: 'update available',
|
|
207
|
+
statusColor: 'yellow',
|
|
208
|
+
name: '',
|
|
209
|
+
details: `${currentVersion} → ${versionInfo.latest}`,
|
|
210
|
+
});
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
// Add default model status
|
|
215
|
+
if (data.defaultModel) {
|
|
216
|
+
if (!data.defaultModel.exists) {
|
|
217
|
+
arkStatusLines.push({
|
|
218
|
+
icon: '○',
|
|
219
|
+
iconColor: 'yellow',
|
|
220
|
+
status: 'default model',
|
|
221
|
+
statusColor: 'yellow',
|
|
222
|
+
name: '',
|
|
223
|
+
details: 'not configured',
|
|
224
|
+
});
|
|
225
|
+
}
|
|
226
|
+
else if (data.defaultModel.available) {
|
|
227
|
+
arkStatusLines.push({
|
|
228
|
+
icon: '●',
|
|
229
|
+
iconColor: 'green',
|
|
230
|
+
status: 'default model',
|
|
231
|
+
statusColor: 'green',
|
|
232
|
+
name: '',
|
|
233
|
+
details: data.defaultModel.provider || 'configured',
|
|
234
|
+
});
|
|
235
|
+
}
|
|
236
|
+
else {
|
|
237
|
+
arkStatusLines.push({
|
|
238
|
+
icon: '●',
|
|
239
|
+
iconColor: 'yellow',
|
|
240
|
+
status: 'default model',
|
|
241
|
+
statusColor: 'yellow',
|
|
242
|
+
name: '',
|
|
243
|
+
details: 'not available',
|
|
244
|
+
});
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
sections.push({ title: 'ark status:', lines: arkStatusLines });
|
|
250
|
+
return sections;
|
|
251
|
+
}
|
|
252
|
+
export async function checkStatus() {
|
|
253
|
+
const spinner = ora('Checking system status').start();
|
|
254
|
+
try {
|
|
255
|
+
spinner.text = 'Checking system dependencies';
|
|
256
|
+
const statusChecker = new StatusChecker();
|
|
257
|
+
spinner.text = 'Testing cluster access';
|
|
258
|
+
spinner.text = 'Checking ARK services';
|
|
259
|
+
// Run status check and version fetch in parallel
|
|
260
|
+
const [statusData, versionInfo] = await Promise.all([
|
|
261
|
+
statusChecker.checkAll(),
|
|
262
|
+
fetchVersionInfo(),
|
|
263
|
+
]);
|
|
264
|
+
spinner.stop();
|
|
265
|
+
const sections = buildStatusSections(statusData, versionInfo);
|
|
266
|
+
StatusFormatter.printSections(sections);
|
|
267
|
+
process.exit(0);
|
|
268
|
+
}
|
|
269
|
+
catch (error) {
|
|
270
|
+
spinner.fail('Failed to check status');
|
|
271
|
+
console.error(chalk.red('Error:'), error);
|
|
272
|
+
process.exit(1);
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
export function createStatusCommand() {
|
|
276
|
+
const statusCommand = new Command('status');
|
|
277
|
+
statusCommand
|
|
278
|
+
.description('Check ARK system status')
|
|
279
|
+
.action(() => checkStatus());
|
|
280
|
+
return statusCommand;
|
|
281
|
+
}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { Command } from 'commander';
|
|
2
|
+
import { execa } from 'execa';
|
|
3
|
+
import output from '../../lib/output.js';
|
|
4
|
+
async function fetchResourceTargets(resourceType) {
|
|
5
|
+
const result = await execa('kubectl', ['get', `${resourceType}s`, '-o', 'json'], {
|
|
6
|
+
stdio: 'pipe',
|
|
7
|
+
});
|
|
8
|
+
const data = JSON.parse(result.stdout);
|
|
9
|
+
const items = data.items || [];
|
|
10
|
+
return items.map((item) => ({
|
|
11
|
+
type: resourceType,
|
|
12
|
+
name: item.metadata.name,
|
|
13
|
+
id: `${resourceType}/${item.metadata.name}`,
|
|
14
|
+
available: item.status?.available || item.status?.phase === 'ready' || true,
|
|
15
|
+
}));
|
|
16
|
+
}
|
|
17
|
+
async function listTargets(options) {
|
|
18
|
+
try {
|
|
19
|
+
// Fetch all resource types in parallel
|
|
20
|
+
const resourceTypes = options.type
|
|
21
|
+
? [options.type]
|
|
22
|
+
: ['model', 'agent', 'team', 'tool'];
|
|
23
|
+
const targetPromises = resourceTypes.map((type) => fetchResourceTargets(type));
|
|
24
|
+
const targetArrays = await Promise.all(targetPromises);
|
|
25
|
+
// Flatten all targets into single array
|
|
26
|
+
const allTargets = targetArrays.flat();
|
|
27
|
+
// Sort targets by type and name
|
|
28
|
+
allTargets.sort((a, b) => {
|
|
29
|
+
if (a.type !== b.type) {
|
|
30
|
+
return a.type.localeCompare(b.type);
|
|
31
|
+
}
|
|
32
|
+
return a.name.localeCompare(b.name);
|
|
33
|
+
});
|
|
34
|
+
if (options.output === 'json') {
|
|
35
|
+
console.log(JSON.stringify(allTargets, null, 2));
|
|
36
|
+
}
|
|
37
|
+
else {
|
|
38
|
+
if (allTargets.length === 0) {
|
|
39
|
+
output.warning('no targets available');
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
// Simple list output with type/name format
|
|
43
|
+
for (const target of allTargets) {
|
|
44
|
+
console.log(target.id);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
catch (error) {
|
|
49
|
+
output.error('fetching targets:', error instanceof Error ? error.message : error);
|
|
50
|
+
process.exit(1);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
export function createTargetsCommand(_) {
|
|
54
|
+
const targets = new Command('targets');
|
|
55
|
+
targets
|
|
56
|
+
.description('list available query targets (agents, teams, models, tools)')
|
|
57
|
+
.option('-o, --output <format>', 'output format (json or text)', 'text')
|
|
58
|
+
.option('-t, --type <type>', 'filter by type (agent, team, model, tool)')
|
|
59
|
+
.action(async (options) => {
|
|
60
|
+
await listTargets(options);
|
|
61
|
+
});
|
|
62
|
+
targets
|
|
63
|
+
.command('list')
|
|
64
|
+
.alias('ls')
|
|
65
|
+
.description('list all available query targets')
|
|
66
|
+
.option('-o, --output <format>', 'output format (json or text)', 'text')
|
|
67
|
+
.option('-t, --type <type>', 'filter by type (agent, team, model, tool)')
|
|
68
|
+
.action(async (options) => {
|
|
69
|
+
await listTargets(options);
|
|
70
|
+
});
|
|
71
|
+
return targets;
|
|
72
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
import { jest } from '@jest/globals';
|
|
2
|
+
import { Command } from 'commander';
|
|
3
|
+
// Mock execa to avoid real kubectl calls
|
|
4
|
+
jest.unstable_mockModule('execa', () => ({
|
|
5
|
+
execa: jest.fn(),
|
|
6
|
+
}));
|
|
7
|
+
const mockOutput = {
|
|
8
|
+
warning: jest.fn(),
|
|
9
|
+
error: jest.fn(),
|
|
10
|
+
};
|
|
11
|
+
jest.unstable_mockModule('../../lib/output.js', () => ({
|
|
12
|
+
default: mockOutput,
|
|
13
|
+
}));
|
|
14
|
+
const mockExit = jest.spyOn(process, 'exit').mockImplementation((() => {
|
|
15
|
+
throw new Error('process.exit called');
|
|
16
|
+
}));
|
|
17
|
+
const mockConsoleLog = jest.spyOn(console, 'log').mockImplementation(() => { });
|
|
18
|
+
const { execa } = await import('execa');
|
|
19
|
+
const mockExeca = execa;
|
|
20
|
+
const { createTargetsCommand } = await import('./index.js');
|
|
21
|
+
describe('targets command', () => {
|
|
22
|
+
beforeEach(() => {
|
|
23
|
+
jest.clearAllMocks();
|
|
24
|
+
});
|
|
25
|
+
it('creates command with correct structure', () => {
|
|
26
|
+
const command = createTargetsCommand({});
|
|
27
|
+
expect(command).toBeInstanceOf(Command);
|
|
28
|
+
expect(command.name()).toBe('targets');
|
|
29
|
+
});
|
|
30
|
+
it('lists targets in text format', async () => {
|
|
31
|
+
// Mock kubectl responses for each resource type (order: model, agent, team, tool)
|
|
32
|
+
mockExeca
|
|
33
|
+
.mockResolvedValueOnce({
|
|
34
|
+
stdout: JSON.stringify({
|
|
35
|
+
items: [{ metadata: { name: 'gpt-4' }, status: { available: true } }],
|
|
36
|
+
}),
|
|
37
|
+
})
|
|
38
|
+
.mockResolvedValueOnce({
|
|
39
|
+
stdout: JSON.stringify({
|
|
40
|
+
items: [
|
|
41
|
+
{ metadata: { name: 'gpt-assistant' }, status: { phase: 'ready' } },
|
|
42
|
+
],
|
|
43
|
+
}),
|
|
44
|
+
})
|
|
45
|
+
.mockResolvedValueOnce({ stdout: JSON.stringify({ items: [] }) })
|
|
46
|
+
.mockResolvedValueOnce({ stdout: JSON.stringify({ items: [] }) });
|
|
47
|
+
const command = createTargetsCommand({});
|
|
48
|
+
await command.parseAsync(['node', 'test']);
|
|
49
|
+
expect(mockExeca).toHaveBeenCalledWith('kubectl', ['get', 'models', '-o', 'json'], {
|
|
50
|
+
stdio: 'pipe',
|
|
51
|
+
});
|
|
52
|
+
expect(mockExeca).toHaveBeenCalledWith('kubectl', ['get', 'agents', '-o', 'json'], {
|
|
53
|
+
stdio: 'pipe',
|
|
54
|
+
});
|
|
55
|
+
expect(mockConsoleLog).toHaveBeenCalledWith('agent/gpt-assistant');
|
|
56
|
+
expect(mockConsoleLog).toHaveBeenCalledWith('model/gpt-4');
|
|
57
|
+
});
|
|
58
|
+
it('lists targets in json format', async () => {
|
|
59
|
+
// Order: model, agent, team, tool
|
|
60
|
+
mockExeca
|
|
61
|
+
.mockResolvedValueOnce({ stdout: JSON.stringify({ items: [] }) })
|
|
62
|
+
.mockResolvedValueOnce({
|
|
63
|
+
stdout: JSON.stringify({
|
|
64
|
+
items: [{ metadata: { name: 'gpt' }, status: { phase: 'ready' } }],
|
|
65
|
+
}),
|
|
66
|
+
})
|
|
67
|
+
.mockResolvedValueOnce({ stdout: JSON.stringify({ items: [] }) })
|
|
68
|
+
.mockResolvedValueOnce({ stdout: JSON.stringify({ items: [] }) });
|
|
69
|
+
const command = createTargetsCommand({});
|
|
70
|
+
await command.parseAsync(['node', 'test', '-o', 'json']);
|
|
71
|
+
const expectedTargets = [
|
|
72
|
+
{ type: 'agent', name: 'gpt', id: 'agent/gpt', available: true },
|
|
73
|
+
];
|
|
74
|
+
expect(mockConsoleLog).toHaveBeenCalledWith(JSON.stringify(expectedTargets, null, 2));
|
|
75
|
+
});
|
|
76
|
+
it('filters targets by type', async () => {
|
|
77
|
+
mockExeca.mockResolvedValueOnce({
|
|
78
|
+
stdout: JSON.stringify({
|
|
79
|
+
items: [
|
|
80
|
+
{ metadata: { name: 'gpt' }, status: { phase: 'ready' } },
|
|
81
|
+
{ metadata: { name: 'helper' }, status: { phase: 'ready' } },
|
|
82
|
+
],
|
|
83
|
+
}),
|
|
84
|
+
});
|
|
85
|
+
const command = createTargetsCommand({});
|
|
86
|
+
await command.parseAsync(['node', 'test', '-t', 'agent']);
|
|
87
|
+
expect(mockExeca).toHaveBeenCalledWith('kubectl', ['get', 'agents', '-o', 'json'], {
|
|
88
|
+
stdio: 'pipe',
|
|
89
|
+
});
|
|
90
|
+
expect(mockExeca).toHaveBeenCalledTimes(1); // Only agents, not other types
|
|
91
|
+
expect(mockConsoleLog).toHaveBeenCalledWith('agent/gpt');
|
|
92
|
+
expect(mockConsoleLog).toHaveBeenCalledWith('agent/helper');
|
|
93
|
+
});
|
|
94
|
+
it('sorts targets by type then name', async () => {
|
|
95
|
+
// Order: model, agent, team, tool
|
|
96
|
+
mockExeca
|
|
97
|
+
.mockResolvedValueOnce({
|
|
98
|
+
stdout: JSON.stringify({
|
|
99
|
+
items: [
|
|
100
|
+
{ metadata: { name: 'b' }, status: { available: true } },
|
|
101
|
+
{ metadata: { name: 'a' }, status: { available: true } },
|
|
102
|
+
],
|
|
103
|
+
}),
|
|
104
|
+
})
|
|
105
|
+
.mockResolvedValueOnce({
|
|
106
|
+
stdout: JSON.stringify({
|
|
107
|
+
items: [
|
|
108
|
+
{ metadata: { name: 'z' }, status: { phase: 'ready' } },
|
|
109
|
+
{ metadata: { name: 'a' }, status: { phase: 'ready' } },
|
|
110
|
+
],
|
|
111
|
+
}),
|
|
112
|
+
})
|
|
113
|
+
.mockResolvedValueOnce({ stdout: JSON.stringify({ items: [] }) })
|
|
114
|
+
.mockResolvedValueOnce({ stdout: JSON.stringify({ items: [] }) });
|
|
115
|
+
const command = createTargetsCommand({});
|
|
116
|
+
await command.parseAsync(['node', 'test']);
|
|
117
|
+
// Check order of calls - sorted by type then name
|
|
118
|
+
const calls = mockConsoleLog.mock.calls
|
|
119
|
+
.filter((call) => call[0] && call[0].includes('/'))
|
|
120
|
+
.map((call) => call[0]);
|
|
121
|
+
expect(calls).toEqual(['agent/a', 'agent/z', 'model/a', 'model/b']);
|
|
122
|
+
});
|
|
123
|
+
it('shows warning when no targets', async () => {
|
|
124
|
+
// All resource types return empty (order: model, agent, team, tool)
|
|
125
|
+
mockExeca
|
|
126
|
+
.mockResolvedValueOnce({ stdout: JSON.stringify({ items: [] }) })
|
|
127
|
+
.mockResolvedValueOnce({ stdout: JSON.stringify({ items: [] }) })
|
|
128
|
+
.mockResolvedValueOnce({ stdout: JSON.stringify({ items: [] }) })
|
|
129
|
+
.mockResolvedValueOnce({ stdout: JSON.stringify({ items: [] }) });
|
|
130
|
+
const command = createTargetsCommand({});
|
|
131
|
+
await command.parseAsync(['node', 'test']);
|
|
132
|
+
expect(mockOutput.warning).toHaveBeenCalledWith('no targets available');
|
|
133
|
+
});
|
|
134
|
+
it('handles errors', async () => {
|
|
135
|
+
mockExeca.mockRejectedValue(new Error('kubectl not found'));
|
|
136
|
+
const command = createTargetsCommand({});
|
|
137
|
+
await expect(command.parseAsync(['node', 'test'])).rejects.toThrow('process.exit called');
|
|
138
|
+
expect(mockOutput.error).toHaveBeenCalledWith('fetching targets:', 'kubectl not found');
|
|
139
|
+
expect(mockExit).toHaveBeenCalledWith(1);
|
|
140
|
+
});
|
|
141
|
+
it('list subcommand works', async () => {
|
|
142
|
+
// Order: model, agent, team, tool
|
|
143
|
+
mockExeca
|
|
144
|
+
.mockResolvedValueOnce({ stdout: JSON.stringify({ items: [] }) })
|
|
145
|
+
.mockResolvedValueOnce({ stdout: JSON.stringify({ items: [] }) })
|
|
146
|
+
.mockResolvedValueOnce({ stdout: JSON.stringify({ items: [] }) })
|
|
147
|
+
.mockResolvedValueOnce({ stdout: JSON.stringify({ items: [] }) });
|
|
148
|
+
const command = createTargetsCommand({});
|
|
149
|
+
await command.parseAsync(['node', 'test', 'list']);
|
|
150
|
+
expect(mockExeca).toHaveBeenCalledWith('kubectl', ['get', 'models', '-o', 'json'], {
|
|
151
|
+
stdio: 'pipe',
|
|
152
|
+
});
|
|
153
|
+
});
|
|
154
|
+
});
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { Command } from 'commander';
|
|
2
|
+
import { execa } from 'execa';
|
|
3
|
+
import output from '../../lib/output.js';
|
|
4
|
+
import { executeQuery } from '../../lib/executeQuery.js';
|
|
5
|
+
async function listTeams(options) {
|
|
6
|
+
try {
|
|
7
|
+
// Use kubectl to get teams
|
|
8
|
+
const result = await execa('kubectl', ['get', 'teams', '-o', 'json'], {
|
|
9
|
+
stdio: 'pipe',
|
|
10
|
+
});
|
|
11
|
+
const data = JSON.parse(result.stdout);
|
|
12
|
+
const teams = data.items || [];
|
|
13
|
+
if (options.output === 'json') {
|
|
14
|
+
// Output the raw items for JSON format
|
|
15
|
+
console.log(JSON.stringify(teams, null, 2));
|
|
16
|
+
}
|
|
17
|
+
else {
|
|
18
|
+
if (teams.length === 0) {
|
|
19
|
+
output.info('No teams found');
|
|
20
|
+
return;
|
|
21
|
+
}
|
|
22
|
+
teams.forEach((team) => {
|
|
23
|
+
console.log(team.metadata.name);
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
catch (error) {
|
|
28
|
+
output.error('fetching teams:', error instanceof Error ? error.message : error);
|
|
29
|
+
process.exit(1);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
export function createTeamsCommand(_) {
|
|
33
|
+
const teamsCommand = new Command('teams');
|
|
34
|
+
teamsCommand
|
|
35
|
+
.description('List available teams')
|
|
36
|
+
.option('-o, --output <format>', 'Output format (json)', 'text')
|
|
37
|
+
.action(async (options) => {
|
|
38
|
+
await listTeams(options);
|
|
39
|
+
});
|
|
40
|
+
const listCommand = new Command('list');
|
|
41
|
+
listCommand
|
|
42
|
+
.alias('ls')
|
|
43
|
+
.description('List available teams')
|
|
44
|
+
.option('-o, --output <format>', 'Output format (json)', 'text')
|
|
45
|
+
.action(async (options) => {
|
|
46
|
+
await listTeams(options);
|
|
47
|
+
});
|
|
48
|
+
teamsCommand.addCommand(listCommand);
|
|
49
|
+
// Add query command
|
|
50
|
+
const queryCommand = new Command('query');
|
|
51
|
+
queryCommand
|
|
52
|
+
.description('Query a team')
|
|
53
|
+
.argument('<name>', 'Team name')
|
|
54
|
+
.argument('<message>', 'Message to send')
|
|
55
|
+
.action(async (name, message) => {
|
|
56
|
+
await executeQuery({
|
|
57
|
+
targetType: 'team',
|
|
58
|
+
targetName: name,
|
|
59
|
+
message,
|
|
60
|
+
});
|
|
61
|
+
});
|
|
62
|
+
teamsCommand.addCommand(queryCommand);
|
|
63
|
+
return teamsCommand;
|
|
64
|
+
}
|