@agents-at-scale/ark 0.1.31
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +95 -0
- package/dist/commands/cluster/get-ip.d.ts +2 -0
- package/dist/commands/cluster/get-ip.js +32 -0
- package/dist/commands/cluster/get-type.d.ts +2 -0
- package/dist/commands/cluster/get-type.js +26 -0
- package/dist/commands/cluster/index.d.ts +2 -0
- package/dist/commands/cluster/index.js +10 -0
- package/dist/commands/completion.d.ts +2 -0
- package/dist/commands/completion.js +108 -0
- package/dist/commands/config.d.ts +5 -0
- package/dist/commands/config.js +327 -0
- package/dist/commands/generate/config.d.ts +145 -0
- package/dist/commands/generate/config.js +253 -0
- package/dist/commands/generate/generators/agent.d.ts +2 -0
- package/dist/commands/generate/generators/agent.js +156 -0
- package/dist/commands/generate/generators/index.d.ts +6 -0
- package/dist/commands/generate/generators/index.js +6 -0
- package/dist/commands/generate/generators/marketplace.d.ts +2 -0
- package/dist/commands/generate/generators/marketplace.js +304 -0
- package/dist/commands/generate/generators/mcpserver.d.ts +25 -0
- package/dist/commands/generate/generators/mcpserver.js +350 -0
- package/dist/commands/generate/generators/project.d.ts +2 -0
- package/dist/commands/generate/generators/project.js +784 -0
- package/dist/commands/generate/generators/query.d.ts +2 -0
- package/dist/commands/generate/generators/query.js +213 -0
- package/dist/commands/generate/generators/team.d.ts +2 -0
- package/dist/commands/generate/generators/team.js +407 -0
- package/dist/commands/generate/index.d.ts +24 -0
- package/dist/commands/generate/index.js +357 -0
- package/dist/commands/generate/templateDiscovery.d.ts +30 -0
- package/dist/commands/generate/templateDiscovery.js +94 -0
- package/dist/commands/generate/templateEngine.d.ts +78 -0
- package/dist/commands/generate/templateEngine.js +368 -0
- package/dist/commands/generate/utils/nameUtils.d.ts +35 -0
- package/dist/commands/generate/utils/nameUtils.js +110 -0
- package/dist/commands/generate/utils/projectUtils.d.ts +28 -0
- package/dist/commands/generate/utils/projectUtils.js +133 -0
- package/dist/components/DashboardCLI.d.ts +3 -0
- package/dist/components/DashboardCLI.js +149 -0
- package/dist/components/GeneratorUI.d.ts +3 -0
- package/dist/components/GeneratorUI.js +167 -0
- package/dist/components/statusChecker.d.ts +48 -0
- package/dist/components/statusChecker.js +251 -0
- package/dist/config.d.ts +42 -0
- package/dist/config.js +243 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +67 -0
- package/dist/lib/arkClient.d.ts +32 -0
- package/dist/lib/arkClient.js +43 -0
- package/dist/lib/cluster.d.ts +8 -0
- package/dist/lib/cluster.js +134 -0
- package/dist/lib/config.d.ts +82 -0
- package/dist/lib/config.js +223 -0
- package/dist/lib/consts.d.ts +10 -0
- package/dist/lib/consts.js +15 -0
- package/dist/lib/errors.d.ts +56 -0
- package/dist/lib/errors.js +208 -0
- package/dist/lib/exec.d.ts +5 -0
- package/dist/lib/exec.js +20 -0
- package/dist/lib/gatewayManager.d.ts +24 -0
- package/dist/lib/gatewayManager.js +85 -0
- package/dist/lib/kubernetes.d.ts +28 -0
- package/dist/lib/kubernetes.js +122 -0
- package/dist/lib/progress.d.ts +128 -0
- package/dist/lib/progress.js +273 -0
- package/dist/lib/security.d.ts +37 -0
- package/dist/lib/security.js +295 -0
- package/dist/lib/types.d.ts +37 -0
- package/dist/lib/types.js +1 -0
- package/dist/lib/wrappers/git.d.ts +2 -0
- package/dist/lib/wrappers/git.js +43 -0
- package/dist/ui/MainMenu.d.ts +3 -0
- package/dist/ui/MainMenu.js +116 -0
- package/dist/ui/statusFormatter.d.ts +9 -0
- package/dist/ui/statusFormatter.js +47 -0
- package/package.json +62 -0
|
@@ -0,0 +1,295 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Security utilities for ARK CLI
|
|
3
|
+
*/
|
|
4
|
+
import path from 'path';
|
|
5
|
+
import fs from 'fs';
|
|
6
|
+
import { ValidationError } from './errors.js';
|
|
7
|
+
export class SecurityUtils {
|
|
8
|
+
/**
|
|
9
|
+
* Validate that a path is safe and doesn't contain directory traversal attempts
|
|
10
|
+
*/
|
|
11
|
+
static validatePath(filePath, context = 'path') {
|
|
12
|
+
if (!filePath || typeof filePath !== 'string') {
|
|
13
|
+
throw new ValidationError(`Invalid ${context}: path must be a non-empty string`, 'path', ['Provide a valid file path']);
|
|
14
|
+
}
|
|
15
|
+
// Normalize the path to resolve any relative components
|
|
16
|
+
const normalizedPath = path.normalize(filePath);
|
|
17
|
+
// Check for directory traversal attempts
|
|
18
|
+
if (normalizedPath.includes('..') || normalizedPath.includes('~')) {
|
|
19
|
+
throw new ValidationError(`Unsafe ${context}: contains directory traversal sequences`, 'path', [
|
|
20
|
+
'Use absolute paths or simple relative paths',
|
|
21
|
+
'Remove parent directory references (..)',
|
|
22
|
+
'Remove home directory references (~)',
|
|
23
|
+
]);
|
|
24
|
+
}
|
|
25
|
+
// Check for absolute paths that go outside expected directories
|
|
26
|
+
if (path.isAbsolute(normalizedPath)) {
|
|
27
|
+
const dangerous = ['/etc', '/var', '/usr', '/bin', '/sbin', '/root'];
|
|
28
|
+
if (dangerous.some((dangerousPath) => normalizedPath.startsWith(dangerousPath))) {
|
|
29
|
+
throw new ValidationError(`Unsafe ${context}: attempts to access system directory`, 'path', [
|
|
30
|
+
'Use project-relative paths',
|
|
31
|
+
'Avoid system directories',
|
|
32
|
+
'Use a safe working directory',
|
|
33
|
+
]);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
// Check for null bytes and other dangerous characters
|
|
37
|
+
if (normalizedPath.includes('\0') || normalizedPath.includes('\u0000')) {
|
|
38
|
+
throw new ValidationError(`Invalid ${context}: contains null bytes`, 'path', ['Remove null bytes from the path']);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Ensure a directory path is safe to create/write to
|
|
43
|
+
*/
|
|
44
|
+
static validateOutputPath(outputPath, baseDir) {
|
|
45
|
+
this.validatePath(outputPath, 'output path');
|
|
46
|
+
this.validatePath(baseDir, 'base directory');
|
|
47
|
+
const resolvedOutput = path.resolve(outputPath);
|
|
48
|
+
const resolvedBase = path.resolve(baseDir);
|
|
49
|
+
// Ensure the output path is within the base directory
|
|
50
|
+
if (!resolvedOutput.startsWith(resolvedBase)) {
|
|
51
|
+
throw new ValidationError('Output path is outside the allowed directory', 'outputPath', [
|
|
52
|
+
`Ensure output path is within: ${resolvedBase}`,
|
|
53
|
+
'Use relative paths within the project',
|
|
54
|
+
'Check for directory traversal in the path',
|
|
55
|
+
]);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Sanitize file names to prevent issues
|
|
60
|
+
*/
|
|
61
|
+
static sanitizeFileName(fileName) {
|
|
62
|
+
if (!fileName || typeof fileName !== 'string') {
|
|
63
|
+
throw new ValidationError('Invalid file name: must be a non-empty string', 'fileName');
|
|
64
|
+
}
|
|
65
|
+
// Standard dotfiles that should be preserved
|
|
66
|
+
const allowedDotfiles = [
|
|
67
|
+
'.gitignore',
|
|
68
|
+
'.gitattributes',
|
|
69
|
+
'.github',
|
|
70
|
+
'.gitmodules',
|
|
71
|
+
'.helmignore',
|
|
72
|
+
'.dockerignore',
|
|
73
|
+
'.eslintrc',
|
|
74
|
+
'.prettierrc',
|
|
75
|
+
'.editorconfig',
|
|
76
|
+
'.nvmrc',
|
|
77
|
+
'.yamllint.yml',
|
|
78
|
+
'.yamllint.yaml',
|
|
79
|
+
'.env',
|
|
80
|
+
'.env.example',
|
|
81
|
+
'.env.local',
|
|
82
|
+
'.env.production',
|
|
83
|
+
'.vscode',
|
|
84
|
+
'.idea',
|
|
85
|
+
'.vimrc',
|
|
86
|
+
'.bashrc',
|
|
87
|
+
'.zshrc',
|
|
88
|
+
'.keep',
|
|
89
|
+
];
|
|
90
|
+
// Check if this is an allowed dotfile
|
|
91
|
+
const isAllowedDotfile = allowedDotfiles.some((allowed) => fileName === allowed || fileName.startsWith(allowed + '.'));
|
|
92
|
+
// Remove dangerous characters
|
|
93
|
+
let sanitized = fileName
|
|
94
|
+
.replace(/[<>:"/\\|?*]/g, '') // Windows forbidden chars
|
|
95
|
+
.replace(/\s+/g, '-') // Replace spaces with hyphens
|
|
96
|
+
.replace(/-{2,}/g, '-') // Replace 2+ consecutive hyphens with single hyphen (ReDoS-safe)
|
|
97
|
+
.replace(/^-/, '') // Remove single leading hyphen (ReDoS-safe)
|
|
98
|
+
.replace(/-$/, ''); // Remove single trailing hyphen (ReDoS-safe)
|
|
99
|
+
// Remove leading dots only if not an allowed dotfile
|
|
100
|
+
if (!isAllowedDotfile) {
|
|
101
|
+
sanitized = sanitized.replace(/^\\./, ''); // Remove single leading dot (ReDoS-safe)
|
|
102
|
+
}
|
|
103
|
+
// Ensure it's not empty after sanitization
|
|
104
|
+
if (!sanitized) {
|
|
105
|
+
throw new ValidationError('File name becomes empty after sanitization', 'fileName', ['Use alphanumeric characters and hyphens only']);
|
|
106
|
+
}
|
|
107
|
+
// Ensure it's not too long
|
|
108
|
+
if (sanitized.length > 255) {
|
|
109
|
+
sanitized = sanitized.substring(0, 255);
|
|
110
|
+
}
|
|
111
|
+
// Check for reserved names (Windows)
|
|
112
|
+
const reservedNames = [
|
|
113
|
+
'CON',
|
|
114
|
+
'PRN',
|
|
115
|
+
'AUX',
|
|
116
|
+
'NUL',
|
|
117
|
+
'COM1',
|
|
118
|
+
'COM2',
|
|
119
|
+
'COM3',
|
|
120
|
+
'COM4',
|
|
121
|
+
'COM5',
|
|
122
|
+
'COM6',
|
|
123
|
+
'COM7',
|
|
124
|
+
'COM8',
|
|
125
|
+
'COM9',
|
|
126
|
+
'LPT1',
|
|
127
|
+
'LPT2',
|
|
128
|
+
'LPT3',
|
|
129
|
+
'LPT4',
|
|
130
|
+
'LPT5',
|
|
131
|
+
'LPT6',
|
|
132
|
+
'LPT7',
|
|
133
|
+
'LPT8',
|
|
134
|
+
'LPT9',
|
|
135
|
+
];
|
|
136
|
+
if (reservedNames.includes(sanitized.toUpperCase())) {
|
|
137
|
+
sanitized = `_${sanitized}`;
|
|
138
|
+
}
|
|
139
|
+
return sanitized;
|
|
140
|
+
}
|
|
141
|
+
/**
|
|
142
|
+
* Validate template content to prevent code injection
|
|
143
|
+
*/
|
|
144
|
+
static validateTemplateContent(content, templatePath) {
|
|
145
|
+
if (!content || typeof content !== 'string') {
|
|
146
|
+
return; // Empty content is fine
|
|
147
|
+
}
|
|
148
|
+
// File types that legitimately need shell/script syntax and should skip validation
|
|
149
|
+
const exemptFileTypes = [
|
|
150
|
+
'.sh',
|
|
151
|
+
'.bash',
|
|
152
|
+
'.zsh',
|
|
153
|
+
'.fish', // Shell scripts
|
|
154
|
+
'Makefile',
|
|
155
|
+
'makefile',
|
|
156
|
+
'.mk', // Makefiles
|
|
157
|
+
'.ps1',
|
|
158
|
+
'.cmd',
|
|
159
|
+
'.bat', // Windows scripts
|
|
160
|
+
'.py',
|
|
161
|
+
'.js',
|
|
162
|
+
'.ts',
|
|
163
|
+
'.rb',
|
|
164
|
+
'.pl', // Programming languages
|
|
165
|
+
'.dockerfile',
|
|
166
|
+
'Dockerfile', // Docker files
|
|
167
|
+
'.yml',
|
|
168
|
+
'.yaml', // YAML files (may contain scripts in CI/CD)
|
|
169
|
+
'.md',
|
|
170
|
+
'.markdown',
|
|
171
|
+
'.rst',
|
|
172
|
+
'.txt', // Documentation files (may contain code examples)
|
|
173
|
+
];
|
|
174
|
+
const fileName = path.basename(templatePath).toLowerCase();
|
|
175
|
+
const fileExt = path.extname(templatePath).toLowerCase();
|
|
176
|
+
// Skip validation for exempt file types
|
|
177
|
+
if (exemptFileTypes.some((exempt) => fileName === exempt.toLowerCase() ||
|
|
178
|
+
fileName.includes(exempt.toLowerCase()) ||
|
|
179
|
+
fileExt === exempt.toLowerCase())) {
|
|
180
|
+
return;
|
|
181
|
+
}
|
|
182
|
+
// Check for potentially dangerous patterns
|
|
183
|
+
const dangerousPatterns = [
|
|
184
|
+
/\$\{[\w.]{0,100}?\([a-zA-Z0-9_.,\s]{0,100}?\)/, // Function calls in template variables (ReDoS-safe)
|
|
185
|
+
/\$\([a-zA-Z0-9\s._-]{0,100}?\)/, // Command substitution (ReDoS-safe)
|
|
186
|
+
/`[a-zA-Z0-9\s._${}()-]{0,500}?`/, // Backticks (ReDoS-safe)
|
|
187
|
+
/eval\s*\(/, // Eval statements
|
|
188
|
+
/exec\s*\(/, // Exec statements
|
|
189
|
+
/require\s*\(/, // Require statements
|
|
190
|
+
/\bimport\s/, // Import statements (ReDoS-safe - simple detection)
|
|
191
|
+
];
|
|
192
|
+
for (const pattern of dangerousPatterns) {
|
|
193
|
+
if (pattern.test(content)) {
|
|
194
|
+
throw new ValidationError(`Template contains potentially dangerous code: ${templatePath}`, 'templateContent', [
|
|
195
|
+
'Remove code execution patterns from templates',
|
|
196
|
+
'Use only variable substitution',
|
|
197
|
+
'Check template for malicious content',
|
|
198
|
+
]);
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
/**
|
|
203
|
+
* Safely create directories with proper permissions
|
|
204
|
+
*/
|
|
205
|
+
static async createDirectorySafe(dirPath, baseDir) {
|
|
206
|
+
this.validateOutputPath(dirPath, baseDir);
|
|
207
|
+
try {
|
|
208
|
+
await fs.promises.mkdir(dirPath, {
|
|
209
|
+
recursive: true,
|
|
210
|
+
mode: 0o755, // Read/write/execute for owner, read/execute for group and others
|
|
211
|
+
});
|
|
212
|
+
}
|
|
213
|
+
catch (error) {
|
|
214
|
+
throw new ValidationError(`Failed to create directory: ${dirPath}: ${error instanceof Error ? error.message : String(error)}`, 'directory', [
|
|
215
|
+
'Check file permissions',
|
|
216
|
+
'Ensure parent directory exists',
|
|
217
|
+
'Verify disk space availability',
|
|
218
|
+
]);
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
/**
|
|
222
|
+
* Safely write files with proper permissions
|
|
223
|
+
*/
|
|
224
|
+
static async writeFileSafe(filePath, content, baseDir) {
|
|
225
|
+
this.validateOutputPath(filePath, baseDir);
|
|
226
|
+
this.validateTemplateContent(content, filePath);
|
|
227
|
+
const fileName = path.basename(filePath);
|
|
228
|
+
const sanitizedFileName = this.sanitizeFileName(fileName);
|
|
229
|
+
if (sanitizedFileName !== fileName) {
|
|
230
|
+
throw new ValidationError(`File name requires sanitization: "${fileName}" ā "${sanitizedFileName}"`, 'fileName', [`Use the sanitized name: "${sanitizedFileName}"`]);
|
|
231
|
+
}
|
|
232
|
+
try {
|
|
233
|
+
await fs.promises.writeFile(filePath, content, {
|
|
234
|
+
mode: 0o644, // Read/write for owner, read for group and others
|
|
235
|
+
flag: 'w', // Overwrite if exists
|
|
236
|
+
});
|
|
237
|
+
}
|
|
238
|
+
catch (error) {
|
|
239
|
+
throw new ValidationError(`Failed to write file: ${filePath}: ${error instanceof Error ? error.message : String(error)}`, 'file', [
|
|
240
|
+
'Check file permissions',
|
|
241
|
+
'Ensure directory exists',
|
|
242
|
+
'Verify disk space availability',
|
|
243
|
+
]);
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
/**
|
|
247
|
+
* Validate environment variables for safety
|
|
248
|
+
*/
|
|
249
|
+
static sanitizeEnvironmentValue(value, varName) {
|
|
250
|
+
if (!value || typeof value !== 'string') {
|
|
251
|
+
return '';
|
|
252
|
+
}
|
|
253
|
+
// Remove potentially dangerous characters
|
|
254
|
+
const sanitized = value
|
|
255
|
+
.replace(/[`$\\]/g, '') // Command injection chars
|
|
256
|
+
.trim();
|
|
257
|
+
// Warn if significant changes were made
|
|
258
|
+
if (sanitized !== value && sanitized.length < value.length * 0.8) {
|
|
259
|
+
console.warn(`Warning: Environment variable ${varName} was heavily sanitized`);
|
|
260
|
+
}
|
|
261
|
+
return sanitized;
|
|
262
|
+
}
|
|
263
|
+
/**
|
|
264
|
+
* Validate API keys and secrets
|
|
265
|
+
*/
|
|
266
|
+
static validateSecret(secret, secretType) {
|
|
267
|
+
if (!secret || typeof secret !== 'string') {
|
|
268
|
+
throw new ValidationError(`Invalid ${secretType}: must be a non-empty string`, 'secret');
|
|
269
|
+
}
|
|
270
|
+
// Check for common patterns that suggest it's not a real secret
|
|
271
|
+
const testPatterns = [
|
|
272
|
+
/^(test|dummy|fake|placeholder|example)/i,
|
|
273
|
+
/^(xxx|000|123)/,
|
|
274
|
+
/^your[_-]?key/i,
|
|
275
|
+
/^replace[_-]?me/i,
|
|
276
|
+
];
|
|
277
|
+
for (const pattern of testPatterns) {
|
|
278
|
+
if (pattern.test(secret)) {
|
|
279
|
+
throw new ValidationError(`${secretType} appears to be a placeholder value`, 'secret', [
|
|
280
|
+
`Replace with a real ${secretType}`,
|
|
281
|
+
'Check your configuration',
|
|
282
|
+
'Ensure secrets are properly set',
|
|
283
|
+
]);
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
// Basic length validation (most API keys are at least 16 chars)
|
|
287
|
+
if (secret.length < 16) {
|
|
288
|
+
throw new ValidationError(`${secretType} appears too short (${secret.length} characters)`, 'secret', [
|
|
289
|
+
'Ensure you have the complete key',
|
|
290
|
+
'Check for truncation',
|
|
291
|
+
'Verify with your provider',
|
|
292
|
+
]);
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
export interface ArkConfig {
|
|
2
|
+
defaultModel?: string;
|
|
3
|
+
defaultAgent?: string;
|
|
4
|
+
defaultNamespace?: string;
|
|
5
|
+
apiBaseUrl?: string;
|
|
6
|
+
kubeconfig?: string;
|
|
7
|
+
currentContext?: string;
|
|
8
|
+
kubeNamespace?: string;
|
|
9
|
+
}
|
|
10
|
+
export interface KubernetesConfig {
|
|
11
|
+
kubeconfig: string;
|
|
12
|
+
currentContext?: string;
|
|
13
|
+
namespace?: string;
|
|
14
|
+
inCluster: boolean;
|
|
15
|
+
}
|
|
16
|
+
export interface ServiceStatus {
|
|
17
|
+
name: string;
|
|
18
|
+
status: 'healthy' | 'unhealthy' | 'not installed';
|
|
19
|
+
url?: string;
|
|
20
|
+
version?: string;
|
|
21
|
+
details?: string;
|
|
22
|
+
}
|
|
23
|
+
export interface DependencyStatus {
|
|
24
|
+
name: string;
|
|
25
|
+
installed: boolean;
|
|
26
|
+
version?: string;
|
|
27
|
+
details?: string;
|
|
28
|
+
}
|
|
29
|
+
export interface StatusData {
|
|
30
|
+
services: ServiceStatus[];
|
|
31
|
+
dependencies: DependencyStatus[];
|
|
32
|
+
}
|
|
33
|
+
export interface CommandVersionConfig {
|
|
34
|
+
command: string;
|
|
35
|
+
versionArgs: string;
|
|
36
|
+
versionExtract: (_output: string) => string;
|
|
37
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import * as path from 'path';
|
|
2
|
+
import chalk from 'chalk';
|
|
3
|
+
import { simpleGit } from 'simple-git';
|
|
4
|
+
import { ARK_REPO_ERROR_MESSAGE } from '../consts.js';
|
|
5
|
+
// Initialize simple-git instance
|
|
6
|
+
const git = simpleGit();
|
|
7
|
+
export async function ensureInArkRepo(expectedRepoName, expectedRemoteSubstring) {
|
|
8
|
+
try {
|
|
9
|
+
// Get repository root
|
|
10
|
+
const gitRoot = await git.revparse(['--show-toplevel']);
|
|
11
|
+
if (path.basename(gitRoot) !== expectedRepoName) {
|
|
12
|
+
console.error(chalk.red(ARK_REPO_ERROR_MESSAGE));
|
|
13
|
+
process.exit(1);
|
|
14
|
+
}
|
|
15
|
+
if (expectedRemoteSubstring) {
|
|
16
|
+
try {
|
|
17
|
+
// Get remote URL
|
|
18
|
+
const remoteUrl = await git.remote(['get-url', 'origin']);
|
|
19
|
+
if (!remoteUrl || !remoteUrl.includes(expectedRemoteSubstring)) {
|
|
20
|
+
console.error(chalk.red(ARK_REPO_ERROR_MESSAGE));
|
|
21
|
+
process.exit(1);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
catch (_error) {
|
|
25
|
+
console.error(chalk.red(ARK_REPO_ERROR_MESSAGE));
|
|
26
|
+
process.exit(1);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
catch (_error) {
|
|
31
|
+
console.error(chalk.red(ARK_REPO_ERROR_MESSAGE));
|
|
32
|
+
process.exit(1);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
export async function getRepoProjectRoot() {
|
|
36
|
+
try {
|
|
37
|
+
return await git.revparse(['--show-toplevel']);
|
|
38
|
+
}
|
|
39
|
+
catch (_error) {
|
|
40
|
+
console.error(chalk.red('Failed to determine git repository root path'));
|
|
41
|
+
process.exit(1);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
+
import { Text, Box, useInput } from 'ink';
|
|
3
|
+
import SelectInput from 'ink-select-input';
|
|
4
|
+
import * as React from 'react';
|
|
5
|
+
import DashboardCLI from '../components/DashboardCLI.js';
|
|
6
|
+
import GeneratorUI from '../components/GeneratorUI.js';
|
|
7
|
+
import { StatusChecker } from '../components/statusChecker.js';
|
|
8
|
+
import { ConfigManager } from '../config.js';
|
|
9
|
+
import { ArkClient } from '../lib/arkClient.js';
|
|
10
|
+
const EXIT_TIMEOUT_MS = 1000;
|
|
11
|
+
const MainMenu = () => {
|
|
12
|
+
const [selectedChoice, setSelectedChoice] = React.useState(null);
|
|
13
|
+
const [statusData, setStatusData] = React.useState(null);
|
|
14
|
+
const [isLoading, setIsLoading] = React.useState(false);
|
|
15
|
+
const [error, setError] = React.useState(null);
|
|
16
|
+
const choices = [
|
|
17
|
+
{ label: 'š·ļø Dashboard', value: 'dashboard' },
|
|
18
|
+
{ label: 'š Status Check', value: 'status' },
|
|
19
|
+
{ label: 'šÆ Generate', value: 'generate' },
|
|
20
|
+
{ label: 'š Exit', value: 'exit' },
|
|
21
|
+
];
|
|
22
|
+
React.useEffect(() => {
|
|
23
|
+
if (selectedChoice === 'exit') {
|
|
24
|
+
const timer = setTimeout(() => {
|
|
25
|
+
process.exit(0);
|
|
26
|
+
}, EXIT_TIMEOUT_MS);
|
|
27
|
+
return () => clearTimeout(timer);
|
|
28
|
+
}
|
|
29
|
+
}, [selectedChoice]);
|
|
30
|
+
React.useEffect(() => {
|
|
31
|
+
if (selectedChoice === 'status' && !statusData && !isLoading) {
|
|
32
|
+
checkStatus();
|
|
33
|
+
}
|
|
34
|
+
}, [selectedChoice, statusData, isLoading]);
|
|
35
|
+
const checkStatus = async () => {
|
|
36
|
+
setIsLoading(true);
|
|
37
|
+
setError(null);
|
|
38
|
+
try {
|
|
39
|
+
const configManager = new ConfigManager();
|
|
40
|
+
const apiBaseUrl = await configManager.getApiBaseUrl();
|
|
41
|
+
const serviceUrls = await configManager.getServiceUrls();
|
|
42
|
+
const arkClient = new ArkClient(apiBaseUrl);
|
|
43
|
+
const statusChecker = new StatusChecker(arkClient);
|
|
44
|
+
const status = await statusChecker.checkAll(serviceUrls, apiBaseUrl);
|
|
45
|
+
setStatusData(status);
|
|
46
|
+
}
|
|
47
|
+
catch (err) {
|
|
48
|
+
setError(err instanceof Error ? err.message : 'Unknown error occurred');
|
|
49
|
+
}
|
|
50
|
+
finally {
|
|
51
|
+
setIsLoading(false);
|
|
52
|
+
}
|
|
53
|
+
};
|
|
54
|
+
useInput((input, key) => {
|
|
55
|
+
if (selectedChoice && selectedChoice !== 'exit') {
|
|
56
|
+
if (key.escape || input === 'q' || key.return) {
|
|
57
|
+
// For generate, only reset on specific key combinations to avoid conflicts
|
|
58
|
+
if (selectedChoice === 'generate' && !(key.escape || input === 'q')) {
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
setSelectedChoice(null);
|
|
62
|
+
setStatusData(null);
|
|
63
|
+
setError(null);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
});
|
|
67
|
+
const renderBanner = () => (_jsxs(Box, { flexDirection: "column", alignItems: "center", marginBottom: 1, children: [_jsx(Text, { color: "cyan", bold: true, children: `
|
|
68
|
+
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
|
|
69
|
+
ā ā
|
|
70
|
+
ā āāāāāā āāāāāāā āāā āāā ā
|
|
71
|
+
ā āāāāāāāāāāāāāāāāāāā āāāā ā
|
|
72
|
+
ā āāāāāāāāāāāāāāāāāāāāāāā ā
|
|
73
|
+
ā āāāāāāāāāāāāāāāāāāāāāāā ā
|
|
74
|
+
ā āāā āāāāāā āāāāāā āāā ā
|
|
75
|
+
ā āāā āāāāāā āāāāāā āāā ā
|
|
76
|
+
ā ā
|
|
77
|
+
ā Agents at Scale Platform ā
|
|
78
|
+
ā ā
|
|
79
|
+
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
|
|
80
|
+
` }), _jsx(Text, { color: "green", bold: true, children: "Welcome to ARK! \uD83D\uDE80" }), _jsx(Text, { color: "gray", children: "Interactive terminal interface for ARK agents" })] }));
|
|
81
|
+
const renderServiceStatus = (service) => {
|
|
82
|
+
const statusColor = service.status === 'healthy'
|
|
83
|
+
? 'green'
|
|
84
|
+
: service.status === 'unhealthy'
|
|
85
|
+
? 'red'
|
|
86
|
+
: 'yellow';
|
|
87
|
+
const statusIcon = service.status === 'healthy'
|
|
88
|
+
? 'ā'
|
|
89
|
+
: service.status === 'unhealthy'
|
|
90
|
+
? 'ā'
|
|
91
|
+
: '?';
|
|
92
|
+
return (_jsxs(Box, { flexDirection: "column", marginLeft: 2, children: [_jsxs(Box, { children: [_jsxs(Text, { color: statusColor, children: [statusIcon, " "] }), _jsxs(Text, { bold: true, children: [service.name, ": "] }), _jsx(Text, { color: statusColor, children: service.status })] }), service.url && (_jsx(Box, { marginLeft: 2, children: _jsxs(Text, { color: "gray", children: ["URL: ", service.url] }) })), service.details && (_jsx(Box, { marginLeft: 2, children: _jsx(Text, { color: "gray", children: service.details }) }))] }, service.name));
|
|
93
|
+
};
|
|
94
|
+
const renderDependencyStatus = (dep) => {
|
|
95
|
+
const statusColor = dep.installed ? 'green' : 'red';
|
|
96
|
+
const statusIcon = dep.installed ? 'ā' : 'ā';
|
|
97
|
+
const statusText = dep.installed ? 'installed' : 'missing';
|
|
98
|
+
return (_jsxs(Box, { flexDirection: "column", marginLeft: 2, children: [_jsxs(Box, { children: [_jsxs(Text, { color: statusColor, children: [statusIcon, " "] }), _jsxs(Text, { bold: true, children: [dep.name, ": "] }), _jsx(Text, { color: statusColor, children: statusText })] }), dep.version && (_jsx(Box, { marginLeft: 2, children: _jsxs(Text, { color: "gray", children: ["Version: ", dep.version] }) })), dep.details && (_jsx(Box, { marginLeft: 2, children: _jsx(Text, { color: "gray", children: dep.details }) }))] }, dep.name));
|
|
99
|
+
};
|
|
100
|
+
const renderStatus = () => {
|
|
101
|
+
if (isLoading) {
|
|
102
|
+
return (_jsxs(Box, { flexDirection: "column", children: [_jsx(Text, { color: "yellow", children: "\uD83D\uDD0D Checking ARK system status..." }), _jsx(Text, { color: "gray", children: "Please wait while we verify services and dependencies." })] }));
|
|
103
|
+
}
|
|
104
|
+
if (error) {
|
|
105
|
+
return (_jsxs(Box, { flexDirection: "column", children: [_jsx(Text, { color: "red", children: "\u274C Error checking status:" }), _jsx(Text, { color: "red", children: error }), _jsx(Box, { marginTop: 1, children: _jsx(Text, { color: "gray", children: "Press ESC, 'q', or Enter to return to menu..." }) })] }));
|
|
106
|
+
}
|
|
107
|
+
if (!statusData) {
|
|
108
|
+
return null;
|
|
109
|
+
}
|
|
110
|
+
return (_jsxs(Box, { flexDirection: "column", children: [_jsx(Text, { color: "cyan", bold: true, children: "\uD83D\uDD0D ARK System Status" }), _jsx(Box, { marginTop: 1, children: _jsx(Text, { color: "cyan", bold: true, children: "\uD83D\uDCE1 ARK Services:" }) }), statusData.services.map(renderServiceStatus), _jsx(Box, { marginTop: 1, children: _jsx(Text, { color: "cyan", bold: true, children: "\uD83D\uDEE0\uFE0F System Dependencies:" }) }), statusData.dependencies.map(renderDependencyStatus), _jsx(Box, { marginTop: 1, children: _jsx(Text, { color: "gray", children: "Press ESC, 'q', or Enter to return to menu..." }) })] }));
|
|
111
|
+
};
|
|
112
|
+
return (_jsxs(_Fragment, { children: [renderBanner(), !selectedChoice && (_jsx(SelectInput, { items: choices, onSelect: (choice) => {
|
|
113
|
+
setSelectedChoice(choice.value);
|
|
114
|
+
} })), selectedChoice === 'status' && renderStatus(), selectedChoice === 'dashboard' && (_jsxs(Box, { flexDirection: "column", children: [_jsx(Text, { color: "green", children: "\uD83C\uDFF7\uFE0F Dashboard feature selected" }), _jsx(DashboardCLI, {})] })), selectedChoice === 'generate' && _jsx(GeneratorUI, {}), selectedChoice === 'exit' && _jsx(Text, { color: "yellow", children: "\uD83D\uDC4B Goodbye!" })] }));
|
|
115
|
+
};
|
|
116
|
+
export default MainMenu;
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
export class StatusFormatter {
|
|
3
|
+
/**
|
|
4
|
+
* Print status check results to console
|
|
5
|
+
*/
|
|
6
|
+
static printStatus(statusData) {
|
|
7
|
+
console.log(chalk.cyan.bold('\nš ARK System Status Check'));
|
|
8
|
+
console.log(chalk.gray('Checking ARK services and dependencies...\n'));
|
|
9
|
+
// Print services status
|
|
10
|
+
console.log(chalk.cyan.bold('š” ARK Services:'));
|
|
11
|
+
for (const service of statusData.services) {
|
|
12
|
+
StatusFormatter.printService(service);
|
|
13
|
+
}
|
|
14
|
+
// Print dependencies status
|
|
15
|
+
console.log(chalk.cyan.bold('\nš ļø System Dependencies:'));
|
|
16
|
+
for (const dep of statusData.dependencies) {
|
|
17
|
+
StatusFormatter.printDependency(dep);
|
|
18
|
+
}
|
|
19
|
+
console.log();
|
|
20
|
+
}
|
|
21
|
+
static printService(service) {
|
|
22
|
+
const statusColor = service.status === 'healthy'
|
|
23
|
+
? chalk.green('ā healthy')
|
|
24
|
+
: service.status === 'unhealthy'
|
|
25
|
+
? chalk.red('ā unhealthy')
|
|
26
|
+
: chalk.yellow('? not installed');
|
|
27
|
+
console.log(` ⢠${chalk.bold(service.name)}: ${statusColor}`);
|
|
28
|
+
if (service.url) {
|
|
29
|
+
console.log(` ${chalk.gray(`URL: ${service.url}`)}`);
|
|
30
|
+
}
|
|
31
|
+
if (service.details) {
|
|
32
|
+
console.log(` ${chalk.gray(service.details)}`);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
static printDependency(dep) {
|
|
36
|
+
const statusColor = dep.installed
|
|
37
|
+
? chalk.green('ā installed')
|
|
38
|
+
: chalk.red('ā missing');
|
|
39
|
+
console.log(` ⢠${chalk.bold(dep.name)}: ${statusColor}`);
|
|
40
|
+
if (dep.version) {
|
|
41
|
+
console.log(` ${chalk.gray(`Version: ${dep.version}`)}`);
|
|
42
|
+
}
|
|
43
|
+
if (dep.details) {
|
|
44
|
+
console.log(` ${chalk.gray(dep.details)}`);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@agents-at-scale/ark",
|
|
3
|
+
"version": "0.1.31",
|
|
4
|
+
"description": "ARK CLI - Interactive terminal interface for ARK agents",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"bin": {
|
|
8
|
+
"ark": "dist/index.js"
|
|
9
|
+
},
|
|
10
|
+
"files": [
|
|
11
|
+
"dist"
|
|
12
|
+
],
|
|
13
|
+
"scripts": {
|
|
14
|
+
"build": "tsc && chmod +x dist/index.js",
|
|
15
|
+
"dev": "node --loader ts-node/esm src/index.tsx",
|
|
16
|
+
"start": "node dist/index.js",
|
|
17
|
+
"clean": "rm -rf dist",
|
|
18
|
+
"lint": "eslint src/ --fix && prettier --write src/",
|
|
19
|
+
"lint:check": "eslint src/ && prettier --check src/",
|
|
20
|
+
"postinstall": "echo \"ARK CLI installed! Run 'ark' to try it out.\""
|
|
21
|
+
},
|
|
22
|
+
"keywords": [
|
|
23
|
+
"ark",
|
|
24
|
+
"cli",
|
|
25
|
+
"terminal",
|
|
26
|
+
"ai",
|
|
27
|
+
"agents"
|
|
28
|
+
],
|
|
29
|
+
"author": "",
|
|
30
|
+
"dependencies": {
|
|
31
|
+
"@kubernetes/client-node": "^1.3.0",
|
|
32
|
+
"@types/react": "^19.1.8",
|
|
33
|
+
"axios": "^1.7.7",
|
|
34
|
+
"chalk": "^4.1.2",
|
|
35
|
+
"commander": "^12.1.0",
|
|
36
|
+
"debug": "^4.4.1",
|
|
37
|
+
"execa": "^9.6.0",
|
|
38
|
+
"ink": "^6.0.1",
|
|
39
|
+
"ink-select-input": "^6.2.0",
|
|
40
|
+
"ink-text-input": "^6.0.0",
|
|
41
|
+
"inquirer": "^12.1.0",
|
|
42
|
+
"open": "^10.2.0",
|
|
43
|
+
"react": "^19.1.0",
|
|
44
|
+
"simple-git": "^3.28.0",
|
|
45
|
+
"yaml": "^2.6.1"
|
|
46
|
+
},
|
|
47
|
+
"devDependencies": {
|
|
48
|
+
"@eslint/js": "^9.17.0",
|
|
49
|
+
"@types/debug": "^4.1.12",
|
|
50
|
+
"@types/inquirer": "^9.0.7",
|
|
51
|
+
"@types/node": "^22.10.2",
|
|
52
|
+
"@typescript-eslint/eslint-plugin": "^8.20.0",
|
|
53
|
+
"@typescript-eslint/parser": "^8.20.0",
|
|
54
|
+
"eslint": "^9.17.0",
|
|
55
|
+
"prettier": "^3.6.2",
|
|
56
|
+
"ts-node": "^10.9.2",
|
|
57
|
+
"typescript": "^5.7.2"
|
|
58
|
+
},
|
|
59
|
+
"engines": {
|
|
60
|
+
"node": ">=18.0.0"
|
|
61
|
+
}
|
|
62
|
+
}
|