@agents-at-scale/ark 0.1.34 → 0.1.35-rc.1
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 +50 -0
- package/dist/arkServices.js +153 -0
- package/dist/charts/charts.d.ts +5 -0
- package/dist/charts/charts.js +6 -0
- package/dist/charts/dependencies.d.ts +6 -0
- package/dist/charts/dependencies.js +50 -0
- package/dist/charts/types.d.ts +40 -0
- package/dist/charts/types.js +1 -0
- package/dist/commands/agents/index.d.ts +2 -0
- package/dist/commands/agents/index.js +56 -0
- package/dist/commands/agents/selector.d.ts +8 -0
- package/dist/commands/agents/selector.js +53 -0
- package/dist/commands/agents.d.ts +2 -0
- package/dist/commands/agents.js +53 -0
- package/dist/commands/chat/index.d.ts +2 -0
- package/dist/commands/chat/index.js +45 -0
- package/dist/commands/chat.d.ts +2 -0
- package/dist/commands/chat.js +45 -0
- package/dist/commands/cluster/get.d.ts +2 -0
- package/dist/commands/cluster/get.js +39 -0
- package/dist/commands/cluster/index.js +2 -4
- package/dist/commands/completion/index.d.ts +2 -0
- package/dist/commands/completion/index.js +268 -0
- package/dist/commands/completion.js +159 -2
- package/dist/commands/config/index.d.ts +2 -0
- package/dist/commands/config/index.js +42 -0
- package/dist/commands/config.d.ts +0 -3
- package/dist/commands/config.js +38 -321
- package/dist/commands/dashboard/index.d.ts +3 -0
- package/dist/commands/dashboard/index.js +39 -0
- package/dist/commands/dashboard.d.ts +3 -0
- package/dist/commands/dashboard.js +39 -0
- package/dist/commands/dev/index.d.ts +2 -0
- package/dist/commands/dev/index.js +9 -0
- package/dist/commands/dev/tool/check.d.ts +2 -0
- package/dist/commands/dev/tool/check.js +142 -0
- package/dist/commands/dev/tool/clean.d.ts +2 -0
- package/dist/commands/dev/tool/clean.js +153 -0
- package/dist/commands/dev/tool/generate.d.ts +2 -0
- package/dist/commands/dev/tool/generate.js +28 -0
- package/dist/commands/dev/tool/index.d.ts +2 -0
- package/dist/commands/dev/tool/index.js +14 -0
- package/dist/commands/dev/tool/init.d.ts +2 -0
- package/dist/commands/dev/tool/init.js +320 -0
- package/dist/commands/dev/tool/shared.d.ts +5 -0
- package/dist/commands/dev/tool/shared.js +256 -0
- package/dist/commands/dev/tool/status.d.ts +2 -0
- package/dist/commands/dev/tool/status.js +136 -0
- package/dist/commands/dev/tool.d.ts +2 -0
- package/dist/commands/dev/tool.js +559 -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/install/index.d.ts +6 -0
- package/dist/commands/install/index.js +165 -0
- package/dist/commands/install.d.ts +3 -0
- package/dist/commands/install.js +147 -0
- package/dist/commands/models/create.d.ts +1 -0
- package/dist/commands/models/create.js +213 -0
- package/dist/commands/models/index.d.ts +2 -0
- package/dist/commands/models/index.js +65 -0
- package/dist/commands/models/selector.d.ts +8 -0
- package/dist/commands/models/selector.js +53 -0
- package/dist/commands/routes/index.d.ts +2 -0
- package/dist/commands/routes/index.js +101 -0
- package/dist/commands/routes.d.ts +2 -0
- package/dist/commands/routes.js +101 -0
- package/dist/commands/status/index.d.ts +3 -0
- package/dist/commands/status/index.js +33 -0
- package/dist/commands/status.d.ts +3 -0
- package/dist/commands/status.js +33 -0
- package/dist/commands/targets/index.d.ts +2 -0
- package/dist/commands/targets/index.js +65 -0
- package/dist/commands/targets.d.ts +2 -0
- package/dist/commands/targets.js +65 -0
- package/dist/commands/teams/index.d.ts +2 -0
- package/dist/commands/teams/index.js +54 -0
- package/dist/commands/teams/selector.d.ts +8 -0
- package/dist/commands/teams/selector.js +55 -0
- package/dist/commands/tools/index.d.ts +2 -0
- package/dist/commands/tools/index.js +54 -0
- package/dist/commands/tools/selector.d.ts +8 -0
- package/dist/commands/tools/selector.js +53 -0
- package/dist/commands/uninstall/index.d.ts +2 -0
- package/dist/commands/uninstall/index.js +84 -0
- package/dist/commands/uninstall.d.ts +2 -0
- package/dist/commands/uninstall.js +83 -0
- package/dist/components/ChatUI.d.ts +16 -0
- package/dist/components/ChatUI.js +801 -0
- package/dist/components/StatusView.d.ts +10 -0
- package/dist/components/StatusView.js +39 -0
- package/dist/components/statusChecker.d.ts +10 -13
- package/dist/components/statusChecker.js +128 -65
- package/dist/config.js +3 -10
- package/dist/index.d.ts +1 -1
- package/dist/index.js +31 -36
- 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 +93 -0
- package/dist/lib/arkStatus.d.ts +5 -0
- package/dist/lib/arkStatus.js +20 -0
- package/dist/lib/chatClient.d.ts +33 -0
- package/dist/lib/chatClient.js +101 -0
- package/dist/lib/cluster.d.ts +2 -1
- package/dist/lib/cluster.js +27 -3
- package/dist/lib/commandUtils.d.ts +4 -0
- package/dist/lib/commandUtils.js +18 -0
- package/dist/lib/commandUtils.test.d.ts +1 -0
- package/dist/lib/commandUtils.test.js +44 -0
- package/dist/lib/config.d.ts +24 -80
- package/dist/lib/config.js +68 -205
- package/dist/lib/config.test.d.ts +1 -0
- package/dist/lib/config.test.js +93 -0
- package/dist/lib/dev/tools/analyzer.d.ts +30 -0
- package/dist/lib/dev/tools/analyzer.js +190 -0
- package/dist/lib/dev/tools/discover_tools.py +392 -0
- package/dist/lib/dev/tools/mcp-types.d.ts +28 -0
- package/dist/lib/dev/tools/mcp-types.js +86 -0
- package/dist/lib/dev/tools/types.d.ts +50 -0
- package/dist/lib/dev/tools/types.js +1 -0
- package/dist/lib/output.d.ts +36 -0
- package/dist/lib/output.js +89 -0
- package/dist/lib/types.d.ts +8 -3
- package/dist/types/types.d.ts +40 -0
- package/dist/types/types.js +1 -0
- package/dist/ui/MainMenu.js +158 -90
- package/dist/ui/statusFormatter.d.ts +4 -1
- package/dist/ui/statusFormatter.js +91 -19
- package/package.json +16 -4
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
import { Command } from 'commander';
|
|
2
|
+
import chalk from 'chalk';
|
|
3
|
+
import { execa } from 'execa';
|
|
4
|
+
import inquirer from 'inquirer';
|
|
5
|
+
import { isCommandAvailable } from '../../lib/commandUtils.js';
|
|
6
|
+
import { getClusterInfo } from '../../lib/cluster.js';
|
|
7
|
+
import output from '../../lib/output.js';
|
|
8
|
+
import { getInstallableServices, arkDependencies } from '../../arkServices.js';
|
|
9
|
+
import { isArkReady } from '../../lib/arkStatus.js';
|
|
10
|
+
import ora from 'ora';
|
|
11
|
+
export async function installArk(options = {}) {
|
|
12
|
+
// Validate that --wait-for-ready requires -y
|
|
13
|
+
if (options.waitForReady && !options.yes) {
|
|
14
|
+
output.error('--wait-for-ready requires -y flag for non-interactive mode');
|
|
15
|
+
process.exit(1);
|
|
16
|
+
}
|
|
17
|
+
// Check if helm is installed
|
|
18
|
+
const helmInstalled = await isCommandAvailable('helm');
|
|
19
|
+
if (!helmInstalled) {
|
|
20
|
+
output.error('helm is not installed. please install helm first:');
|
|
21
|
+
output.info('https://helm.sh/docs/intro/install/');
|
|
22
|
+
process.exit(1);
|
|
23
|
+
}
|
|
24
|
+
// Check if kubectl is installed (needed for some dependencies)
|
|
25
|
+
const kubectlInstalled = await isCommandAvailable('kubectl');
|
|
26
|
+
if (!kubectlInstalled) {
|
|
27
|
+
output.error('kubectl is not installed. please install kubectl first:');
|
|
28
|
+
output.info('https://kubernetes.io/docs/tasks/tools/');
|
|
29
|
+
process.exit(1);
|
|
30
|
+
}
|
|
31
|
+
// Check cluster connectivity
|
|
32
|
+
const clusterInfo = await getClusterInfo();
|
|
33
|
+
if (clusterInfo.error) {
|
|
34
|
+
output.error('no kubernetes cluster detected');
|
|
35
|
+
output.info('please ensure you have a running cluster and kubectl is configured.');
|
|
36
|
+
output.info('');
|
|
37
|
+
output.info('for local development, we recommend minikube:');
|
|
38
|
+
output.info('• install: https://minikube.sigs.k8s.io/docs/start');
|
|
39
|
+
output.info('• start cluster: minikube start');
|
|
40
|
+
output.info('');
|
|
41
|
+
output.info('alternatively, you can use kind or docker desktop.');
|
|
42
|
+
process.exit(1);
|
|
43
|
+
}
|
|
44
|
+
// Show cluster info
|
|
45
|
+
output.success(`connected to cluster: ${chalk.bold(clusterInfo.context)}`);
|
|
46
|
+
output.info(`type: ${clusterInfo.type}`);
|
|
47
|
+
output.info(`namespace: ${clusterInfo.namespace}`);
|
|
48
|
+
if (clusterInfo.ip) {
|
|
49
|
+
output.info(`ip: ${clusterInfo.ip}`);
|
|
50
|
+
}
|
|
51
|
+
console.log(); // Add blank line after cluster info
|
|
52
|
+
// Ask about installing dependencies
|
|
53
|
+
const shouldInstallDeps = options.yes || (await inquirer.prompt([
|
|
54
|
+
{
|
|
55
|
+
type: 'confirm',
|
|
56
|
+
name: 'shouldInstallDeps',
|
|
57
|
+
message: 'install required dependencies (cert-manager, gateway api)?',
|
|
58
|
+
default: true,
|
|
59
|
+
},
|
|
60
|
+
])).shouldInstallDeps;
|
|
61
|
+
if (shouldInstallDeps) {
|
|
62
|
+
for (const dep of Object.values(arkDependencies)) {
|
|
63
|
+
output.info(`installing ${dep.description || dep.name}...`);
|
|
64
|
+
try {
|
|
65
|
+
await execa(dep.command, dep.args, {
|
|
66
|
+
stdio: 'inherit',
|
|
67
|
+
});
|
|
68
|
+
output.success(`${dep.name} completed`);
|
|
69
|
+
console.log(); // Add blank line after dependency
|
|
70
|
+
}
|
|
71
|
+
catch {
|
|
72
|
+
console.log(); // Add blank line after error
|
|
73
|
+
process.exit(1);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
// Get installable services and iterate through them
|
|
78
|
+
const services = getInstallableServices();
|
|
79
|
+
for (const service of Object.values(services)) {
|
|
80
|
+
// Ask for confirmation
|
|
81
|
+
const shouldInstall = options.yes || (await inquirer.prompt([
|
|
82
|
+
{
|
|
83
|
+
type: 'confirm',
|
|
84
|
+
name: 'shouldInstall',
|
|
85
|
+
message: `install ${chalk.bold(service.name)}? ${service.description ? chalk.gray(`(${service.description.toLowerCase()})`) : ''}`,
|
|
86
|
+
default: true,
|
|
87
|
+
},
|
|
88
|
+
])).shouldInstall;
|
|
89
|
+
if (!shouldInstall) {
|
|
90
|
+
output.warning(`skipping ${service.name}`);
|
|
91
|
+
continue;
|
|
92
|
+
}
|
|
93
|
+
try {
|
|
94
|
+
// Build helm arguments
|
|
95
|
+
const helmArgs = [
|
|
96
|
+
'upgrade',
|
|
97
|
+
'--install',
|
|
98
|
+
service.helmReleaseName,
|
|
99
|
+
service.chartPath,
|
|
100
|
+
'--namespace',
|
|
101
|
+
service.namespace,
|
|
102
|
+
];
|
|
103
|
+
// Add any additional args from the service definition
|
|
104
|
+
if (service.installArgs) {
|
|
105
|
+
helmArgs.push(...service.installArgs);
|
|
106
|
+
}
|
|
107
|
+
// Run helm upgrade --install with streaming output
|
|
108
|
+
await execa('helm', helmArgs, {
|
|
109
|
+
stdio: 'inherit',
|
|
110
|
+
});
|
|
111
|
+
console.log(); // Add blank line after command output
|
|
112
|
+
}
|
|
113
|
+
catch {
|
|
114
|
+
// Continue with remaining services on error
|
|
115
|
+
console.log(); // Add blank line after error output
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
// Wait for ARK to be ready if requested
|
|
119
|
+
if (options.waitForReady) {
|
|
120
|
+
// Parse timeout value (e.g., '30s', '2m', '60')
|
|
121
|
+
const parseTimeout = (value) => {
|
|
122
|
+
const match = value.match(/^(\d+)([sm])?$/);
|
|
123
|
+
if (!match) {
|
|
124
|
+
throw new Error('Invalid timeout format. Use format like 30s or 2m');
|
|
125
|
+
}
|
|
126
|
+
const num = parseInt(match[1], 10);
|
|
127
|
+
const unit = match[2] || 's';
|
|
128
|
+
return unit === 'm' ? num * 60 : num;
|
|
129
|
+
};
|
|
130
|
+
try {
|
|
131
|
+
const timeoutSeconds = parseTimeout(options.waitForReady);
|
|
132
|
+
const startTime = Date.now();
|
|
133
|
+
const endTime = startTime + timeoutSeconds * 1000;
|
|
134
|
+
const spinner = ora(`Waiting for ARK to be ready (timeout: ${timeoutSeconds}s)...`).start();
|
|
135
|
+
while (Date.now() < endTime) {
|
|
136
|
+
if (await isArkReady()) {
|
|
137
|
+
spinner.succeed('ARK is ready!');
|
|
138
|
+
return;
|
|
139
|
+
}
|
|
140
|
+
const elapsed = Math.floor((Date.now() - startTime) / 1000);
|
|
141
|
+
spinner.text = `Waiting for ARK to be ready (${elapsed}/${timeoutSeconds}s)...`;
|
|
142
|
+
// Wait 2 seconds before checking again
|
|
143
|
+
await new Promise(resolve => setTimeout(resolve, 2000));
|
|
144
|
+
}
|
|
145
|
+
// Timeout reached
|
|
146
|
+
spinner.fail(`ARK did not become ready within ${timeoutSeconds} seconds`);
|
|
147
|
+
process.exit(1);
|
|
148
|
+
}
|
|
149
|
+
catch (error) {
|
|
150
|
+
output.error(`Failed to wait for ready: ${error instanceof Error ? error.message : 'Unknown error'}`);
|
|
151
|
+
process.exit(1);
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
export function createInstallCommand() {
|
|
156
|
+
const command = new Command('install');
|
|
157
|
+
command
|
|
158
|
+
.description('Install ARK components using Helm')
|
|
159
|
+
.option('-y, --yes', 'automatically confirm all installations')
|
|
160
|
+
.option('--wait-for-ready <timeout>', 'wait for ARK to be ready after installation (e.g., 30s, 2m)')
|
|
161
|
+
.action(async (options) => {
|
|
162
|
+
await installArk(options);
|
|
163
|
+
});
|
|
164
|
+
return command;
|
|
165
|
+
}
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
import { Command } from 'commander';
|
|
2
|
+
import chalk from 'chalk';
|
|
3
|
+
import { execa } from 'execa';
|
|
4
|
+
import inquirer from 'inquirer';
|
|
5
|
+
import { isCommandAvailable } from '../lib/commandUtils.js';
|
|
6
|
+
import { getClusterInfo } from '../lib/cluster.js';
|
|
7
|
+
import output from '../lib/output.js';
|
|
8
|
+
import { getInstallableServices, arkDependencies } from '../arkServices.js';
|
|
9
|
+
import { createModel } from './models/create.js';
|
|
10
|
+
export async function installArk() {
|
|
11
|
+
// Check if helm is installed
|
|
12
|
+
const helmInstalled = await isCommandAvailable('helm');
|
|
13
|
+
if (!helmInstalled) {
|
|
14
|
+
output.error('helm is not installed. please install helm first:');
|
|
15
|
+
output.info('https://helm.sh/docs/intro/install/');
|
|
16
|
+
process.exit(1);
|
|
17
|
+
}
|
|
18
|
+
// Check if kubectl is installed (needed for some dependencies)
|
|
19
|
+
const kubectlInstalled = await isCommandAvailable('kubectl');
|
|
20
|
+
if (!kubectlInstalled) {
|
|
21
|
+
output.error('kubectl is not installed. please install kubectl first:');
|
|
22
|
+
output.info('https://kubernetes.io/docs/tasks/tools/');
|
|
23
|
+
process.exit(1);
|
|
24
|
+
}
|
|
25
|
+
// Check cluster connectivity
|
|
26
|
+
const clusterInfo = await getClusterInfo();
|
|
27
|
+
if (clusterInfo.error) {
|
|
28
|
+
output.error('no kubernetes cluster detected');
|
|
29
|
+
output.info('please ensure you have a running cluster and kubectl is configured.');
|
|
30
|
+
output.info('');
|
|
31
|
+
output.info('for local development, we recommend minikube:');
|
|
32
|
+
output.info('• install: https://minikube.sigs.k8s.io/docs/start');
|
|
33
|
+
output.info('• start cluster: minikube start');
|
|
34
|
+
output.info('');
|
|
35
|
+
output.info('alternatively, you can use kind or docker desktop.');
|
|
36
|
+
process.exit(1);
|
|
37
|
+
}
|
|
38
|
+
// Show cluster info
|
|
39
|
+
output.success(`connected to cluster: ${chalk.bold(clusterInfo.context)}`);
|
|
40
|
+
output.info(`type: ${clusterInfo.type}`);
|
|
41
|
+
output.info(`namespace: ${clusterInfo.namespace}`);
|
|
42
|
+
if (clusterInfo.ip) {
|
|
43
|
+
output.info(`ip: ${clusterInfo.ip}`);
|
|
44
|
+
}
|
|
45
|
+
console.log(); // Add blank line after cluster info
|
|
46
|
+
// Ask about installing dependencies
|
|
47
|
+
const { shouldInstallDeps } = await inquirer.prompt([
|
|
48
|
+
{
|
|
49
|
+
type: 'confirm',
|
|
50
|
+
name: 'shouldInstallDeps',
|
|
51
|
+
message: 'install required dependencies (cert-manager, gateway api)?',
|
|
52
|
+
default: true,
|
|
53
|
+
},
|
|
54
|
+
]);
|
|
55
|
+
if (shouldInstallDeps) {
|
|
56
|
+
for (const dep of Object.values(arkDependencies)) {
|
|
57
|
+
output.info(`installing ${dep.description || dep.name}...`);
|
|
58
|
+
try {
|
|
59
|
+
await execa(dep.command, dep.args, {
|
|
60
|
+
stdio: 'inherit',
|
|
61
|
+
});
|
|
62
|
+
output.success(`${dep.name} completed`);
|
|
63
|
+
console.log(); // Add blank line after dependency
|
|
64
|
+
}
|
|
65
|
+
catch {
|
|
66
|
+
console.log(); // Add blank line after error
|
|
67
|
+
process.exit(1);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
// Get installable services and iterate through them
|
|
72
|
+
const services = getInstallableServices();
|
|
73
|
+
for (const service of Object.values(services)) {
|
|
74
|
+
// Ask for confirmation
|
|
75
|
+
const { shouldInstall } = await inquirer.prompt([
|
|
76
|
+
{
|
|
77
|
+
type: 'confirm',
|
|
78
|
+
name: 'shouldInstall',
|
|
79
|
+
message: `install ${chalk.bold(service.name)}? ${service.description ? chalk.gray(`(${service.description.toLowerCase()})`) : ''}`,
|
|
80
|
+
default: true,
|
|
81
|
+
},
|
|
82
|
+
]);
|
|
83
|
+
if (!shouldInstall) {
|
|
84
|
+
output.warning(`skipping ${service.name}`);
|
|
85
|
+
continue;
|
|
86
|
+
}
|
|
87
|
+
try {
|
|
88
|
+
// Build helm arguments
|
|
89
|
+
const helmArgs = [
|
|
90
|
+
'upgrade',
|
|
91
|
+
'--install',
|
|
92
|
+
service.helmReleaseName,
|
|
93
|
+
service.chartPath,
|
|
94
|
+
'--namespace',
|
|
95
|
+
service.namespace,
|
|
96
|
+
];
|
|
97
|
+
// Add any additional args from the service definition
|
|
98
|
+
if (service.installArgs) {
|
|
99
|
+
helmArgs.push(...service.installArgs);
|
|
100
|
+
}
|
|
101
|
+
// Run helm upgrade --install with streaming output
|
|
102
|
+
await execa('helm', helmArgs, {
|
|
103
|
+
stdio: 'inherit',
|
|
104
|
+
});
|
|
105
|
+
console.log(); // Add blank line after command output
|
|
106
|
+
}
|
|
107
|
+
catch {
|
|
108
|
+
// Continue with remaining services on error
|
|
109
|
+
console.log(); // Add blank line after error output
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
// Check for default model after installing services
|
|
113
|
+
output.info('checking for default model...');
|
|
114
|
+
let modelExists = false;
|
|
115
|
+
try {
|
|
116
|
+
await execa('kubectl', ['get', 'model', 'default'], { stdio: 'pipe' });
|
|
117
|
+
modelExists = true;
|
|
118
|
+
output.success('default model already configured');
|
|
119
|
+
}
|
|
120
|
+
catch {
|
|
121
|
+
output.warning('no default model found');
|
|
122
|
+
}
|
|
123
|
+
if (!modelExists) {
|
|
124
|
+
const { shouldCreateModel } = await inquirer.prompt([
|
|
125
|
+
{
|
|
126
|
+
type: 'confirm',
|
|
127
|
+
name: 'shouldCreateModel',
|
|
128
|
+
message: 'would you like to create a default model?',
|
|
129
|
+
default: true,
|
|
130
|
+
},
|
|
131
|
+
]);
|
|
132
|
+
if (shouldCreateModel) {
|
|
133
|
+
await createModel('default');
|
|
134
|
+
}
|
|
135
|
+
else {
|
|
136
|
+
output.warning('skipping model creation');
|
|
137
|
+
output.info('you can create a model later using ark models create or the dashboard');
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
export function createInstallCommand() {
|
|
142
|
+
const command = new Command('install');
|
|
143
|
+
command.description('Install ARK components using Helm').action(async () => {
|
|
144
|
+
await installArk();
|
|
145
|
+
});
|
|
146
|
+
return command;
|
|
147
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function createModel(modelName?: string): Promise<boolean>;
|
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
import { execa } from 'execa';
|
|
2
|
+
import inquirer from 'inquirer';
|
|
3
|
+
import output from '../../lib/output.js';
|
|
4
|
+
export async function createModel(modelName) {
|
|
5
|
+
// Step 1: Get model name if not provided
|
|
6
|
+
if (!modelName) {
|
|
7
|
+
const nameAnswer = await inquirer.prompt([
|
|
8
|
+
{
|
|
9
|
+
type: 'input',
|
|
10
|
+
name: 'modelName',
|
|
11
|
+
message: 'model name:',
|
|
12
|
+
default: 'default',
|
|
13
|
+
validate: (input) => {
|
|
14
|
+
if (!input)
|
|
15
|
+
return 'model name is required';
|
|
16
|
+
// Kubernetes name validation
|
|
17
|
+
if (!/^[a-z0-9]([-a-z0-9]*[a-z0-9])?$/.test(input)) {
|
|
18
|
+
return 'model name must be a valid Kubernetes resource name';
|
|
19
|
+
}
|
|
20
|
+
return true;
|
|
21
|
+
},
|
|
22
|
+
},
|
|
23
|
+
]);
|
|
24
|
+
modelName = nameAnswer.modelName;
|
|
25
|
+
}
|
|
26
|
+
// Check if model already exists
|
|
27
|
+
try {
|
|
28
|
+
await execa('kubectl', ['get', 'model', modelName], { stdio: 'pipe' });
|
|
29
|
+
output.warning(`model ${modelName} already exists`);
|
|
30
|
+
const { overwrite } = await inquirer.prompt([
|
|
31
|
+
{
|
|
32
|
+
type: 'confirm',
|
|
33
|
+
name: 'overwrite',
|
|
34
|
+
message: `overwrite existing model ${modelName}?`,
|
|
35
|
+
default: false,
|
|
36
|
+
},
|
|
37
|
+
]);
|
|
38
|
+
if (!overwrite) {
|
|
39
|
+
output.info('model creation cancelled');
|
|
40
|
+
return false;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
catch {
|
|
44
|
+
// Model doesn't exist, continue
|
|
45
|
+
}
|
|
46
|
+
// Step 2: Choose model type
|
|
47
|
+
const { modelType } = await inquirer.prompt([
|
|
48
|
+
{
|
|
49
|
+
type: 'list',
|
|
50
|
+
name: 'modelType',
|
|
51
|
+
message: 'select model provider:',
|
|
52
|
+
choices: [
|
|
53
|
+
{ name: 'Azure OpenAI', value: 'azure' },
|
|
54
|
+
{ name: 'OpenAI', value: 'openai' },
|
|
55
|
+
],
|
|
56
|
+
default: 'azure',
|
|
57
|
+
},
|
|
58
|
+
]);
|
|
59
|
+
// Step 3: Get common parameters
|
|
60
|
+
const commonAnswers = await inquirer.prompt([
|
|
61
|
+
{
|
|
62
|
+
type: 'input',
|
|
63
|
+
name: 'modelVersion',
|
|
64
|
+
message: 'model version:',
|
|
65
|
+
default: 'gpt-4o-mini',
|
|
66
|
+
},
|
|
67
|
+
{
|
|
68
|
+
type: 'input',
|
|
69
|
+
name: 'baseUrl',
|
|
70
|
+
message: 'base URL:',
|
|
71
|
+
validate: (input) => {
|
|
72
|
+
if (!input)
|
|
73
|
+
return 'base URL is required';
|
|
74
|
+
try {
|
|
75
|
+
new URL(input);
|
|
76
|
+
return true;
|
|
77
|
+
}
|
|
78
|
+
catch {
|
|
79
|
+
return 'please enter a valid URL';
|
|
80
|
+
}
|
|
81
|
+
},
|
|
82
|
+
},
|
|
83
|
+
]);
|
|
84
|
+
// Remove trailing slash from base URL
|
|
85
|
+
const baseUrl = commonAnswers.baseUrl.replace(/\/$/, '');
|
|
86
|
+
// Step 4: Get provider-specific parameters
|
|
87
|
+
let apiVersion = '';
|
|
88
|
+
if (modelType === 'azure') {
|
|
89
|
+
const azureAnswers = await inquirer.prompt([
|
|
90
|
+
{
|
|
91
|
+
type: 'input',
|
|
92
|
+
name: 'apiVersion',
|
|
93
|
+
message: 'Azure API version:',
|
|
94
|
+
default: '2024-12-01-preview',
|
|
95
|
+
},
|
|
96
|
+
]);
|
|
97
|
+
apiVersion = azureAnswers.apiVersion;
|
|
98
|
+
}
|
|
99
|
+
// Step 5: Get API key (password input)
|
|
100
|
+
const { apiKey } = await inquirer.prompt([
|
|
101
|
+
{
|
|
102
|
+
type: 'password',
|
|
103
|
+
name: 'apiKey',
|
|
104
|
+
message: 'API key:',
|
|
105
|
+
mask: '*',
|
|
106
|
+
validate: (input) => {
|
|
107
|
+
if (!input)
|
|
108
|
+
return 'API key is required';
|
|
109
|
+
return true;
|
|
110
|
+
},
|
|
111
|
+
},
|
|
112
|
+
]);
|
|
113
|
+
// Step 6: Create the Kubernetes secret
|
|
114
|
+
const secretName = `${modelName}-model-api-key`;
|
|
115
|
+
output.info(`creating secret ${secretName}...`);
|
|
116
|
+
try {
|
|
117
|
+
// Delete existing secret if it exists (update scenario)
|
|
118
|
+
await execa('kubectl', ['delete', 'secret', secretName], {
|
|
119
|
+
stdio: 'pipe',
|
|
120
|
+
}).catch(() => {
|
|
121
|
+
// Ignore error if secret doesn't exist
|
|
122
|
+
});
|
|
123
|
+
// Create the secret
|
|
124
|
+
await execa('kubectl', [
|
|
125
|
+
'create',
|
|
126
|
+
'secret',
|
|
127
|
+
'generic',
|
|
128
|
+
secretName,
|
|
129
|
+
`--from-literal=api-key=${apiKey}`,
|
|
130
|
+
], { stdio: 'pipe' });
|
|
131
|
+
output.success(`secret ${secretName} created`);
|
|
132
|
+
}
|
|
133
|
+
catch (error) {
|
|
134
|
+
output.error('failed to create secret');
|
|
135
|
+
console.error(error);
|
|
136
|
+
return false;
|
|
137
|
+
}
|
|
138
|
+
// Step 7: Create the Model resource
|
|
139
|
+
output.info(`creating model ${modelName}...`);
|
|
140
|
+
const modelManifest = {
|
|
141
|
+
apiVersion: 'ark.mckinsey.com/v1alpha1',
|
|
142
|
+
kind: 'Model',
|
|
143
|
+
metadata: {
|
|
144
|
+
name: modelName,
|
|
145
|
+
},
|
|
146
|
+
spec: {
|
|
147
|
+
type: modelType,
|
|
148
|
+
model: {
|
|
149
|
+
value: commonAnswers.modelVersion,
|
|
150
|
+
},
|
|
151
|
+
config: {},
|
|
152
|
+
},
|
|
153
|
+
};
|
|
154
|
+
// Add provider-specific config
|
|
155
|
+
if (modelType === 'azure') {
|
|
156
|
+
modelManifest.spec.config.azure = {
|
|
157
|
+
apiKey: {
|
|
158
|
+
valueFrom: {
|
|
159
|
+
secretKeyRef: {
|
|
160
|
+
name: secretName,
|
|
161
|
+
key: 'api-key',
|
|
162
|
+
},
|
|
163
|
+
},
|
|
164
|
+
},
|
|
165
|
+
baseUrl: {
|
|
166
|
+
value: baseUrl,
|
|
167
|
+
},
|
|
168
|
+
apiVersion: {
|
|
169
|
+
value: apiVersion,
|
|
170
|
+
},
|
|
171
|
+
};
|
|
172
|
+
}
|
|
173
|
+
else {
|
|
174
|
+
modelManifest.spec.config.openai = {
|
|
175
|
+
apiKey: {
|
|
176
|
+
valueFrom: {
|
|
177
|
+
secretKeyRef: {
|
|
178
|
+
name: secretName,
|
|
179
|
+
key: 'api-key',
|
|
180
|
+
},
|
|
181
|
+
},
|
|
182
|
+
},
|
|
183
|
+
baseUrl: {
|
|
184
|
+
value: baseUrl,
|
|
185
|
+
},
|
|
186
|
+
};
|
|
187
|
+
}
|
|
188
|
+
try {
|
|
189
|
+
// Apply the model manifest using kubectl
|
|
190
|
+
const manifestJson = JSON.stringify(modelManifest);
|
|
191
|
+
await execa('kubectl', ['apply', '-f', '-'], {
|
|
192
|
+
input: manifestJson,
|
|
193
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
194
|
+
});
|
|
195
|
+
output.success(`model ${modelName} created successfully`);
|
|
196
|
+
console.log();
|
|
197
|
+
output.info('you can now use this model with ARK agents and queries');
|
|
198
|
+
return true;
|
|
199
|
+
}
|
|
200
|
+
catch (error) {
|
|
201
|
+
output.error('failed to create model');
|
|
202
|
+
console.error(error);
|
|
203
|
+
// Try to clean up the secret if model creation failed
|
|
204
|
+
try {
|
|
205
|
+
await execa('kubectl', ['delete', 'secret', secretName], { stdio: 'pipe' });
|
|
206
|
+
output.info(`cleaned up secret ${secretName}`);
|
|
207
|
+
}
|
|
208
|
+
catch {
|
|
209
|
+
// Ignore cleanup errors
|
|
210
|
+
}
|
|
211
|
+
return false;
|
|
212
|
+
}
|
|
213
|
+
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { Command } from 'commander';
|
|
2
|
+
import { execa } from 'execa';
|
|
3
|
+
import output from '../../lib/output.js';
|
|
4
|
+
import { createModel } from './create.js';
|
|
5
|
+
async function listModels(options) {
|
|
6
|
+
try {
|
|
7
|
+
// Use kubectl to get models
|
|
8
|
+
const result = await execa('kubectl', ['get', 'models', '-o', 'json'], {
|
|
9
|
+
stdio: 'pipe',
|
|
10
|
+
});
|
|
11
|
+
const data = JSON.parse(result.stdout);
|
|
12
|
+
const models = data.items || [];
|
|
13
|
+
if (options.output === 'json') {
|
|
14
|
+
// Output the raw items for JSON format
|
|
15
|
+
console.log(JSON.stringify(models, null, 2));
|
|
16
|
+
}
|
|
17
|
+
else {
|
|
18
|
+
if (models.length === 0) {
|
|
19
|
+
output.info('No models found');
|
|
20
|
+
return;
|
|
21
|
+
}
|
|
22
|
+
// Just output the model names
|
|
23
|
+
models.forEach((model) => {
|
|
24
|
+
console.log(model.metadata.name);
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
catch (error) {
|
|
29
|
+
if (error instanceof Error && error.message.includes('the server doesn\'t have a resource type')) {
|
|
30
|
+
output.error('Model CRDs not installed. Is the ARK controller running?');
|
|
31
|
+
}
|
|
32
|
+
else {
|
|
33
|
+
output.error(`Failed to list models: ${error instanceof Error ? error.message : 'Unknown error'}`);
|
|
34
|
+
}
|
|
35
|
+
process.exit(1);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
export function createModelsCommand() {
|
|
39
|
+
const modelsCommand = new Command('models');
|
|
40
|
+
modelsCommand
|
|
41
|
+
.description('List available models')
|
|
42
|
+
.option('-o, --output <format>', 'Output format (json)', 'text')
|
|
43
|
+
.action(async (options) => {
|
|
44
|
+
await listModels(options);
|
|
45
|
+
});
|
|
46
|
+
const listCommand = new Command('list');
|
|
47
|
+
listCommand
|
|
48
|
+
.alias('ls')
|
|
49
|
+
.description('List available models')
|
|
50
|
+
.option('-o, --output <format>', 'Output format (json)', 'text')
|
|
51
|
+
.action(async (options) => {
|
|
52
|
+
await listModels(options);
|
|
53
|
+
});
|
|
54
|
+
modelsCommand.addCommand(listCommand);
|
|
55
|
+
// Add create command
|
|
56
|
+
const createCommand = new Command('create');
|
|
57
|
+
createCommand
|
|
58
|
+
.description('Create a new model')
|
|
59
|
+
.argument('[name]', 'Model name (optional)')
|
|
60
|
+
.action(async (name) => {
|
|
61
|
+
await createModel(name);
|
|
62
|
+
});
|
|
63
|
+
modelsCommand.addCommand(createCommand);
|
|
64
|
+
return modelsCommand;
|
|
65
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { Model, ArkApiClient } from '../../lib/arkApiClient.js';
|
|
2
|
+
interface ModelSelectorProps {
|
|
3
|
+
arkApiClient: ArkApiClient;
|
|
4
|
+
onSelect: (model: Model) => void;
|
|
5
|
+
onExit: () => void;
|
|
6
|
+
}
|
|
7
|
+
export declare function ModelSelector({ arkApiClient, onSelect, onExit, }: ModelSelectorProps): import("react/jsx-runtime").JSX.Element;
|
|
8
|
+
export {};
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useState, useEffect } from 'react';
|
|
3
|
+
import { Box, Text, useInput } from 'ink';
|
|
4
|
+
export function ModelSelector({ arkApiClient, onSelect, onExit, }) {
|
|
5
|
+
const [selectedIndex, setSelectedIndex] = useState(0);
|
|
6
|
+
const [models, setModels] = useState([]);
|
|
7
|
+
const [loading, setLoading] = useState(true);
|
|
8
|
+
const [error, setError] = useState(null);
|
|
9
|
+
useEffect(() => {
|
|
10
|
+
arkApiClient
|
|
11
|
+
.getModels()
|
|
12
|
+
.then((fetchedModels) => {
|
|
13
|
+
setModels(fetchedModels);
|
|
14
|
+
setLoading(false);
|
|
15
|
+
})
|
|
16
|
+
.catch((err) => {
|
|
17
|
+
setError(err.message || 'Failed to fetch models');
|
|
18
|
+
setLoading(false);
|
|
19
|
+
});
|
|
20
|
+
}, [arkApiClient]);
|
|
21
|
+
useInput((input, key) => {
|
|
22
|
+
if (key.escape) {
|
|
23
|
+
onExit();
|
|
24
|
+
}
|
|
25
|
+
else if (key.upArrow || input === 'k') {
|
|
26
|
+
setSelectedIndex((prev) => (prev === 0 ? models.length - 1 : prev - 1));
|
|
27
|
+
}
|
|
28
|
+
else if (key.downArrow || input === 'j') {
|
|
29
|
+
setSelectedIndex((prev) => (prev === models.length - 1 ? 0 : prev + 1));
|
|
30
|
+
}
|
|
31
|
+
else if (key.return) {
|
|
32
|
+
onSelect(models[selectedIndex]);
|
|
33
|
+
}
|
|
34
|
+
else {
|
|
35
|
+
// Handle number keys for quick selection
|
|
36
|
+
const num = parseInt(input, 10);
|
|
37
|
+
if (!isNaN(num) && num >= 1 && num <= models.length) {
|
|
38
|
+
onSelect(models[num - 1]);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
});
|
|
42
|
+
if (loading) {
|
|
43
|
+
return (_jsx(Box, { children: _jsx(Text, { children: "Loading models..." }) }));
|
|
44
|
+
}
|
|
45
|
+
if (error) {
|
|
46
|
+
return (_jsx(Box, { children: _jsxs(Text, { color: "red", children: ["Error: ", error] }) }));
|
|
47
|
+
}
|
|
48
|
+
if (models.length === 0) {
|
|
49
|
+
return (_jsx(Box, { children: _jsx(Text, { children: "No models available" }) }));
|
|
50
|
+
}
|
|
51
|
+
const selectedModel = models[selectedIndex];
|
|
52
|
+
return (_jsxs(Box, { flexDirection: "column", borderStyle: "round", borderColor: "gray", paddingX: 2, paddingY: 1, children: [_jsx(Box, { marginBottom: 1, children: _jsx(Text, { bold: true, children: "Select Model" }) }), _jsx(Box, { marginBottom: 1, children: _jsx(Text, { dimColor: true, children: "Choose a model to start a conversation with" }) }), _jsx(Box, { flexDirection: "column", children: models.map((model, index) => (_jsx(Box, { marginBottom: 0, children: _jsxs(Text, { color: index === selectedIndex ? 'green' : undefined, children: [index === selectedIndex ? '❯ ' : ' ', index + 1, ". ", model.name, model.type ? ` (${model.type})` : ''] }) }, model.name))) }), selectedModel && selectedModel.model && (_jsx(Box, { marginTop: 1, paddingLeft: 2, children: _jsxs(Text, { dimColor: true, wrap: "wrap", children: ["Model: ", selectedModel.model] }) })), _jsx(Box, { marginTop: 1, children: _jsx(Text, { dimColor: true, children: "Enter to confirm \u00B7 Number to select \u00B7 Esc to exit" }) })] }));
|
|
53
|
+
}
|