@agents-at-scale/ark 0.1.36 → 0.1.38
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +53 -70
- package/dist/arkServices.d.ts +3 -27
- package/dist/arkServices.js +31 -3
- package/dist/arkServices.spec.js +118 -10
- package/dist/commands/chat/index.js +1 -2
- package/dist/commands/completion/index.js +0 -2
- package/dist/commands/generate/generators/project.js +33 -26
- package/dist/commands/generate/index.js +2 -2
- package/dist/commands/generate/templateDiscovery.js +13 -4
- package/dist/commands/install/index.js +49 -58
- package/dist/commands/models/create.d.ts +9 -1
- package/dist/commands/models/create.js +97 -90
- package/dist/commands/models/create.spec.js +9 -37
- package/dist/commands/models/index.js +8 -2
- package/dist/commands/models/index.spec.js +1 -1
- package/dist/commands/status/index.d.ts +3 -1
- package/dist/commands/status/index.js +54 -2
- package/dist/components/AsyncOperation.d.ts +54 -0
- package/dist/components/AsyncOperation.js +110 -0
- package/dist/components/ChatUI.js +39 -72
- package/dist/components/SelectMenu.d.ts +17 -0
- package/dist/components/SelectMenu.js +21 -0
- package/dist/components/StatusMessage.d.ts +20 -0
- package/dist/components/StatusMessage.js +13 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/lib/arkApiClient.d.ts +1 -2
- package/dist/lib/arkApiClient.js +5 -6
- package/dist/lib/config.d.ts +4 -0
- package/dist/lib/config.js +9 -0
- package/dist/lib/nextSteps.js +1 -1
- package/dist/lib/nextSteps.spec.js +1 -1
- package/dist/lib/security.js +4 -0
- package/dist/lib/startup.js +6 -2
- package/dist/lib/startup.spec.js +1 -1
- package/dist/lib/timeout.d.ts +1 -0
- package/dist/lib/timeout.js +20 -0
- package/dist/lib/timeout.spec.d.ts +1 -0
- package/dist/lib/timeout.spec.js +14 -0
- package/dist/lib/waitForReady.d.ts +8 -0
- package/dist/lib/waitForReady.js +32 -0
- package/dist/lib/waitForReady.spec.d.ts +1 -0
- package/dist/lib/waitForReady.spec.js +104 -0
- package/dist/types/arkService.d.ts +27 -0
- package/dist/types/arkService.js +1 -0
- package/dist/ui/asyncOperations/connectingToArk.d.ts +15 -0
- package/dist/ui/asyncOperations/connectingToArk.js +63 -0
- package/package.json +7 -5
- package/templates/agent/agent.template.yaml +27 -0
- package/templates/marketplace/.editorconfig +24 -0
- package/templates/marketplace/.github/.keep +11 -0
- package/templates/marketplace/.github/workflows/.keep +16 -0
- package/templates/marketplace/.helmignore +23 -0
- package/templates/marketplace/.prettierrc.json +20 -0
- package/templates/marketplace/.yamllint.yml +53 -0
- package/templates/marketplace/README.md +197 -0
- package/templates/marketplace/agents/.keep +29 -0
- package/templates/marketplace/docs/.keep +19 -0
- package/templates/marketplace/mcp-servers/.keep +32 -0
- package/templates/marketplace/models/.keep +23 -0
- package/templates/marketplace/projects/.keep +43 -0
- package/templates/marketplace/queries/.keep +25 -0
- package/templates/marketplace/teams/.keep +29 -0
- package/templates/marketplace/tools/.keep +32 -0
- package/templates/marketplace/tools/examples/.keep +17 -0
- package/templates/mcp-server/Dockerfile +133 -0
- package/templates/mcp-server/Makefile +186 -0
- package/templates/mcp-server/README.md +178 -0
- package/templates/mcp-server/build.sh +76 -0
- package/templates/mcp-server/chart/Chart.yaml +22 -0
- package/templates/mcp-server/chart/templates/_helpers.tpl +62 -0
- package/templates/mcp-server/chart/templates/deployment.yaml +80 -0
- package/templates/mcp-server/chart/templates/hpa.yaml +32 -0
- package/templates/mcp-server/chart/templates/mcpserver.yaml +21 -0
- package/templates/mcp-server/chart/templates/secret.yaml +11 -0
- package/templates/mcp-server/chart/templates/service.yaml +15 -0
- package/templates/mcp-server/chart/templates/serviceaccount.yaml +13 -0
- package/templates/mcp-server/chart/values.yaml +84 -0
- package/templates/mcp-server/example-values.yaml +74 -0
- package/templates/mcp-server/examples/{{ .Values.mcpServerName }}-agent.yaml +33 -0
- package/templates/mcp-server/examples/{{ .Values.mcpServerName }}-query.yaml +24 -0
- package/templates/models/azure.yaml +33 -0
- package/templates/models/claude.yaml +28 -0
- package/templates/models/gemini.yaml +28 -0
- package/templates/models/openai.yaml +39 -0
- package/templates/project/.editorconfig +24 -0
- package/templates/project/.helmignore +24 -0
- package/templates/project/.prettierrc.json +16 -0
- package/templates/project/.yamllint.yml +50 -0
- package/templates/project/Chart.yaml +19 -0
- package/templates/project/Makefile +360 -0
- package/templates/project/README.md +377 -0
- package/templates/project/agents/.keep +11 -0
- package/templates/project/docs/.keep +14 -0
- package/templates/project/mcp-servers/.keep +34 -0
- package/templates/project/models/.keep +17 -0
- package/templates/project/queries/.keep +11 -0
- package/templates/project/scripts/setup.sh +108 -0
- package/templates/project/teams/.keep +11 -0
- package/templates/project/templates/00-rbac.yaml +168 -0
- package/templates/project/templates/01-models.yaml +11 -0
- package/templates/project/templates/02-mcp-servers.yaml +22 -0
- package/templates/project/templates/03-tools.yaml +12 -0
- package/templates/project/templates/04-agents.yaml +12 -0
- package/templates/project/templates/05-teams.yaml +11 -0
- package/templates/project/templates/06-queries.yaml +11 -0
- package/templates/project/templates/_helpers.tpl +91 -0
- package/templates/project/tests/e2e/.keep +10 -0
- package/templates/project/tests/unit/.keep +10 -0
- package/templates/project/tools/.keep +25 -0
- package/templates/project/tools/example-tool.yaml.disabled +94 -0
- package/templates/project/tools/examples/data-tool/Dockerfile +32 -0
- package/templates/project/values.yaml +141 -0
- package/templates/query/query.template.yaml +13 -0
- package/templates/team/team.template.yaml +17 -0
- package/templates/tool/.python-version +1 -0
- package/templates/tool/Dockerfile +23 -0
- package/templates/tool/README.md +238 -0
- package/templates/tool/agent.yaml +19 -0
- package/templates/tool/deploy.sh +10 -0
- package/templates/tool/deployment/deployment.yaml +31 -0
- package/templates/tool/deployment/kustomization.yaml +7 -0
- package/templates/tool/deployment/mcpserver.yaml +12 -0
- package/templates/tool/deployment/service.yaml +12 -0
- package/templates/tool/deployment/serviceaccount.yaml +8 -0
- package/templates/tool/deployment/values.yaml +3 -0
- package/templates/tool/pyproject.toml +9 -0
- package/templates/tool/src/main.py +36 -0
- package/templates/tool/uv.lock +498 -0
|
@@ -4,10 +4,11 @@ import { execute } from '../../lib/commands.js';
|
|
|
4
4
|
import inquirer from 'inquirer';
|
|
5
5
|
import { showNoClusterError } from '../../lib/startup.js';
|
|
6
6
|
import output from '../../lib/output.js';
|
|
7
|
-
import { getInstallableServices, arkDependencies, } from '../../arkServices.js';
|
|
8
|
-
import { isArkReady } from '../../lib/arkStatus.js';
|
|
7
|
+
import { getInstallableServices, arkDependencies, arkServices, } from '../../arkServices.js';
|
|
9
8
|
import { printNextSteps } from '../../lib/nextSteps.js';
|
|
10
9
|
import ora from 'ora';
|
|
10
|
+
import { waitForServicesReady, } from '../../lib/waitForReady.js';
|
|
11
|
+
import { parseTimeoutToSeconds } from '../../lib/timeout.js';
|
|
11
12
|
async function installService(service, verbose = false) {
|
|
12
13
|
const helmArgs = [
|
|
13
14
|
'upgrade',
|
|
@@ -67,6 +68,12 @@ export async function installArk(config, serviceName, options = {}) {
|
|
|
67
68
|
console.log(chalk.cyan.bold('\nSelect components to install:'));
|
|
68
69
|
console.log(chalk.gray('Use arrow keys to navigate, space to toggle, enter to confirm\n'));
|
|
69
70
|
// Build choices for the checkbox prompt
|
|
71
|
+
const coreServices = Object.values(arkServices)
|
|
72
|
+
.filter((s) => s.category === 'core')
|
|
73
|
+
.sort((a, b) => a.name.localeCompare(b.name));
|
|
74
|
+
const otherServices = Object.values(arkServices)
|
|
75
|
+
.filter((s) => s.category === 'service')
|
|
76
|
+
.sort((a, b) => a.name.localeCompare(b.name));
|
|
70
77
|
const allChoices = [
|
|
71
78
|
new inquirer.Separator(chalk.bold('──── Dependencies ────')),
|
|
72
79
|
{
|
|
@@ -80,32 +87,17 @@ export async function installArk(config, serviceName, options = {}) {
|
|
|
80
87
|
checked: true,
|
|
81
88
|
},
|
|
82
89
|
new inquirer.Separator(chalk.bold('──── Ark Core ────')),
|
|
83
|
-
{
|
|
84
|
-
name:
|
|
85
|
-
value:
|
|
86
|
-
checked:
|
|
87
|
-
},
|
|
90
|
+
...coreServices.map((service) => ({
|
|
91
|
+
name: `${service.name} ${chalk.gray(`- ${service.description}`)}`,
|
|
92
|
+
value: service.helmReleaseName,
|
|
93
|
+
checked: Boolean(service.enabled),
|
|
94
|
+
})),
|
|
88
95
|
new inquirer.Separator(chalk.bold('──── Ark Services ────')),
|
|
89
|
-
{
|
|
90
|
-
name:
|
|
91
|
-
value:
|
|
92
|
-
checked:
|
|
93
|
-
},
|
|
94
|
-
{
|
|
95
|
-
name: `ark-dashboard ${chalk.gray('- Web dashboard')}`,
|
|
96
|
-
value: 'ark-dashboard',
|
|
97
|
-
checked: true,
|
|
98
|
-
},
|
|
99
|
-
{
|
|
100
|
-
name: `ark-mcp ${chalk.gray('- MCP services')}`,
|
|
101
|
-
value: 'ark-mcp',
|
|
102
|
-
checked: true,
|
|
103
|
-
},
|
|
104
|
-
{
|
|
105
|
-
name: `localhost-gateway ${chalk.gray('- Gateway for local access')}`,
|
|
106
|
-
value: 'localhost-gateway',
|
|
107
|
-
checked: true,
|
|
108
|
-
},
|
|
96
|
+
...otherServices.map((service) => ({
|
|
97
|
+
name: `${service.name} ${chalk.gray(`- ${service.description}`)}`,
|
|
98
|
+
value: service.helmReleaseName,
|
|
99
|
+
checked: Boolean(service.enabled),
|
|
100
|
+
})),
|
|
109
101
|
];
|
|
110
102
|
let selectedComponents = [];
|
|
111
103
|
try {
|
|
@@ -190,11 +182,9 @@ export async function installArk(config, serviceName, options = {}) {
|
|
|
190
182
|
}
|
|
191
183
|
}
|
|
192
184
|
// Install selected services
|
|
193
|
-
const
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
const serviceKey = service.helmReleaseName;
|
|
197
|
-
if (!selectedComponents.includes(serviceKey)) {
|
|
185
|
+
for (const serviceName of selectedComponents) {
|
|
186
|
+
const service = Object.values(arkServices).find((s) => s.helmReleaseName === serviceName);
|
|
187
|
+
if (!service || !service.chartPath) {
|
|
198
188
|
continue;
|
|
199
189
|
}
|
|
200
190
|
output.info(`installing ${service.name}...`);
|
|
@@ -203,8 +193,8 @@ export async function installArk(config, serviceName, options = {}) {
|
|
|
203
193
|
console.log(); // Add blank line after command output
|
|
204
194
|
}
|
|
205
195
|
catch {
|
|
206
|
-
// Continue with remaining services on error
|
|
207
196
|
console.log(); // Add blank line after error output
|
|
197
|
+
process.exit(1);
|
|
208
198
|
}
|
|
209
199
|
}
|
|
210
200
|
}
|
|
@@ -234,8 +224,8 @@ export async function installArk(config, serviceName, options = {}) {
|
|
|
234
224
|
console.log(); // Add blank line after command output
|
|
235
225
|
}
|
|
236
226
|
catch {
|
|
237
|
-
// Continue with remaining services on error
|
|
238
227
|
console.log(); // Add blank line after error output
|
|
228
|
+
process.exit(1);
|
|
239
229
|
}
|
|
240
230
|
}
|
|
241
231
|
}
|
|
@@ -245,34 +235,35 @@ export async function installArk(config, serviceName, options = {}) {
|
|
|
245
235
|
}
|
|
246
236
|
// Wait for Ark to be ready if requested
|
|
247
237
|
if (options.waitForReady) {
|
|
248
|
-
// Parse timeout value (e.g., '30s', '2m', '60')
|
|
249
|
-
const parseTimeout = (value) => {
|
|
250
|
-
const match = value.match(/^(\d+)([sm])?$/);
|
|
251
|
-
if (!match) {
|
|
252
|
-
throw new Error('Invalid timeout format. Use format like 30s or 2m');
|
|
253
|
-
}
|
|
254
|
-
const num = parseInt(match[1], 10);
|
|
255
|
-
const unit = match[2] || 's';
|
|
256
|
-
return unit === 'm' ? num * 60 : num;
|
|
257
|
-
};
|
|
258
238
|
try {
|
|
259
|
-
const timeoutSeconds =
|
|
260
|
-
const
|
|
261
|
-
|
|
239
|
+
const timeoutSeconds = parseTimeoutToSeconds(options.waitForReady);
|
|
240
|
+
const servicesToWait = Object.values(arkServices).filter((s) => s.enabled &&
|
|
241
|
+
s.category === 'core' &&
|
|
242
|
+
s.k8sDeploymentName &&
|
|
243
|
+
s.namespace);
|
|
262
244
|
const spinner = ora(`Waiting for Ark to be ready (timeout: ${timeoutSeconds}s)...`).start();
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
245
|
+
const statusMap = new Map();
|
|
246
|
+
servicesToWait.forEach((s) => statusMap.set(s.name, false));
|
|
247
|
+
const startTime = Date.now();
|
|
248
|
+
const result = await waitForServicesReady(servicesToWait, timeoutSeconds, (progress) => {
|
|
249
|
+
statusMap.set(progress.serviceName, progress.ready);
|
|
268
250
|
const elapsed = Math.floor((Date.now() - startTime) / 1000);
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
251
|
+
const lines = servicesToWait.map((s) => {
|
|
252
|
+
const ready = statusMap.get(s.name);
|
|
253
|
+
const icon = ready ? '✓' : '⋯';
|
|
254
|
+
const status = ready ? 'ready' : 'waiting...';
|
|
255
|
+
const color = ready ? chalk.green : chalk.yellow;
|
|
256
|
+
return ` ${color(icon)} ${chalk.bold(s.name)} ${chalk.blue(`(${s.namespace})`)} - ${status}`;
|
|
257
|
+
});
|
|
258
|
+
spinner.text = `Waiting for Ark to be ready (${elapsed}/${timeoutSeconds}s)...\n${lines.join('\n')}`;
|
|
259
|
+
});
|
|
260
|
+
if (result) {
|
|
261
|
+
spinner.succeed('Ark is ready');
|
|
262
|
+
}
|
|
263
|
+
else {
|
|
264
|
+
spinner.fail(`Ark did not become ready within ${timeoutSeconds} seconds`);
|
|
265
|
+
process.exit(1);
|
|
272
266
|
}
|
|
273
|
-
// Timeout reached
|
|
274
|
-
spinner.fail(`Ark did not become ready within ${timeoutSeconds} seconds`);
|
|
275
|
-
process.exit(1);
|
|
276
267
|
}
|
|
277
268
|
catch (error) {
|
|
278
269
|
output.error(`Failed to wait for ready: ${error instanceof Error ? error.message : 'Unknown error'}`);
|
|
@@ -1 +1,9 @@
|
|
|
1
|
-
export
|
|
1
|
+
export interface CreateModelOptions {
|
|
2
|
+
type?: string;
|
|
3
|
+
model?: string;
|
|
4
|
+
baseUrl?: string;
|
|
5
|
+
apiKey?: string;
|
|
6
|
+
apiVersion?: string;
|
|
7
|
+
yes?: boolean;
|
|
8
|
+
}
|
|
9
|
+
export declare function createModel(modelName?: string, options?: CreateModelOptions): Promise<boolean>;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { execa } from 'execa';
|
|
2
2
|
import inquirer from 'inquirer';
|
|
3
3
|
import output from '../../lib/output.js';
|
|
4
|
-
export async function createModel(modelName) {
|
|
4
|
+
export async function createModel(modelName, options = {}) {
|
|
5
5
|
// Step 1: Get model name if not provided
|
|
6
6
|
if (!modelName) {
|
|
7
7
|
const nameAnswer = await inquirer.prompt([
|
|
@@ -27,66 +27,87 @@ export async function createModel(modelName) {
|
|
|
27
27
|
try {
|
|
28
28
|
await execa('kubectl', ['get', 'model', modelName], { stdio: 'pipe' });
|
|
29
29
|
output.warning(`model ${modelName} already exists`);
|
|
30
|
-
|
|
31
|
-
{
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
30
|
+
if (!options.yes) {
|
|
31
|
+
const { overwrite } = await inquirer.prompt([
|
|
32
|
+
{
|
|
33
|
+
type: 'confirm',
|
|
34
|
+
name: 'overwrite',
|
|
35
|
+
message: `overwrite existing model ${modelName}?`,
|
|
36
|
+
default: false,
|
|
37
|
+
},
|
|
38
|
+
]);
|
|
39
|
+
if (!overwrite) {
|
|
40
|
+
output.info('model creation cancelled');
|
|
41
|
+
return false;
|
|
42
|
+
}
|
|
41
43
|
}
|
|
42
44
|
}
|
|
43
45
|
catch {
|
|
44
46
|
// Model doesn't exist, continue
|
|
45
47
|
}
|
|
46
|
-
// Step 2:
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
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
|
-
}
|
|
48
|
+
// Step 2: Get model type
|
|
49
|
+
let modelType = options.type;
|
|
50
|
+
if (!modelType) {
|
|
51
|
+
const answer = await inquirer.prompt([
|
|
52
|
+
{
|
|
53
|
+
type: 'list',
|
|
54
|
+
name: 'modelType',
|
|
55
|
+
message: 'select model provider:',
|
|
56
|
+
choices: [
|
|
57
|
+
{ name: 'Azure OpenAI', value: 'azure' },
|
|
58
|
+
{ name: 'OpenAI', value: 'openai' },
|
|
59
|
+
],
|
|
60
|
+
default: 'azure',
|
|
81
61
|
},
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
62
|
+
]);
|
|
63
|
+
modelType = answer.modelType;
|
|
64
|
+
}
|
|
65
|
+
// Step 3: Get model name
|
|
66
|
+
let model = options.model;
|
|
67
|
+
if (!model) {
|
|
68
|
+
const answer = await inquirer.prompt([
|
|
69
|
+
{
|
|
70
|
+
type: 'input',
|
|
71
|
+
name: 'model',
|
|
72
|
+
message: 'model:',
|
|
73
|
+
default: 'gpt-4o-mini',
|
|
74
|
+
},
|
|
75
|
+
]);
|
|
76
|
+
model = answer.model;
|
|
77
|
+
}
|
|
78
|
+
// Step 4: Get base URL
|
|
79
|
+
let baseUrl = options.baseUrl;
|
|
80
|
+
if (!baseUrl) {
|
|
81
|
+
const answer = await inquirer.prompt([
|
|
82
|
+
{
|
|
83
|
+
type: 'input',
|
|
84
|
+
name: 'baseUrl',
|
|
85
|
+
message: 'base URL:',
|
|
86
|
+
validate: (input) => {
|
|
87
|
+
if (!input)
|
|
88
|
+
return 'base URL is required';
|
|
89
|
+
try {
|
|
90
|
+
new URL(input);
|
|
91
|
+
return true;
|
|
92
|
+
}
|
|
93
|
+
catch {
|
|
94
|
+
return 'please enter a valid URL';
|
|
95
|
+
}
|
|
96
|
+
},
|
|
97
|
+
},
|
|
98
|
+
]);
|
|
99
|
+
baseUrl = answer.baseUrl;
|
|
100
|
+
}
|
|
101
|
+
// Validate and clean base URL
|
|
102
|
+
if (!baseUrl) {
|
|
103
|
+
output.error('base URL is required');
|
|
104
|
+
return false;
|
|
105
|
+
}
|
|
106
|
+
baseUrl = baseUrl.replace(/\/$/, '');
|
|
107
|
+
// Step 5: Get API version (Azure only)
|
|
108
|
+
let apiVersion = options.apiVersion || '';
|
|
109
|
+
if (modelType === 'azure' && !options.apiVersion) {
|
|
110
|
+
const answer = await inquirer.prompt([
|
|
90
111
|
{
|
|
91
112
|
type: 'input',
|
|
92
113
|
name: 'apiVersion',
|
|
@@ -94,33 +115,29 @@ export async function createModel(modelName) {
|
|
|
94
115
|
default: '2024-12-01-preview',
|
|
95
116
|
},
|
|
96
117
|
]);
|
|
97
|
-
apiVersion =
|
|
118
|
+
apiVersion = answer.apiVersion;
|
|
98
119
|
}
|
|
99
|
-
// Step
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
120
|
+
// Step 6: Get API key
|
|
121
|
+
let apiKey = options.apiKey;
|
|
122
|
+
if (!apiKey) {
|
|
123
|
+
const answer = await inquirer.prompt([
|
|
124
|
+
{
|
|
125
|
+
type: 'password',
|
|
126
|
+
name: 'apiKey',
|
|
127
|
+
message: 'API key:',
|
|
128
|
+
mask: '*',
|
|
129
|
+
validate: (input) => {
|
|
130
|
+
if (!input)
|
|
131
|
+
return 'API key is required';
|
|
132
|
+
return true;
|
|
133
|
+
},
|
|
110
134
|
},
|
|
111
|
-
|
|
112
|
-
|
|
135
|
+
]);
|
|
136
|
+
apiKey = answer.apiKey;
|
|
137
|
+
}
|
|
113
138
|
// Step 6: Create the Kubernetes secret
|
|
114
139
|
const secretName = `${modelName}-model-api-key`;
|
|
115
|
-
output.info(`creating secret ${secretName}...`);
|
|
116
140
|
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
141
|
await execa('kubectl', [
|
|
125
142
|
'create',
|
|
126
143
|
'secret',
|
|
@@ -128,7 +145,7 @@ export async function createModel(modelName) {
|
|
|
128
145
|
secretName,
|
|
129
146
|
`--from-literal=api-key=${apiKey}`,
|
|
130
147
|
], { stdio: 'pipe' });
|
|
131
|
-
output.success(`secret ${secretName}
|
|
148
|
+
output.success(`created secret ${secretName}`);
|
|
132
149
|
}
|
|
133
150
|
catch (error) {
|
|
134
151
|
output.error('failed to create secret');
|
|
@@ -146,7 +163,7 @@ export async function createModel(modelName) {
|
|
|
146
163
|
spec: {
|
|
147
164
|
type: modelType,
|
|
148
165
|
model: {
|
|
149
|
-
value:
|
|
166
|
+
value: model,
|
|
150
167
|
},
|
|
151
168
|
config: {},
|
|
152
169
|
},
|
|
@@ -192,22 +209,12 @@ export async function createModel(modelName) {
|
|
|
192
209
|
input: manifestJson,
|
|
193
210
|
stdio: ['pipe', 'pipe', 'pipe'],
|
|
194
211
|
});
|
|
195
|
-
output.success(`model ${modelName} created
|
|
196
|
-
console.log();
|
|
197
|
-
output.info('you can now use this model with ARK agents and queries');
|
|
212
|
+
output.success(`model ${modelName} created`);
|
|
198
213
|
return true;
|
|
199
214
|
}
|
|
200
215
|
catch (error) {
|
|
201
216
|
output.error('failed to create model');
|
|
202
217
|
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
218
|
return false;
|
|
212
219
|
}
|
|
213
220
|
}
|
|
@@ -31,28 +31,23 @@ describe('createModel', () => {
|
|
|
31
31
|
// Prompts for model details
|
|
32
32
|
mockInquirer.prompt
|
|
33
33
|
.mockResolvedValueOnce({ modelType: 'openai' })
|
|
34
|
-
.mockResolvedValueOnce({
|
|
35
|
-
|
|
36
|
-
baseUrl: 'https://api.openai.com/',
|
|
37
|
-
})
|
|
34
|
+
.mockResolvedValueOnce({ model: 'gpt-4' })
|
|
35
|
+
.mockResolvedValueOnce({ baseUrl: 'https://api.openai.com/' })
|
|
38
36
|
.mockResolvedValueOnce({ apiKey: 'secret-key' });
|
|
39
37
|
// Secret operations succeed
|
|
40
|
-
mockExeca.mockResolvedValueOnce({}); // delete secret (may not exist)
|
|
41
38
|
mockExeca.mockResolvedValueOnce({}); // create secret
|
|
42
39
|
mockExeca.mockResolvedValueOnce({}); // apply model
|
|
43
40
|
const result = await createModel('test-model');
|
|
44
41
|
expect(result).toBe(true);
|
|
45
42
|
expect(mockExeca).toHaveBeenCalledWith('kubectl', ['get', 'model', 'test-model'], { stdio: 'pipe' });
|
|
46
|
-
expect(mockOutput.success).toHaveBeenCalledWith('model test-model created
|
|
43
|
+
expect(mockOutput.success).toHaveBeenCalledWith('model test-model created');
|
|
47
44
|
});
|
|
48
45
|
it('prompts for name when not provided', async () => {
|
|
49
46
|
mockInquirer.prompt
|
|
50
47
|
.mockResolvedValueOnce({ modelName: 'prompted-model' })
|
|
51
48
|
.mockResolvedValueOnce({ modelType: 'azure' })
|
|
52
|
-
.mockResolvedValueOnce({
|
|
53
|
-
|
|
54
|
-
baseUrl: 'https://azure.com',
|
|
55
|
-
})
|
|
49
|
+
.mockResolvedValueOnce({ model: 'gpt-4' })
|
|
50
|
+
.mockResolvedValueOnce({ baseUrl: 'https://azure.com' })
|
|
56
51
|
.mockResolvedValueOnce({ apiVersion: '2024-12-01' })
|
|
57
52
|
.mockResolvedValueOnce({ apiKey: 'secret' });
|
|
58
53
|
mockExeca.mockRejectedValueOnce(new Error('not found')); // model doesn't exist
|
|
@@ -72,10 +67,8 @@ describe('createModel', () => {
|
|
|
72
67
|
mockInquirer.prompt
|
|
73
68
|
.mockResolvedValueOnce({ overwrite: true })
|
|
74
69
|
.mockResolvedValueOnce({ modelType: 'openai' })
|
|
75
|
-
.mockResolvedValueOnce({
|
|
76
|
-
|
|
77
|
-
baseUrl: 'https://api.openai.com',
|
|
78
|
-
})
|
|
70
|
+
.mockResolvedValueOnce({ model: 'gpt-4' })
|
|
71
|
+
.mockResolvedValueOnce({ baseUrl: 'https://api.openai.com' })
|
|
79
72
|
.mockResolvedValueOnce({ apiKey: 'secret' });
|
|
80
73
|
mockExeca.mockResolvedValue({}); // remaining kubectl ops
|
|
81
74
|
const result = await createModel('existing-model');
|
|
@@ -93,33 +86,12 @@ describe('createModel', () => {
|
|
|
93
86
|
mockExeca.mockRejectedValueOnce(new Error('not found')); // model doesn't exist
|
|
94
87
|
mockInquirer.prompt
|
|
95
88
|
.mockResolvedValueOnce({ modelType: 'openai' })
|
|
96
|
-
.mockResolvedValueOnce({
|
|
97
|
-
|
|
98
|
-
baseUrl: 'https://api.openai.com',
|
|
99
|
-
})
|
|
89
|
+
.mockResolvedValueOnce({ model: 'gpt-4' })
|
|
90
|
+
.mockResolvedValueOnce({ baseUrl: 'https://api.openai.com' })
|
|
100
91
|
.mockResolvedValueOnce({ apiKey: 'secret' });
|
|
101
|
-
mockExeca.mockRejectedValueOnce(new Error('delete failed')); // delete secret may fail
|
|
102
92
|
mockExeca.mockRejectedValueOnce(new Error('secret creation failed')); // create secret fails
|
|
103
93
|
const result = await createModel('test-model');
|
|
104
94
|
expect(result).toBe(false);
|
|
105
95
|
expect(mockOutput.error).toHaveBeenCalledWith('failed to create secret');
|
|
106
96
|
});
|
|
107
|
-
it('cleans up secret if model creation fails', async () => {
|
|
108
|
-
mockExeca.mockRejectedValueOnce(new Error('not found')); // model doesn't exist
|
|
109
|
-
mockInquirer.prompt
|
|
110
|
-
.mockResolvedValueOnce({ modelType: 'openai' })
|
|
111
|
-
.mockResolvedValueOnce({
|
|
112
|
-
modelVersion: 'gpt-4',
|
|
113
|
-
baseUrl: 'https://api.openai.com',
|
|
114
|
-
})
|
|
115
|
-
.mockResolvedValueOnce({ apiKey: 'secret' });
|
|
116
|
-
mockExeca.mockResolvedValueOnce({}); // delete secret
|
|
117
|
-
mockExeca.mockResolvedValueOnce({}); // create secret
|
|
118
|
-
mockExeca.mockRejectedValueOnce(new Error('apply failed')); // apply model fails
|
|
119
|
-
mockExeca.mockResolvedValueOnce({}); // cleanup secret
|
|
120
|
-
const result = await createModel('test-model');
|
|
121
|
-
expect(result).toBe(false);
|
|
122
|
-
expect(mockOutput.error).toHaveBeenCalledWith('failed to create model');
|
|
123
|
-
expect(mockExeca).toHaveBeenCalledWith('kubectl', ['delete', 'secret', 'test-model-model-api-key'], { stdio: 'pipe' });
|
|
124
|
-
});
|
|
125
97
|
});
|
|
@@ -53,8 +53,14 @@ export function createModelsCommand(_) {
|
|
|
53
53
|
createCommand
|
|
54
54
|
.description('Create a new model')
|
|
55
55
|
.argument('[name]', 'Model name (optional)')
|
|
56
|
-
.
|
|
57
|
-
|
|
56
|
+
.option('--type <type>', 'Model provider type (azure, openai, bedrock)')
|
|
57
|
+
.option('--model <model>', 'Model name (e.g., gpt-4o-mini)')
|
|
58
|
+
.option('--base-url <url>', 'Base URL for the model API')
|
|
59
|
+
.option('--api-key <key>', 'API key for authentication')
|
|
60
|
+
.option('--api-version <version>', 'API version (Azure only)')
|
|
61
|
+
.option('--yes', 'Skip confirmation prompts')
|
|
62
|
+
.action(async (name, options) => {
|
|
63
|
+
await createModel(name, options);
|
|
58
64
|
});
|
|
59
65
|
modelsCommand.addCommand(createCommand);
|
|
60
66
|
// Add query command
|
|
@@ -76,7 +76,7 @@ describe('models command', () => {
|
|
|
76
76
|
it('create subcommand works', async () => {
|
|
77
77
|
const command = createModelsCommand({});
|
|
78
78
|
await command.parseAsync(['node', 'test', 'create', 'my-model']);
|
|
79
|
-
expect(mockCreateModel).toHaveBeenCalledWith('my-model');
|
|
79
|
+
expect(mockCreateModel).toHaveBeenCalledWith('my-model', expect.objectContaining({}));
|
|
80
80
|
});
|
|
81
81
|
it('query subcommand works', async () => {
|
|
82
82
|
const command = createModelsCommand({});
|
|
@@ -1,3 +1,5 @@
|
|
|
1
1
|
import { Command } from 'commander';
|
|
2
|
-
export declare function checkStatus(
|
|
2
|
+
export declare function checkStatus(serviceNames?: string[], options?: {
|
|
3
|
+
waitForReady?: string;
|
|
4
|
+
}): Promise<void>;
|
|
3
5
|
export declare function createStatusCommand(): Command;
|
|
@@ -4,6 +4,10 @@ import ora from 'ora';
|
|
|
4
4
|
import { StatusChecker } from '../../components/statusChecker.js';
|
|
5
5
|
import { StatusFormatter, } from '../../ui/statusFormatter.js';
|
|
6
6
|
import { fetchVersionInfo } from '../../lib/versions.js';
|
|
7
|
+
import { waitForServicesReady, } from '../../lib/waitForReady.js';
|
|
8
|
+
import { arkServices } from '../../arkServices.js';
|
|
9
|
+
import output from '../../lib/output.js';
|
|
10
|
+
import { parseTimeoutToSeconds } from '../../lib/timeout.js';
|
|
7
11
|
/**
|
|
8
12
|
* Enrich service with formatted details including version/revision
|
|
9
13
|
*/
|
|
@@ -249,7 +253,7 @@ function buildStatusSections(data, versionInfo) {
|
|
|
249
253
|
sections.push({ title: 'ark status:', lines: arkStatusLines });
|
|
250
254
|
return sections;
|
|
251
255
|
}
|
|
252
|
-
export async function checkStatus() {
|
|
256
|
+
export async function checkStatus(serviceNames, options) {
|
|
253
257
|
const spinner = ora('Checking system status').start();
|
|
254
258
|
try {
|
|
255
259
|
spinner.text = 'Checking system dependencies';
|
|
@@ -264,6 +268,52 @@ export async function checkStatus() {
|
|
|
264
268
|
spinner.stop();
|
|
265
269
|
const sections = buildStatusSections(statusData, versionInfo);
|
|
266
270
|
StatusFormatter.printSections(sections);
|
|
271
|
+
if (options?.waitForReady) {
|
|
272
|
+
const timeoutSeconds = parseTimeoutToSeconds(options.waitForReady);
|
|
273
|
+
let servicesToWait = [];
|
|
274
|
+
if (serviceNames && serviceNames.length > 0) {
|
|
275
|
+
servicesToWait = serviceNames
|
|
276
|
+
.map((name) => Object.values(arkServices).find((s) => s.name === name))
|
|
277
|
+
.filter((s) => s !== undefined &&
|
|
278
|
+
s.k8sDeploymentName !== undefined &&
|
|
279
|
+
s.namespace !== undefined);
|
|
280
|
+
if (servicesToWait.length === 0) {
|
|
281
|
+
output.error(`No valid services found matching: ${serviceNames.join(', ')}`);
|
|
282
|
+
process.exit(1);
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
else {
|
|
286
|
+
servicesToWait = Object.values(arkServices).filter((s) => s.enabled &&
|
|
287
|
+
s.category === 'core' &&
|
|
288
|
+
s.k8sDeploymentName &&
|
|
289
|
+
s.namespace);
|
|
290
|
+
}
|
|
291
|
+
console.log();
|
|
292
|
+
const waitSpinner = ora(`Waiting for services to be ready (timeout: ${timeoutSeconds}s)...`).start();
|
|
293
|
+
const statusMap = new Map();
|
|
294
|
+
servicesToWait.forEach((s) => statusMap.set(s.name, false));
|
|
295
|
+
const startTime = Date.now();
|
|
296
|
+
const result = await waitForServicesReady(servicesToWait, timeoutSeconds, (progress) => {
|
|
297
|
+
statusMap.set(progress.serviceName, progress.ready);
|
|
298
|
+
const elapsed = Math.floor((Date.now() - startTime) / 1000);
|
|
299
|
+
const lines = servicesToWait.map((s) => {
|
|
300
|
+
const ready = statusMap.get(s.name);
|
|
301
|
+
const icon = ready ? '✓' : '⋯';
|
|
302
|
+
const status = ready ? 'ready' : 'waiting...';
|
|
303
|
+
const color = ready ? chalk.green : chalk.yellow;
|
|
304
|
+
return ` ${color(icon)} ${chalk.bold(s.name)} ${chalk.blue(`(${s.namespace})`)} - ${status}`;
|
|
305
|
+
});
|
|
306
|
+
waitSpinner.text = `Waiting for services to be ready (${elapsed}/${timeoutSeconds}s)...\n${lines.join('\n')}`;
|
|
307
|
+
});
|
|
308
|
+
if (result) {
|
|
309
|
+
waitSpinner.succeed('All services are ready');
|
|
310
|
+
process.exit(0);
|
|
311
|
+
}
|
|
312
|
+
else {
|
|
313
|
+
waitSpinner.fail(`Services did not become ready within ${timeoutSeconds} seconds`);
|
|
314
|
+
process.exit(1);
|
|
315
|
+
}
|
|
316
|
+
}
|
|
267
317
|
process.exit(0);
|
|
268
318
|
}
|
|
269
319
|
catch (error) {
|
|
@@ -276,6 +326,8 @@ export function createStatusCommand() {
|
|
|
276
326
|
const statusCommand = new Command('status');
|
|
277
327
|
statusCommand
|
|
278
328
|
.description('Check ARK system status')
|
|
279
|
-
.
|
|
329
|
+
.argument('[services...]', 'specific services to check (optional)')
|
|
330
|
+
.option('--wait-for-ready <timeout>', 'wait for services to be ready (e.g., 30s, 2m, 1h)')
|
|
331
|
+
.action((services, options) => checkStatus(services, options));
|
|
280
332
|
return statusCommand;
|
|
281
333
|
}
|