@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.
Files changed (76) hide show
  1. package/README.md +95 -0
  2. package/dist/commands/cluster/get-ip.d.ts +2 -0
  3. package/dist/commands/cluster/get-ip.js +32 -0
  4. package/dist/commands/cluster/get-type.d.ts +2 -0
  5. package/dist/commands/cluster/get-type.js +26 -0
  6. package/dist/commands/cluster/index.d.ts +2 -0
  7. package/dist/commands/cluster/index.js +10 -0
  8. package/dist/commands/completion.d.ts +2 -0
  9. package/dist/commands/completion.js +108 -0
  10. package/dist/commands/config.d.ts +5 -0
  11. package/dist/commands/config.js +327 -0
  12. package/dist/commands/generate/config.d.ts +145 -0
  13. package/dist/commands/generate/config.js +253 -0
  14. package/dist/commands/generate/generators/agent.d.ts +2 -0
  15. package/dist/commands/generate/generators/agent.js +156 -0
  16. package/dist/commands/generate/generators/index.d.ts +6 -0
  17. package/dist/commands/generate/generators/index.js +6 -0
  18. package/dist/commands/generate/generators/marketplace.d.ts +2 -0
  19. package/dist/commands/generate/generators/marketplace.js +304 -0
  20. package/dist/commands/generate/generators/mcpserver.d.ts +25 -0
  21. package/dist/commands/generate/generators/mcpserver.js +350 -0
  22. package/dist/commands/generate/generators/project.d.ts +2 -0
  23. package/dist/commands/generate/generators/project.js +784 -0
  24. package/dist/commands/generate/generators/query.d.ts +2 -0
  25. package/dist/commands/generate/generators/query.js +213 -0
  26. package/dist/commands/generate/generators/team.d.ts +2 -0
  27. package/dist/commands/generate/generators/team.js +407 -0
  28. package/dist/commands/generate/index.d.ts +24 -0
  29. package/dist/commands/generate/index.js +357 -0
  30. package/dist/commands/generate/templateDiscovery.d.ts +30 -0
  31. package/dist/commands/generate/templateDiscovery.js +94 -0
  32. package/dist/commands/generate/templateEngine.d.ts +78 -0
  33. package/dist/commands/generate/templateEngine.js +368 -0
  34. package/dist/commands/generate/utils/nameUtils.d.ts +35 -0
  35. package/dist/commands/generate/utils/nameUtils.js +110 -0
  36. package/dist/commands/generate/utils/projectUtils.d.ts +28 -0
  37. package/dist/commands/generate/utils/projectUtils.js +133 -0
  38. package/dist/components/DashboardCLI.d.ts +3 -0
  39. package/dist/components/DashboardCLI.js +149 -0
  40. package/dist/components/GeneratorUI.d.ts +3 -0
  41. package/dist/components/GeneratorUI.js +167 -0
  42. package/dist/components/statusChecker.d.ts +48 -0
  43. package/dist/components/statusChecker.js +251 -0
  44. package/dist/config.d.ts +42 -0
  45. package/dist/config.js +243 -0
  46. package/dist/index.d.ts +2 -0
  47. package/dist/index.js +67 -0
  48. package/dist/lib/arkClient.d.ts +32 -0
  49. package/dist/lib/arkClient.js +43 -0
  50. package/dist/lib/cluster.d.ts +8 -0
  51. package/dist/lib/cluster.js +134 -0
  52. package/dist/lib/config.d.ts +82 -0
  53. package/dist/lib/config.js +223 -0
  54. package/dist/lib/consts.d.ts +10 -0
  55. package/dist/lib/consts.js +15 -0
  56. package/dist/lib/errors.d.ts +56 -0
  57. package/dist/lib/errors.js +208 -0
  58. package/dist/lib/exec.d.ts +5 -0
  59. package/dist/lib/exec.js +20 -0
  60. package/dist/lib/gatewayManager.d.ts +24 -0
  61. package/dist/lib/gatewayManager.js +85 -0
  62. package/dist/lib/kubernetes.d.ts +28 -0
  63. package/dist/lib/kubernetes.js +122 -0
  64. package/dist/lib/progress.d.ts +128 -0
  65. package/dist/lib/progress.js +273 -0
  66. package/dist/lib/security.d.ts +37 -0
  67. package/dist/lib/security.js +295 -0
  68. package/dist/lib/types.d.ts +37 -0
  69. package/dist/lib/types.js +1 -0
  70. package/dist/lib/wrappers/git.d.ts +2 -0
  71. package/dist/lib/wrappers/git.js +43 -0
  72. package/dist/ui/MainMenu.d.ts +3 -0
  73. package/dist/ui/MainMenu.js +116 -0
  74. package/dist/ui/statusFormatter.d.ts +9 -0
  75. package/dist/ui/statusFormatter.js +47 -0
  76. 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
+ }
@@ -0,0 +1,5 @@
1
+ export declare function executeCommand(command: string, args?: string[]): Promise<{
2
+ stdout: string;
3
+ stderr: string;
4
+ }>;
5
+ export declare function fileExists(path: string): boolean;
@@ -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
+ }