@agents-at-scale/ark 0.1.36-rc1 → 0.1.37
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/completion/index.js +0 -2
- 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/ChatUI.js +19 -1
- 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.js +14 -0
- package/dist/lib/waitForReady.d.ts +8 -0
- package/dist/lib/waitForReady.js +32 -0
- package/dist/lib/waitForReady.spec.js +104 -0
- package/dist/types/arkService.d.ts +27 -0
- package/package.json +3 -3
- package/dist/charts/charts.d.ts +0 -5
- package/dist/charts/charts.js +0 -6
- package/dist/charts/dependencies.d.ts +0 -6
- package/dist/charts/dependencies.js +0 -50
- package/dist/charts/types.d.ts +0 -40
- package/dist/commands/agents/selector.d.ts +0 -8
- package/dist/commands/agents/selector.js +0 -53
- package/dist/commands/agents.d.ts +0 -2
- package/dist/commands/agents.js +0 -53
- package/dist/commands/chat.d.ts +0 -2
- package/dist/commands/chat.js +0 -45
- 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 -265
- package/dist/commands/config.d.ts +0 -2
- package/dist/commands/config.js +0 -44
- package/dist/commands/dashboard.d.ts +0 -3
- package/dist/commands/dashboard.js +0 -39
- package/dist/commands/dev/index.d.ts +0 -3
- package/dist/commands/dev/index.js +0 -9
- package/dist/commands/dev/tool/check.d.ts +0 -2
- package/dist/commands/dev/tool/check.js +0 -142
- package/dist/commands/dev/tool/clean.d.ts +0 -2
- package/dist/commands/dev/tool/clean.js +0 -153
- package/dist/commands/dev/tool/generate.d.ts +0 -2
- package/dist/commands/dev/tool/generate.js +0 -28
- package/dist/commands/dev/tool/index.d.ts +0 -2
- package/dist/commands/dev/tool/index.js +0 -14
- package/dist/commands/dev/tool/init.d.ts +0 -2
- package/dist/commands/dev/tool/init.js +0 -320
- package/dist/commands/dev/tool/shared.d.ts +0 -5
- package/dist/commands/dev/tool/shared.js +0 -258
- package/dist/commands/dev/tool/status.d.ts +0 -2
- package/dist/commands/dev/tool/status.js +0 -136
- package/dist/commands/dev/tool-generate.spec.js +0 -163
- package/dist/commands/dev/tool.d.ts +0 -2
- package/dist/commands/dev/tool.js +0 -559
- package/dist/commands/dev/tool.spec.js +0 -48
- package/dist/commands/install.d.ts +0 -3
- package/dist/commands/install.js +0 -147
- package/dist/commands/models/selector.d.ts +0 -8
- package/dist/commands/models/selector.js +0 -53
- package/dist/commands/routes.d.ts +0 -2
- package/dist/commands/routes.js +0 -101
- package/dist/commands/status.d.ts +0 -3
- package/dist/commands/status.js +0 -33
- package/dist/commands/targets.d.ts +0 -2
- package/dist/commands/targets.js +0 -65
- package/dist/commands/teams/selector.d.ts +0 -8
- package/dist/commands/teams/selector.js +0 -55
- package/dist/commands/tools/selector.d.ts +0 -8
- package/dist/commands/tools/selector.js +0 -53
- package/dist/commands/uninstall.d.ts +0 -2
- package/dist/commands/uninstall.js +0 -83
- package/dist/components/DashboardCLI.d.ts +0 -3
- package/dist/components/DashboardCLI.js +0 -149
- package/dist/components/StatusView.d.ts +0 -10
- package/dist/components/StatusView.js +0 -39
- package/dist/config.d.ts +0 -23
- package/dist/config.js +0 -92
- package/dist/lib/arkClient.d.ts +0 -32
- package/dist/lib/arkClient.js +0 -43
- package/dist/lib/commandUtils.d.ts +0 -4
- package/dist/lib/commandUtils.js +0 -18
- package/dist/lib/commandUtils.test.d.ts +0 -1
- package/dist/lib/commandUtils.test.js +0 -44
- package/dist/lib/config.test.d.ts +0 -1
- package/dist/lib/config.test.js +0 -93
- package/dist/lib/consts.d.ts +0 -9
- package/dist/lib/consts.js +0 -13
- package/dist/lib/consts.spec.d.ts +0 -1
- package/dist/lib/consts.spec.js +0 -15
- package/dist/lib/dev/tools/analyzer.d.ts +0 -30
- package/dist/lib/dev/tools/analyzer.js +0 -190
- package/dist/lib/dev/tools/discover_tools.py +0 -392
- package/dist/lib/dev/tools/mcp-types.d.ts +0 -28
- package/dist/lib/dev/tools/mcp-types.js +0 -86
- package/dist/lib/dev/tools/types.d.ts +0 -50
- package/dist/lib/dev/tools/types.js +0 -1
- package/dist/lib/exec.d.ts +0 -1
- package/dist/lib/exec.js +0 -9
- 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/portUtils.d.ts +0 -8
- package/dist/lib/portUtils.js +0 -39
- package/dist/lib/progress.d.ts +0 -128
- package/dist/lib/progress.js +0 -273
- package/dist/lib/queryRunner.d.ts +0 -22
- package/dist/lib/queryRunner.js +0 -142
- package/dist/lib/wrappers/git.d.ts +0 -2
- package/dist/lib/wrappers/git.js +0 -43
- /package/dist/{charts/types.js → lib/timeout.spec.d.ts} +0 -0
- /package/dist/{commands/dev/tool-generate.spec.d.ts → lib/waitForReady.spec.d.ts} +0 -0
- /package/dist/{commands/dev/tool.spec.d.ts → types/arkService.js} +0 -0
|
@@ -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
|
}
|
|
@@ -7,6 +7,7 @@ import * as React from 'react';
|
|
|
7
7
|
import { marked } from 'marked';
|
|
8
8
|
// @ts-ignore - no types available
|
|
9
9
|
import TerminalRenderer from 'marked-terminal';
|
|
10
|
+
import { APIError } from 'openai';
|
|
10
11
|
import { ChatClient, } from '../lib/chatClient.js';
|
|
11
12
|
import { AgentSelector } from '../ui/AgentSelector.js';
|
|
12
13
|
import { ModelSelector } from '../ui/ModelSelector.js';
|
|
@@ -548,7 +549,24 @@ const ChatUI = ({ initialTargetId, arkApiClient, arkApiProxy, config, }) => {
|
|
|
548
549
|
// Request was cancelled, message already updated by Esc handler
|
|
549
550
|
return;
|
|
550
551
|
}
|
|
551
|
-
|
|
552
|
+
let errorMessage = 'Failed to send message';
|
|
553
|
+
// OpenAI SDK errors include response body in .error property
|
|
554
|
+
if (err instanceof APIError) {
|
|
555
|
+
if (err.error && typeof err.error === 'object') {
|
|
556
|
+
errorMessage = JSON.stringify(err.error, null, 2);
|
|
557
|
+
}
|
|
558
|
+
else {
|
|
559
|
+
errorMessage = err.message;
|
|
560
|
+
}
|
|
561
|
+
}
|
|
562
|
+
// Standard JavaScript errors
|
|
563
|
+
else if (err instanceof Error) {
|
|
564
|
+
errorMessage = err.message;
|
|
565
|
+
}
|
|
566
|
+
// String errors from throw statements
|
|
567
|
+
else if (typeof err === 'string') {
|
|
568
|
+
errorMessage = err;
|
|
569
|
+
}
|
|
552
570
|
setError(errorMessage);
|
|
553
571
|
setIsTyping(false);
|
|
554
572
|
setAbortController(null);
|
package/dist/index.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
#!/usr/bin/env
|
|
1
|
+
#!/usr/bin/env node
|
|
2
2
|
export {};
|
package/dist/index.js
CHANGED
|
@@ -40,8 +40,7 @@ export interface Team {
|
|
|
40
40
|
export declare class ArkApiClient {
|
|
41
41
|
private openai;
|
|
42
42
|
private baseUrl;
|
|
43
|
-
|
|
44
|
-
constructor(arkApiUrl: string, namespace?: string);
|
|
43
|
+
constructor(arkApiUrl: string);
|
|
45
44
|
getBaseUrl(): string;
|
|
46
45
|
getQueryTargets(): Promise<QueryTarget[]>;
|
|
47
46
|
getAgents(): Promise<Agent[]>;
|
package/dist/lib/arkApiClient.js
CHANGED
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
import OpenAI from 'openai';
|
|
2
2
|
export class ArkApiClient {
|
|
3
|
-
constructor(arkApiUrl
|
|
3
|
+
constructor(arkApiUrl) {
|
|
4
4
|
this.baseUrl = arkApiUrl;
|
|
5
|
-
this.namespace = namespace;
|
|
6
5
|
this.openai = new OpenAI({
|
|
7
6
|
baseURL: `${arkApiUrl}/openai/v1`,
|
|
8
7
|
apiKey: 'dummy', // ark-api doesn't require an API key
|
|
@@ -34,7 +33,7 @@ export class ArkApiClient {
|
|
|
34
33
|
}
|
|
35
34
|
async getAgents() {
|
|
36
35
|
try {
|
|
37
|
-
const response = await fetch(`${this.baseUrl}/v1/
|
|
36
|
+
const response = await fetch(`${this.baseUrl}/v1/agents`);
|
|
38
37
|
if (!response.ok) {
|
|
39
38
|
throw new Error(`HTTP error! status: ${response.status}`);
|
|
40
39
|
}
|
|
@@ -47,7 +46,7 @@ export class ArkApiClient {
|
|
|
47
46
|
}
|
|
48
47
|
async getModels() {
|
|
49
48
|
try {
|
|
50
|
-
const response = await fetch(`${this.baseUrl}/v1/
|
|
49
|
+
const response = await fetch(`${this.baseUrl}/v1/models`);
|
|
51
50
|
if (!response.ok) {
|
|
52
51
|
throw new Error(`HTTP error! status: ${response.status}`);
|
|
53
52
|
}
|
|
@@ -60,7 +59,7 @@ export class ArkApiClient {
|
|
|
60
59
|
}
|
|
61
60
|
async getTools() {
|
|
62
61
|
try {
|
|
63
|
-
const response = await fetch(`${this.baseUrl}/v1/
|
|
62
|
+
const response = await fetch(`${this.baseUrl}/v1/tools`);
|
|
64
63
|
if (!response.ok) {
|
|
65
64
|
throw new Error(`HTTP error! status: ${response.status}`);
|
|
66
65
|
}
|
|
@@ -73,7 +72,7 @@ export class ArkApiClient {
|
|
|
73
72
|
}
|
|
74
73
|
async getTeams() {
|
|
75
74
|
try {
|
|
76
|
-
const response = await fetch(`${this.baseUrl}/v1/
|
|
75
|
+
const response = await fetch(`${this.baseUrl}/v1/teams`);
|
|
77
76
|
if (!response.ok) {
|
|
78
77
|
throw new Error(`HTTP error! status: ${response.status}`);
|
|
79
78
|
}
|
package/dist/lib/config.d.ts
CHANGED
|
@@ -1,10 +1,14 @@
|
|
|
1
1
|
import type { ClusterInfo } from './cluster.js';
|
|
2
|
+
import type { ArkService } from '../types/arkService.js';
|
|
2
3
|
export interface ChatConfig {
|
|
3
4
|
streaming?: boolean;
|
|
4
5
|
outputFormat?: 'text' | 'markdown';
|
|
5
6
|
}
|
|
6
7
|
export interface ArkConfig {
|
|
7
8
|
chat?: ChatConfig;
|
|
9
|
+
services?: {
|
|
10
|
+
[serviceName: string]: Partial<ArkService>;
|
|
11
|
+
};
|
|
8
12
|
clusterInfo?: ClusterInfo;
|
|
9
13
|
}
|
|
10
14
|
/**
|
package/dist/lib/config.js
CHANGED
|
@@ -70,6 +70,15 @@ function mergeConfig(target, source) {
|
|
|
70
70
|
target.chat.outputFormat = source.chat.outputFormat;
|
|
71
71
|
}
|
|
72
72
|
}
|
|
73
|
+
if (source.services) {
|
|
74
|
+
target.services = target.services || {};
|
|
75
|
+
for (const [serviceName, overrides] of Object.entries(source.services)) {
|
|
76
|
+
target.services[serviceName] = {
|
|
77
|
+
...target.services[serviceName],
|
|
78
|
+
...overrides,
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
}
|
|
73
82
|
}
|
|
74
83
|
/**
|
|
75
84
|
* Get the paths checked for config files
|
package/dist/lib/nextSteps.js
CHANGED
|
@@ -4,7 +4,7 @@ import chalk from 'chalk';
|
|
|
4
4
|
*/
|
|
5
5
|
export function printNextSteps() {
|
|
6
6
|
console.log();
|
|
7
|
-
console.log(chalk.green.bold('✓
|
|
7
|
+
console.log(chalk.green.bold('✓ Installation complete'));
|
|
8
8
|
console.log();
|
|
9
9
|
console.log(chalk.gray('Next steps:'));
|
|
10
10
|
console.log();
|
|
@@ -15,7 +15,7 @@ describe('printNextSteps', () => {
|
|
|
15
15
|
it('prints successful installation message', () => {
|
|
16
16
|
printNextSteps();
|
|
17
17
|
const fullOutput = output.join('\n');
|
|
18
|
-
expect(fullOutput).toContain('
|
|
18
|
+
expect(fullOutput).toContain('✓ Installation complete');
|
|
19
19
|
});
|
|
20
20
|
it('includes all required commands', () => {
|
|
21
21
|
printNextSteps();
|
package/dist/lib/security.js
CHANGED
|
@@ -9,6 +9,10 @@ export class SecurityUtils {
|
|
|
9
9
|
* Validate that a path is safe and doesn't contain directory traversal attempts
|
|
10
10
|
*/
|
|
11
11
|
static validatePath(filePath, context = 'path') {
|
|
12
|
+
// Skip validation for internal template paths - they're always safe
|
|
13
|
+
if (context === 'template path') {
|
|
14
|
+
return;
|
|
15
|
+
}
|
|
12
16
|
if (!filePath || typeof filePath !== 'string') {
|
|
13
17
|
throw new ValidationError(`Invalid ${context}: path must be a non-empty string`, 'path', ['Provide a valid file path']);
|
|
14
18
|
}
|
package/dist/lib/startup.js
CHANGED
|
@@ -52,7 +52,9 @@ export function showNoClusterError() {
|
|
|
52
52
|
*/
|
|
53
53
|
async function hasKubernetesContext() {
|
|
54
54
|
try {
|
|
55
|
-
const { stdout } = await execa('kubectl', ['config', 'current-context']
|
|
55
|
+
const { stdout } = await execa('kubectl', ['config', 'current-context'], {
|
|
56
|
+
timeout: 5000,
|
|
57
|
+
});
|
|
56
58
|
return stdout.trim().length > 0;
|
|
57
59
|
}
|
|
58
60
|
catch {
|
|
@@ -72,7 +74,9 @@ export async function startup() {
|
|
|
72
74
|
const hasContext = await hasKubernetesContext();
|
|
73
75
|
if (hasContext) {
|
|
74
76
|
try {
|
|
75
|
-
const { stdout } = await execa('kubectl', ['config', 'current-context']
|
|
77
|
+
const { stdout } = await execa('kubectl', ['config', 'current-context'], {
|
|
78
|
+
timeout: 5000,
|
|
79
|
+
});
|
|
76
80
|
config.clusterInfo = {
|
|
77
81
|
type: 'unknown', // We don't detect cluster type here - too slow
|
|
78
82
|
context: stdout.trim(),
|
package/dist/lib/startup.spec.js
CHANGED
|
@@ -138,7 +138,7 @@ describe('startup', () => {
|
|
|
138
138
|
type: 'unknown',
|
|
139
139
|
context: 'minikube',
|
|
140
140
|
});
|
|
141
|
-
expect(mockExeca).toHaveBeenCalledWith('kubectl', ['config', 'current-context']);
|
|
141
|
+
expect(mockExeca).toHaveBeenCalledWith('kubectl', ['config', 'current-context'], { timeout: 5000 });
|
|
142
142
|
});
|
|
143
143
|
it('handles missing kubectl context gracefully', async () => {
|
|
144
144
|
mockCheckCommandExists.mockResolvedValue(true);
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function parseTimeoutToSeconds(value: string): number;
|