@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,223 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Configuration management for ARK CLI
|
|
3
|
+
*/
|
|
4
|
+
import fs from 'fs';
|
|
5
|
+
import path from 'path';
|
|
6
|
+
import os from 'os';
|
|
7
|
+
import { SecurityUtils } from './security.js';
|
|
8
|
+
import { ValidationError } from './errors.js';
|
|
9
|
+
export const DEFAULT_CONFIG = {
|
|
10
|
+
defaultProjectType: 'with-samples',
|
|
11
|
+
defaultDestination: process.cwd(),
|
|
12
|
+
skipGitByDefault: false,
|
|
13
|
+
skipModelsbyDefault: false,
|
|
14
|
+
preferredEditor: process.env.EDITOR || 'code',
|
|
15
|
+
colorOutput: true,
|
|
16
|
+
verboseOutput: false,
|
|
17
|
+
defaultModelProvider: 'azure',
|
|
18
|
+
customTemplates: {},
|
|
19
|
+
parallelOperations: true,
|
|
20
|
+
maxConcurrentFiles: 10,
|
|
21
|
+
fileWatchingEnabled: false,
|
|
22
|
+
telemetryEnabled: false,
|
|
23
|
+
errorReporting: false,
|
|
24
|
+
};
|
|
25
|
+
export class ConfigManager {
|
|
26
|
+
constructor() {
|
|
27
|
+
this.configPath = this.getConfigPath();
|
|
28
|
+
this.config = this.loadConfig();
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Get the path to the configuration file
|
|
32
|
+
*/
|
|
33
|
+
getConfigPath() {
|
|
34
|
+
const configDir = process.env.ARK_CONFIG_DIR || path.join(os.homedir(), '.config', 'ark');
|
|
35
|
+
// Ensure config directory exists
|
|
36
|
+
if (!fs.existsSync(configDir)) {
|
|
37
|
+
fs.mkdirSync(configDir, { recursive: true, mode: 0o755 });
|
|
38
|
+
}
|
|
39
|
+
return path.join(configDir, 'config.json');
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Load configuration from file or create with defaults
|
|
43
|
+
*/
|
|
44
|
+
loadConfig() {
|
|
45
|
+
try {
|
|
46
|
+
if (fs.existsSync(this.configPath)) {
|
|
47
|
+
const configContent = fs.readFileSync(this.configPath, 'utf-8');
|
|
48
|
+
const userConfig = JSON.parse(configContent);
|
|
49
|
+
// Merge with defaults to ensure all properties exist
|
|
50
|
+
return { ...DEFAULT_CONFIG, ...userConfig };
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
catch (error) {
|
|
54
|
+
console.warn(`Warning: Failed to load config from ${this.configPath}: ${error}`);
|
|
55
|
+
}
|
|
56
|
+
// Return defaults and save them
|
|
57
|
+
this.saveConfig(DEFAULT_CONFIG);
|
|
58
|
+
return { ...DEFAULT_CONFIG };
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Save configuration to file
|
|
62
|
+
*/
|
|
63
|
+
saveConfig(config) {
|
|
64
|
+
try {
|
|
65
|
+
const configContent = JSON.stringify(config, null, 2);
|
|
66
|
+
SecurityUtils.validatePath(this.configPath, 'config file');
|
|
67
|
+
fs.writeFileSync(this.configPath, configContent, {
|
|
68
|
+
mode: 0o600, // Owner read/write only
|
|
69
|
+
flag: 'w',
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
catch (error) {
|
|
73
|
+
throw new ValidationError(`Failed to save configuration: ${error}`, 'config', [
|
|
74
|
+
'Check file permissions',
|
|
75
|
+
'Ensure config directory exists',
|
|
76
|
+
'Verify disk space',
|
|
77
|
+
]);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Get the current configuration
|
|
82
|
+
*/
|
|
83
|
+
getConfig() {
|
|
84
|
+
return { ...this.config };
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Update configuration
|
|
88
|
+
*/
|
|
89
|
+
updateConfig(updates) {
|
|
90
|
+
this.config = { ...this.config, ...updates };
|
|
91
|
+
this.saveConfig(this.config);
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Reset configuration to defaults
|
|
95
|
+
*/
|
|
96
|
+
resetConfig() {
|
|
97
|
+
this.config = { ...DEFAULT_CONFIG };
|
|
98
|
+
this.saveConfig(this.config);
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* Get a specific configuration value
|
|
102
|
+
*/
|
|
103
|
+
get(key) {
|
|
104
|
+
return this.config[key];
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* Set a specific configuration value
|
|
108
|
+
*/
|
|
109
|
+
set(key, value) {
|
|
110
|
+
this.config[key] = value;
|
|
111
|
+
this.saveConfig(this.config);
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* Validate configuration values
|
|
115
|
+
*/
|
|
116
|
+
validateConfig() {
|
|
117
|
+
const config = this.config;
|
|
118
|
+
// Validate project type
|
|
119
|
+
if (!['empty', 'with-samples'].includes(config.defaultProjectType)) {
|
|
120
|
+
throw new ValidationError(`Invalid defaultProjectType: ${config.defaultProjectType}`, 'defaultProjectType', ['Must be "empty" or "with-samples"']);
|
|
121
|
+
}
|
|
122
|
+
// Validate model provider
|
|
123
|
+
const validProviders = ['azure', 'openai', 'claude', 'gemini', 'custom'];
|
|
124
|
+
if (!validProviders.includes(config.defaultModelProvider)) {
|
|
125
|
+
throw new ValidationError(`Invalid defaultModelProvider: ${config.defaultModelProvider}`, 'defaultModelProvider', [`Must be one of: ${validProviders.join(', ')}`]);
|
|
126
|
+
}
|
|
127
|
+
// Validate numeric values
|
|
128
|
+
if (config.maxConcurrentFiles < 1 || config.maxConcurrentFiles > 100) {
|
|
129
|
+
throw new ValidationError(`Invalid maxConcurrentFiles: ${config.maxConcurrentFiles}`, 'maxConcurrentFiles', ['Must be between 1 and 100']);
|
|
130
|
+
}
|
|
131
|
+
// Validate paths
|
|
132
|
+
if (config.defaultDestination) {
|
|
133
|
+
SecurityUtils.validatePath(config.defaultDestination, 'default destination');
|
|
134
|
+
}
|
|
135
|
+
if (config.templateDirectory) {
|
|
136
|
+
SecurityUtils.validatePath(config.templateDirectory, 'template directory');
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
/**
|
|
140
|
+
* Get environment variable overrides
|
|
141
|
+
*/
|
|
142
|
+
getEnvironmentOverrides() {
|
|
143
|
+
const overrides = {};
|
|
144
|
+
// Check for environment variable overrides
|
|
145
|
+
if (process.env.ARK_DEFAULT_PROJECT_TYPE) {
|
|
146
|
+
const projectType = process.env.ARK_DEFAULT_PROJECT_TYPE;
|
|
147
|
+
if (['empty', 'with-samples'].includes(projectType)) {
|
|
148
|
+
overrides.defaultProjectType = projectType;
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
if (process.env.ARK_DEFAULT_DESTINATION) {
|
|
152
|
+
overrides.defaultDestination = process.env.ARK_DEFAULT_DESTINATION;
|
|
153
|
+
}
|
|
154
|
+
if (process.env.ARK_SKIP_GIT) {
|
|
155
|
+
overrides.skipGitByDefault = process.env.ARK_SKIP_GIT === 'true';
|
|
156
|
+
}
|
|
157
|
+
if (process.env.ARK_SKIP_MODELS) {
|
|
158
|
+
overrides.skipModelsbyDefault = process.env.ARK_SKIP_MODELS === 'true';
|
|
159
|
+
}
|
|
160
|
+
if (process.env.ARK_COLOR_OUTPUT) {
|
|
161
|
+
overrides.colorOutput = process.env.ARK_COLOR_OUTPUT !== 'false';
|
|
162
|
+
}
|
|
163
|
+
if (process.env.ARK_VERBOSE) {
|
|
164
|
+
overrides.verboseOutput = process.env.ARK_VERBOSE === 'true';
|
|
165
|
+
}
|
|
166
|
+
if (process.env.ARK_DEFAULT_MODEL_PROVIDER) {
|
|
167
|
+
const provider = process.env.ARK_DEFAULT_MODEL_PROVIDER;
|
|
168
|
+
const validProviders = [
|
|
169
|
+
'azure',
|
|
170
|
+
'openai',
|
|
171
|
+
'claude',
|
|
172
|
+
'gemini',
|
|
173
|
+
'custom',
|
|
174
|
+
];
|
|
175
|
+
if (validProviders.includes(provider)) {
|
|
176
|
+
overrides.defaultModelProvider =
|
|
177
|
+
provider;
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
return overrides;
|
|
181
|
+
}
|
|
182
|
+
/**
|
|
183
|
+
* Get merged configuration with environment overrides
|
|
184
|
+
*/
|
|
185
|
+
getMergedConfig() {
|
|
186
|
+
const envOverrides = this.getEnvironmentOverrides();
|
|
187
|
+
return { ...this.config, ...envOverrides };
|
|
188
|
+
}
|
|
189
|
+
/**
|
|
190
|
+
* Export configuration for backup
|
|
191
|
+
*/
|
|
192
|
+
exportConfig() {
|
|
193
|
+
return JSON.stringify(this.config, null, 2);
|
|
194
|
+
}
|
|
195
|
+
/**
|
|
196
|
+
* Import configuration from backup
|
|
197
|
+
*/
|
|
198
|
+
importConfig(configJson) {
|
|
199
|
+
try {
|
|
200
|
+
const importedConfig = JSON.parse(configJson);
|
|
201
|
+
// Validate the imported config
|
|
202
|
+
const tempManager = new ConfigManager();
|
|
203
|
+
tempManager.config = { ...DEFAULT_CONFIG, ...importedConfig };
|
|
204
|
+
tempManager.validateConfig();
|
|
205
|
+
// If validation passes, update our config
|
|
206
|
+
this.config = tempManager.config;
|
|
207
|
+
this.saveConfig(this.config);
|
|
208
|
+
}
|
|
209
|
+
catch (error) {
|
|
210
|
+
throw new ValidationError(`Failed to import configuration: ${error}`, 'config', [
|
|
211
|
+
'Check JSON syntax',
|
|
212
|
+
'Ensure all required fields are present',
|
|
213
|
+
'Verify configuration values are valid',
|
|
214
|
+
]);
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
/**
|
|
218
|
+
* Get configuration file path for CLI display
|
|
219
|
+
*/
|
|
220
|
+
getConfigFilePath() {
|
|
221
|
+
return this.configPath;
|
|
222
|
+
}
|
|
223
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export declare const DEFAULT_ADDRESS_ARK_API = "http://localhost:8000";
|
|
2
|
+
export declare const DEFAULT_TIMEOUT_MS = 30000;
|
|
3
|
+
export declare const DEFAULT_CONNECTION_TEST_TIMEOUT_MS = 5000;
|
|
4
|
+
export declare const CONFIG_DIR_NAME = "ark";
|
|
5
|
+
export declare const CONFIG_FILE_NAME = "ark-cli.json";
|
|
6
|
+
export declare const DEFAULT_ARK_DASHBOARD_URL = "http://localhost:3000";
|
|
7
|
+
export declare const DEFAULT_ARK_A2A_URL = "http://localhost:8080";
|
|
8
|
+
export declare const DEFAULT_ARK_MEMORY_URL = "http://localhost:8081";
|
|
9
|
+
export declare const DEFAULT_ARK_OTEL_URL = "http://localhost:4318";
|
|
10
|
+
export declare const ARK_REPO_ERROR_MESSAGE = "Error: This command must be run inside the ARK repository.";
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
// Default ARK API server address
|
|
2
|
+
export const DEFAULT_ADDRESS_ARK_API = 'http://localhost:8000';
|
|
3
|
+
// Default timeout values
|
|
4
|
+
export const DEFAULT_TIMEOUT_MS = 30000;
|
|
5
|
+
export const DEFAULT_CONNECTION_TEST_TIMEOUT_MS = 5000;
|
|
6
|
+
// Configuration paths
|
|
7
|
+
export const CONFIG_DIR_NAME = 'ark';
|
|
8
|
+
export const CONFIG_FILE_NAME = 'ark-cli.json';
|
|
9
|
+
// Default service URLs
|
|
10
|
+
export const DEFAULT_ARK_DASHBOARD_URL = 'http://localhost:3000';
|
|
11
|
+
export const DEFAULT_ARK_A2A_URL = 'http://localhost:8080';
|
|
12
|
+
export const DEFAULT_ARK_MEMORY_URL = 'http://localhost:8081';
|
|
13
|
+
export const DEFAULT_ARK_OTEL_URL = 'http://localhost:4318';
|
|
14
|
+
// Error message for different ARK repo
|
|
15
|
+
export const ARK_REPO_ERROR_MESSAGE = 'Error: This command must be run inside the ARK repository.';
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Centralized error handling for ARK CLI
|
|
3
|
+
*/
|
|
4
|
+
export declare enum ErrorCode {
|
|
5
|
+
INVALID_INPUT = "INVALID_INPUT",
|
|
6
|
+
FILE_NOT_FOUND = "FILE_NOT_FOUND",
|
|
7
|
+
PERMISSION_DENIED = "PERMISSION_DENIED",
|
|
8
|
+
TEMPLATE_ERROR = "TEMPLATE_ERROR",
|
|
9
|
+
VALIDATION_ERROR = "VALIDATION_ERROR",
|
|
10
|
+
NETWORK_ERROR = "NETWORK_ERROR",
|
|
11
|
+
DEPENDENCY_MISSING = "DEPENDENCY_MISSING",
|
|
12
|
+
PROJECT_STRUCTURE_INVALID = "PROJECT_STRUCTURE_INVALID",
|
|
13
|
+
GENERATION_FAILED = "GENERATION_FAILED",
|
|
14
|
+
UNKNOWN_ERROR = "UNKNOWN_ERROR"
|
|
15
|
+
}
|
|
16
|
+
export declare class ArkError extends Error {
|
|
17
|
+
readonly code: ErrorCode;
|
|
18
|
+
readonly details?: Record<string, unknown>;
|
|
19
|
+
readonly suggestions?: string[];
|
|
20
|
+
constructor(message: string, code?: ErrorCode, details?: Record<string, unknown>, suggestions?: string[]);
|
|
21
|
+
}
|
|
22
|
+
export declare class ValidationError extends ArkError {
|
|
23
|
+
constructor(message: string, field?: string, suggestions?: string[]);
|
|
24
|
+
}
|
|
25
|
+
export declare class TemplateError extends ArkError {
|
|
26
|
+
constructor(message: string, templatePath?: string, suggestions?: string[]);
|
|
27
|
+
}
|
|
28
|
+
export declare class ProjectStructureError extends ArkError {
|
|
29
|
+
constructor(message: string, projectPath?: string, suggestions?: string[]);
|
|
30
|
+
}
|
|
31
|
+
export declare class ErrorHandler {
|
|
32
|
+
/**
|
|
33
|
+
* Format and display an error with helpful context
|
|
34
|
+
*/
|
|
35
|
+
static formatError(error: Error | ArkError): string;
|
|
36
|
+
/**
|
|
37
|
+
* Handle and exit with appropriate error code
|
|
38
|
+
*/
|
|
39
|
+
static handleAndExit(error: Error | ArkError): never;
|
|
40
|
+
/**
|
|
41
|
+
* Wrap async functions with error handling
|
|
42
|
+
*/
|
|
43
|
+
static catchAndHandle<T>(fn: () => Promise<T>, context?: string): Promise<T>;
|
|
44
|
+
/**
|
|
45
|
+
* Validate required dependencies and throw helpful errors
|
|
46
|
+
*/
|
|
47
|
+
static validateDependency(command: string, purpose: string, installInstructions?: string): Promise<void>;
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Input validation utilities with better error messages
|
|
51
|
+
*/
|
|
52
|
+
export declare class InputValidator {
|
|
53
|
+
static validateName(name: string, type?: string): void;
|
|
54
|
+
static validatePath(path: string, type?: string): void;
|
|
55
|
+
static validateDirectory(dirPath: string, shouldExist?: boolean): void;
|
|
56
|
+
}
|
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Centralized error handling for ARK CLI
|
|
3
|
+
*/
|
|
4
|
+
import chalk from 'chalk';
|
|
5
|
+
import fs from 'fs';
|
|
6
|
+
export var ErrorCode;
|
|
7
|
+
(function (ErrorCode) {
|
|
8
|
+
ErrorCode["INVALID_INPUT"] = "INVALID_INPUT";
|
|
9
|
+
ErrorCode["FILE_NOT_FOUND"] = "FILE_NOT_FOUND";
|
|
10
|
+
ErrorCode["PERMISSION_DENIED"] = "PERMISSION_DENIED";
|
|
11
|
+
ErrorCode["TEMPLATE_ERROR"] = "TEMPLATE_ERROR";
|
|
12
|
+
ErrorCode["VALIDATION_ERROR"] = "VALIDATION_ERROR";
|
|
13
|
+
ErrorCode["NETWORK_ERROR"] = "NETWORK_ERROR";
|
|
14
|
+
ErrorCode["DEPENDENCY_MISSING"] = "DEPENDENCY_MISSING";
|
|
15
|
+
ErrorCode["PROJECT_STRUCTURE_INVALID"] = "PROJECT_STRUCTURE_INVALID";
|
|
16
|
+
ErrorCode["GENERATION_FAILED"] = "GENERATION_FAILED";
|
|
17
|
+
ErrorCode["UNKNOWN_ERROR"] = "UNKNOWN_ERROR";
|
|
18
|
+
})(ErrorCode || (ErrorCode = {}));
|
|
19
|
+
export class ArkError extends Error {
|
|
20
|
+
constructor(message, code = ErrorCode.UNKNOWN_ERROR, details, suggestions) {
|
|
21
|
+
super(message);
|
|
22
|
+
this.name = 'ArkError';
|
|
23
|
+
this.code = code;
|
|
24
|
+
this.details = details;
|
|
25
|
+
this.suggestions = suggestions;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
export class ValidationError extends ArkError {
|
|
29
|
+
constructor(message, field, suggestions) {
|
|
30
|
+
super(message, ErrorCode.VALIDATION_ERROR, field ? { field } : undefined, suggestions);
|
|
31
|
+
this.name = 'ValidationError';
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
export class TemplateError extends ArkError {
|
|
35
|
+
constructor(message, templatePath, suggestions) {
|
|
36
|
+
super(message, ErrorCode.TEMPLATE_ERROR, templatePath ? { templatePath } : undefined, suggestions);
|
|
37
|
+
this.name = 'TemplateError';
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
export class ProjectStructureError extends ArkError {
|
|
41
|
+
constructor(message, projectPath, suggestions) {
|
|
42
|
+
super(message, ErrorCode.PROJECT_STRUCTURE_INVALID, projectPath ? { projectPath } : undefined, suggestions || [
|
|
43
|
+
'Ensure you are in a valid ARK project directory',
|
|
44
|
+
'Run "ark generate project" to create a new project',
|
|
45
|
+
'Check that Chart.yaml and agents/ directory exist',
|
|
46
|
+
]);
|
|
47
|
+
this.name = 'ProjectStructureError';
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
export class ErrorHandler {
|
|
51
|
+
/**
|
|
52
|
+
* Format and display an error with helpful context
|
|
53
|
+
*/
|
|
54
|
+
static formatError(error) {
|
|
55
|
+
const lines = [];
|
|
56
|
+
// Error header
|
|
57
|
+
lines.push(chalk.red(`❌ ${error.message}`));
|
|
58
|
+
// Add error details if available
|
|
59
|
+
if (error instanceof ArkError) {
|
|
60
|
+
if (error.details) {
|
|
61
|
+
lines.push('');
|
|
62
|
+
lines.push(chalk.gray('Details:'));
|
|
63
|
+
for (const [key, value] of Object.entries(error.details)) {
|
|
64
|
+
lines.push(chalk.gray(` ${key}: ${value}`));
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
// Add suggestions if available
|
|
68
|
+
if (error.suggestions && error.suggestions.length > 0) {
|
|
69
|
+
lines.push('');
|
|
70
|
+
lines.push(chalk.yellow('💡 Suggestions:'));
|
|
71
|
+
error.suggestions.forEach((suggestion) => {
|
|
72
|
+
lines.push(chalk.yellow(` • ${suggestion}`));
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
// Add stack trace in debug mode
|
|
77
|
+
if (process.env.DEBUG || process.env.NODE_ENV === 'development') {
|
|
78
|
+
lines.push('');
|
|
79
|
+
lines.push(chalk.gray('Stack trace:'));
|
|
80
|
+
lines.push(chalk.gray(error.stack || 'No stack trace available'));
|
|
81
|
+
}
|
|
82
|
+
return lines.join('\n');
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Handle and exit with appropriate error code
|
|
86
|
+
*/
|
|
87
|
+
static handleAndExit(error) {
|
|
88
|
+
console.error('\n' + this.formatError(error));
|
|
89
|
+
// Exit with appropriate code
|
|
90
|
+
if (error instanceof ArkError) {
|
|
91
|
+
switch (error.code) {
|
|
92
|
+
case ErrorCode.INVALID_INPUT:
|
|
93
|
+
case ErrorCode.VALIDATION_ERROR:
|
|
94
|
+
process.exit(22); // EINVAL
|
|
95
|
+
break;
|
|
96
|
+
case ErrorCode.FILE_NOT_FOUND:
|
|
97
|
+
process.exit(2); // ENOENT
|
|
98
|
+
break;
|
|
99
|
+
case ErrorCode.PERMISSION_DENIED:
|
|
100
|
+
process.exit(13); // EACCES
|
|
101
|
+
break;
|
|
102
|
+
case ErrorCode.DEPENDENCY_MISSING:
|
|
103
|
+
process.exit(127); // Command not found
|
|
104
|
+
break;
|
|
105
|
+
default:
|
|
106
|
+
process.exit(1);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
process.exit(1);
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* Wrap async functions with error handling
|
|
113
|
+
*/
|
|
114
|
+
static async catchAndHandle(fn, context) {
|
|
115
|
+
try {
|
|
116
|
+
return await fn();
|
|
117
|
+
}
|
|
118
|
+
catch (error) {
|
|
119
|
+
if (error instanceof ArkError) {
|
|
120
|
+
throw error;
|
|
121
|
+
}
|
|
122
|
+
// Convert generic errors to ArkError with context
|
|
123
|
+
const errorText = error instanceof Error ? error.message : String(error);
|
|
124
|
+
const message = context ? `${context}: ${errorText}` : errorText;
|
|
125
|
+
throw new ArkError(message, ErrorCode.UNKNOWN_ERROR);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
/**
|
|
129
|
+
* Validate required dependencies and throw helpful errors
|
|
130
|
+
*/
|
|
131
|
+
static async validateDependency(command, purpose, installInstructions) {
|
|
132
|
+
try {
|
|
133
|
+
const { execa } = await import('execa');
|
|
134
|
+
await execa(command, ['--version'], { stdio: 'ignore' });
|
|
135
|
+
}
|
|
136
|
+
catch {
|
|
137
|
+
const suggestions = [];
|
|
138
|
+
if (installInstructions) {
|
|
139
|
+
suggestions.push(installInstructions);
|
|
140
|
+
}
|
|
141
|
+
suggestions.push(`Visit the official ${command} documentation for installation instructions`);
|
|
142
|
+
throw new ArkError(`${command} is required for ${purpose} but was not found`, ErrorCode.DEPENDENCY_MISSING, { command, purpose }, suggestions);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
/**
|
|
147
|
+
* Input validation utilities with better error messages
|
|
148
|
+
*/
|
|
149
|
+
export class InputValidator {
|
|
150
|
+
static validateName(name, type = 'name') {
|
|
151
|
+
if (!name || name.trim().length === 0) {
|
|
152
|
+
throw new ValidationError(`${type} cannot be empty`, 'name', [
|
|
153
|
+
`Provide a valid ${type}`,
|
|
154
|
+
]);
|
|
155
|
+
}
|
|
156
|
+
const trimmed = name.trim();
|
|
157
|
+
if (trimmed.length > 63) {
|
|
158
|
+
throw new ValidationError(`${type} must be 63 characters or less (got ${trimmed.length})`, 'name', [`Shorten the ${type} to 63 characters or less`]);
|
|
159
|
+
}
|
|
160
|
+
// Kubernetes name validation
|
|
161
|
+
const kebabRegex = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
|
|
162
|
+
if (!kebabRegex.test(trimmed)) {
|
|
163
|
+
const suggestions = [];
|
|
164
|
+
const normalized = trimmed
|
|
165
|
+
.toLowerCase()
|
|
166
|
+
.replace(/[\s_]+/g, '-')
|
|
167
|
+
.replace(/([a-z])([A-Z])/g, '$1-$2')
|
|
168
|
+
.replace(/-{2,}/g, '-') // Replace 2+ consecutive hyphens with single hyphen (ReDoS-safe)
|
|
169
|
+
.replace(/^-/, '') // Remove single leading hyphen (ReDoS-safe)
|
|
170
|
+
.replace(/-$/, ''); // Remove single trailing hyphen (ReDoS-safe)
|
|
171
|
+
if (normalized !== trimmed) {
|
|
172
|
+
suggestions.push(`Try: "${normalized}"`);
|
|
173
|
+
}
|
|
174
|
+
suggestions.push(`${type} must be lowercase letters, numbers, and hyphens only`);
|
|
175
|
+
suggestions.push(`${type} cannot start or end with a hyphen`);
|
|
176
|
+
throw new ValidationError(`Invalid ${type}: "${trimmed}"`, 'name', suggestions);
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
static validatePath(path, type = 'path') {
|
|
180
|
+
if (!path || path.trim().length === 0) {
|
|
181
|
+
throw new ValidationError(`${type} cannot be empty`, 'path');
|
|
182
|
+
}
|
|
183
|
+
// Check for potentially dangerous paths
|
|
184
|
+
const dangerous = ['..', '~', '$'];
|
|
185
|
+
if (dangerous.some((pattern) => path.includes(pattern))) {
|
|
186
|
+
throw new ValidationError(`${type} contains potentially unsafe characters`, 'path', [
|
|
187
|
+
'Use absolute paths or simple relative paths',
|
|
188
|
+
'Avoid parent directory references (..)',
|
|
189
|
+
'Avoid shell variables and special characters',
|
|
190
|
+
]);
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
static validateDirectory(dirPath, shouldExist = false) {
|
|
194
|
+
this.validatePath(dirPath, 'directory');
|
|
195
|
+
if (shouldExist) {
|
|
196
|
+
if (!fs.existsSync(dirPath)) {
|
|
197
|
+
throw new ValidationError(`Directory does not exist: ${dirPath}`, 'directory', [
|
|
198
|
+
'Create the directory first',
|
|
199
|
+
'Check the path spelling',
|
|
200
|
+
'Ensure you have read permissions',
|
|
201
|
+
]);
|
|
202
|
+
}
|
|
203
|
+
if (!fs.statSync(dirPath).isDirectory()) {
|
|
204
|
+
throw new ValidationError(`Path exists but is not a directory: ${dirPath}`, 'directory');
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
}
|
package/dist/lib/exec.js
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import { execa } from 'execa';
|
|
3
|
+
export async function executeCommand(command, args = []) {
|
|
4
|
+
try {
|
|
5
|
+
const result = await execa(command, args);
|
|
6
|
+
return { stdout: result.stdout, stderr: result.stderr };
|
|
7
|
+
}
|
|
8
|
+
catch (error) {
|
|
9
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
10
|
+
throw new Error(`Command failed: ${command} ${args.join(' ')}\n${errorMessage}`);
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
export function fileExists(path) {
|
|
14
|
+
try {
|
|
15
|
+
return fs.existsSync(path);
|
|
16
|
+
}
|
|
17
|
+
catch {
|
|
18
|
+
return false;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
export declare class GatewayManager {
|
|
2
|
+
private kubernetesManager;
|
|
3
|
+
constructor();
|
|
4
|
+
/**
|
|
5
|
+
* Check if localhost-gateway port-forward is running by testing the endpoint
|
|
6
|
+
*/
|
|
7
|
+
isPortForwardRunning(port?: number): Promise<boolean>;
|
|
8
|
+
/**
|
|
9
|
+
* Check if localhost-gateway service is deployed in cluster
|
|
10
|
+
*/
|
|
11
|
+
isGatewayDeployed(): Promise<boolean>;
|
|
12
|
+
/**
|
|
13
|
+
* Check if port-forward is needed and provide setup instructions
|
|
14
|
+
*/
|
|
15
|
+
checkPortForwardStatus(port?: number): Promise<{
|
|
16
|
+
isRunning: boolean;
|
|
17
|
+
needsSetup: boolean;
|
|
18
|
+
instructions?: string;
|
|
19
|
+
}>;
|
|
20
|
+
/**
|
|
21
|
+
* Get setup instructions for fresh installations
|
|
22
|
+
*/
|
|
23
|
+
getSetupInstructions(): string;
|
|
24
|
+
}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import * as k8s from '@kubernetes/client-node';
|
|
2
|
+
import { KubernetesConfigManager } from './kubernetes.js';
|
|
3
|
+
export class GatewayManager {
|
|
4
|
+
constructor() {
|
|
5
|
+
this.kubernetesManager = new KubernetesConfigManager();
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* Check if localhost-gateway port-forward is running by testing the endpoint
|
|
9
|
+
*/
|
|
10
|
+
async isPortForwardRunning(port = 8080) {
|
|
11
|
+
try {
|
|
12
|
+
const axios = (await import('axios')).default;
|
|
13
|
+
await axios.get(`http://127.0.0.1:${port}`, {
|
|
14
|
+
timeout: 2000,
|
|
15
|
+
validateStatus: () => true,
|
|
16
|
+
});
|
|
17
|
+
return true;
|
|
18
|
+
}
|
|
19
|
+
catch {
|
|
20
|
+
return false;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Check if localhost-gateway service is deployed in cluster
|
|
25
|
+
*/
|
|
26
|
+
async isGatewayDeployed() {
|
|
27
|
+
try {
|
|
28
|
+
await this.kubernetesManager.initializeConfig();
|
|
29
|
+
const kc = this.kubernetesManager.getKubeConfig();
|
|
30
|
+
const k8sApi = kc.makeApiClient(k8s.CoreV1Api);
|
|
31
|
+
await k8sApi.readNamespacedService({
|
|
32
|
+
name: 'localhost-gateway-nginx',
|
|
33
|
+
namespace: 'ark-system',
|
|
34
|
+
});
|
|
35
|
+
return true;
|
|
36
|
+
}
|
|
37
|
+
catch {
|
|
38
|
+
return false;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Check if port-forward is needed and provide setup instructions
|
|
43
|
+
*/
|
|
44
|
+
async checkPortForwardStatus(port = 8080) {
|
|
45
|
+
const isRunning = await this.isPortForwardRunning(port);
|
|
46
|
+
if (isRunning) {
|
|
47
|
+
return { isRunning: true, needsSetup: false };
|
|
48
|
+
}
|
|
49
|
+
const isDeployed = await this.isGatewayDeployed();
|
|
50
|
+
if (!isDeployed) {
|
|
51
|
+
return {
|
|
52
|
+
isRunning: false,
|
|
53
|
+
needsSetup: true,
|
|
54
|
+
instructions: this.getSetupInstructions(),
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
return {
|
|
58
|
+
isRunning: false,
|
|
59
|
+
needsSetup: true,
|
|
60
|
+
instructions: `Gateway is deployed but port-forward not running. Start it with:\nkubectl port-forward -n ark-system service/localhost-gateway-nginx ${port}:80`,
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Get setup instructions for fresh installations
|
|
65
|
+
*/
|
|
66
|
+
getSetupInstructions() {
|
|
67
|
+
return `
|
|
68
|
+
🔧 ARK Gateway Setup Required:
|
|
69
|
+
|
|
70
|
+
To enable service discovery, you need to install the localhost-gateway:
|
|
71
|
+
|
|
72
|
+
1. From your agents-at-scale project root, run:
|
|
73
|
+
make localhost-gateway-install
|
|
74
|
+
|
|
75
|
+
2. This will:
|
|
76
|
+
- Install Gateway API CRDs
|
|
77
|
+
- Deploy NGINX Gateway Fabric
|
|
78
|
+
- Set up port-forwarding to localhost:8080
|
|
79
|
+
|
|
80
|
+
3. Then run 'ark check status' again
|
|
81
|
+
|
|
82
|
+
For more info: docs/content/ark-101/ark-gateway.mdx
|
|
83
|
+
`;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import * as k8s from '@kubernetes/client-node';
|
|
2
|
+
import { KubernetesConfig } from './types.js';
|
|
3
|
+
export declare class KubernetesConfigManager {
|
|
4
|
+
private kc;
|
|
5
|
+
constructor();
|
|
6
|
+
/**
|
|
7
|
+
* Get the KubeConfig instance for API client creation
|
|
8
|
+
*/
|
|
9
|
+
getKubeConfig(): k8s.KubeConfig;
|
|
10
|
+
/**
|
|
11
|
+
* Initialize Kubernetes configuration similar to fark's approach
|
|
12
|
+
* Priority: in-cluster config > KUBECONFIG env > ~/.kube/config
|
|
13
|
+
*/
|
|
14
|
+
initializeConfig(): Promise<KubernetesConfig>;
|
|
15
|
+
/**
|
|
16
|
+
* Get the API server URL for the current cluster
|
|
17
|
+
*/
|
|
18
|
+
getClusterApiUrl(): string;
|
|
19
|
+
/**
|
|
20
|
+
* Detect ark-api service URL in the cluster
|
|
21
|
+
* This mimics how fark discovers services
|
|
22
|
+
*/
|
|
23
|
+
getArkApiUrl(namespace?: string): Promise<string>;
|
|
24
|
+
/**
|
|
25
|
+
* Check if we can access the cluster
|
|
26
|
+
*/
|
|
27
|
+
testClusterAccess(): Promise<boolean>;
|
|
28
|
+
}
|