@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,368 @@
1
+ import fs from 'fs';
2
+ import path from 'path';
3
+ import chalk from 'chalk';
4
+ import { SecurityUtils } from '../../lib/security.js';
5
+ import { TemplateError } from '../../lib/errors.js';
6
+ export class TemplateEngine {
7
+ constructor() {
8
+ this.variables = {};
9
+ }
10
+ /**
11
+ * Set template variables for substitution
12
+ */
13
+ setVariables(variables) {
14
+ this.variables = { ...this.variables, ...variables };
15
+ }
16
+ /**
17
+ * Get current template variables
18
+ */
19
+ getVariables() {
20
+ return { ...this.variables };
21
+ }
22
+ /**
23
+ * Process a template directory and copy it to destination
24
+ */
25
+ async processTemplate(templatePath, destinationPath, options = {}) {
26
+ const { skipIfExists = false, createDirectories = true, exclude = ['.git', 'node_modules', '.DS_Store'], include = [], } = options;
27
+ // Validate paths for security
28
+ SecurityUtils.validatePath(templatePath, 'template path');
29
+ SecurityUtils.validatePath(destinationPath, 'destination path');
30
+ if (!fs.existsSync(templatePath)) {
31
+ throw new TemplateError(`Template path does not exist: ${templatePath}`, templatePath, [
32
+ 'Check that the template directory exists',
33
+ 'Verify the template path is correct',
34
+ 'Ensure templates are properly installed',
35
+ ]);
36
+ }
37
+ if (createDirectories) {
38
+ await this.ensureDirectorySafe(destinationPath, destinationPath);
39
+ }
40
+ await this.copyDirectory(templatePath, destinationPath, {
41
+ skipIfExists,
42
+ exclude,
43
+ include,
44
+ baseDir: destinationPath,
45
+ });
46
+ }
47
+ /**
48
+ * Process a single template file
49
+ */
50
+ async processFile(templateFilePath, destinationFilePath, options = {}) {
51
+ const { skipIfExists = false, baseDir = process.cwd() } = options;
52
+ // Validate paths for security
53
+ SecurityUtils.validatePath(templateFilePath, 'template file path');
54
+ SecurityUtils.validatePath(destinationFilePath, 'destination file path');
55
+ SecurityUtils.validateOutputPath(destinationFilePath, baseDir);
56
+ if (skipIfExists && fs.existsSync(destinationFilePath)) {
57
+ console.log(chalk.yellow(`⏭️ Skipping existing file: ${destinationFilePath}`));
58
+ return;
59
+ }
60
+ let templateContent;
61
+ try {
62
+ templateContent = fs.readFileSync(templateFilePath, 'utf-8');
63
+ }
64
+ catch (error) {
65
+ throw new TemplateError(`Failed to read template file: ${templateFilePath}. ${error instanceof Error ? error.message : String(error)}`, templateFilePath, [
66
+ 'Check that the template file exists',
67
+ 'Verify file permissions',
68
+ 'Ensure the template path is correct',
69
+ ]);
70
+ }
71
+ // Validate template content for security
72
+ SecurityUtils.validateTemplateContent(templateContent, templateFilePath);
73
+ const processedContent = this.substituteVariables(templateContent);
74
+ // Ensure destination directory exists
75
+ const destinationDir = path.dirname(destinationFilePath);
76
+ await this.ensureDirectorySafe(destinationDir, baseDir);
77
+ // Write file securely
78
+ await SecurityUtils.writeFileSafe(destinationFilePath, processedContent, baseDir);
79
+ // Show important generated content with relative paths
80
+ if (this.isImportantContent(destinationFilePath)) {
81
+ const relativePath = this.getRelativePath(destinationFilePath);
82
+ console.log(chalk.green(`📝 ${relativePath}`));
83
+ }
84
+ }
85
+ /**
86
+ * Substitute template variables in content
87
+ */
88
+ substituteVariables(content) {
89
+ let result = content;
90
+ // Replace template variables in Golang template format {{ .Values.variableName }}
91
+ for (const [key, value] of Object.entries(this.variables)) {
92
+ // Escape regex metacharacters in key to prevent ReDoS attacks
93
+ const escapedKey = key.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
94
+ const regex = new RegExp(`{{\\s*\\.Values\\.${escapedKey}\\s*}}`, 'g');
95
+ result = result.replace(regex, String(value));
96
+ }
97
+ return result;
98
+ }
99
+ /**
100
+ * Substitute variables in file/directory names
101
+ */
102
+ substituteVariablesInPath(pathStr) {
103
+ let result = pathStr;
104
+ // Handle new .template.yaml naming convention
105
+ if (pathStr.endsWith('.template.yaml')) {
106
+ const templateName = path.basename(pathStr, '.template.yaml');
107
+ result = this.deriveDestinationFilename(templateName);
108
+ }
109
+ else {
110
+ // Use Golang template variable substitution for other files
111
+ for (const [key, value] of Object.entries(this.variables)) {
112
+ const regex = new RegExp(`{{\\s*\\.Values\\.${key}\\s*}}`, 'g');
113
+ result = result.replace(regex, String(value));
114
+ }
115
+ }
116
+ return result;
117
+ }
118
+ /**
119
+ * Derive destination filename from template name
120
+ */
121
+ deriveDestinationFilename(templateName) {
122
+ switch (templateName) {
123
+ case 'agent':
124
+ return `${this.variables.agentName || 'unnamed'}-agent.yaml`;
125
+ case 'team':
126
+ return `${this.variables.teamName || 'unnamed'}-team.yaml`;
127
+ case 'query':
128
+ return `${this.variables.queryName || 'unnamed'}-query.yaml`;
129
+ default: {
130
+ // For unknown template types, use the template name as-is with variables
131
+ let result = `${templateName}.yaml`;
132
+ for (const [key, value] of Object.entries(this.variables)) {
133
+ const regex = new RegExp(`{{\\s*\\.Values\\.${key}\\s*}}`, 'g');
134
+ result = result.replace(regex, String(value));
135
+ }
136
+ return result;
137
+ }
138
+ }
139
+ }
140
+ /**
141
+ * Copy directory recursively with template processing
142
+ */
143
+ async copyDirectory(sourcePath, destinationPath, options) {
144
+ const { baseDir = destinationPath } = options;
145
+ const entries = fs.readdirSync(sourcePath, { withFileTypes: true });
146
+ for (const entry of entries) {
147
+ if (!this.shouldProcessEntry(entry.name, options)) {
148
+ continue;
149
+ }
150
+ const sourceEntry = path.join(sourcePath, entry.name);
151
+ const destinationName = this.substituteVariablesInPath(entry.name);
152
+ const destinationEntry = path.join(destinationPath, destinationName);
153
+ if (entry.isDirectory()) {
154
+ await this.processDirectory(sourceEntry, destinationEntry, options, baseDir);
155
+ }
156
+ else {
157
+ await this.processFileEntry(sourceEntry, destinationEntry, destinationName, options, baseDir);
158
+ }
159
+ }
160
+ }
161
+ shouldProcessEntry(entryName, options) {
162
+ if (this.shouldExclude(entryName, options.exclude || [])) {
163
+ return false;
164
+ }
165
+ if (options.include &&
166
+ options.include.length > 0 &&
167
+ !this.shouldInclude(entryName, options.include)) {
168
+ return false;
169
+ }
170
+ return true;
171
+ }
172
+ async processDirectory(sourceEntry, destinationEntry, options, baseDir) {
173
+ await this.ensureDirectorySafe(destinationEntry, baseDir);
174
+ await this.copyDirectory(sourceEntry, destinationEntry, {
175
+ ...options,
176
+ baseDir,
177
+ });
178
+ }
179
+ async processFileEntry(sourceEntry, destinationEntry, destinationName, options, baseDir) {
180
+ const sanitizedName = SecurityUtils.sanitizeFileName(destinationName);
181
+ if (sanitizedName !== destinationName) {
182
+ console.warn(chalk.yellow(`⚠️ File name sanitized: "${destinationName}" → "${sanitizedName}"`));
183
+ return;
184
+ }
185
+ if (this.isTextFile(sourceEntry)) {
186
+ await this.processFile(sourceEntry, destinationEntry, {
187
+ skipIfExists: options.skipIfExists,
188
+ baseDir,
189
+ });
190
+ }
191
+ else if (options.skipIfExists && fs.existsSync(destinationEntry)) {
192
+ console.log(chalk.yellow(`⏭️ Skipping existing file: ${destinationEntry}`));
193
+ }
194
+ else {
195
+ SecurityUtils.validateOutputPath(destinationEntry, baseDir);
196
+ fs.copyFileSync(sourceEntry, destinationEntry);
197
+ if (this.isImportantContent(destinationEntry)) {
198
+ const relativePath = this.getRelativePath(destinationEntry);
199
+ console.log(chalk.green(`📋 ${relativePath}`));
200
+ }
201
+ }
202
+ }
203
+ /**
204
+ * Ensure a directory exists, creating it if necessary
205
+ */
206
+ async ensureDirectory(dirPath) {
207
+ if (!fs.existsSync(dirPath)) {
208
+ fs.mkdirSync(dirPath, { recursive: true });
209
+ }
210
+ }
211
+ /**
212
+ * Safely ensure a directory exists with security validation
213
+ */
214
+ async ensureDirectorySafe(dirPath, baseDir) {
215
+ SecurityUtils.validateOutputPath(dirPath, baseDir);
216
+ if (!fs.existsSync(dirPath)) {
217
+ await SecurityUtils.createDirectorySafe(dirPath, baseDir);
218
+ }
219
+ }
220
+ /**
221
+ * Check if a file should be excluded
222
+ */
223
+ shouldExclude(fileName, excludePatterns) {
224
+ return excludePatterns.some((pattern) => {
225
+ if (pattern.includes('*')) {
226
+ // Safe glob pattern matching - escape regex metacharacters except *
227
+ const escapedPattern = pattern
228
+ .replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
229
+ .replace(/\\\*/g, '.*');
230
+ const regex = new RegExp(escapedPattern);
231
+ return regex.test(fileName);
232
+ }
233
+ return fileName === pattern;
234
+ });
235
+ }
236
+ /**
237
+ * Check if a file should be included
238
+ */
239
+ shouldInclude(fileName, includePatterns) {
240
+ return includePatterns.some((pattern) => {
241
+ if (pattern.includes('*')) {
242
+ // Safe glob pattern matching - escape regex metacharacters except *
243
+ const escapedPattern = pattern
244
+ .replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
245
+ .replace(/\\\*/g, '.*');
246
+ const regex = new RegExp(escapedPattern);
247
+ return regex.test(fileName);
248
+ }
249
+ return fileName === pattern;
250
+ });
251
+ }
252
+ /**
253
+ * Check if a file is a text file that should have variable substitution
254
+ */
255
+ isTextFile(filePath) {
256
+ const textExtensions = [
257
+ '.txt',
258
+ '.md',
259
+ '.yaml',
260
+ '.yml',
261
+ '.json',
262
+ '.js',
263
+ '.ts',
264
+ '.jsx',
265
+ '.tsx',
266
+ '.py',
267
+ '.go',
268
+ '.rs',
269
+ '.java',
270
+ '.cs',
271
+ '.php',
272
+ '.rb',
273
+ '.sh',
274
+ '.bash',
275
+ '.zsh',
276
+ '.fish',
277
+ '.ps1',
278
+ '.bat',
279
+ '.cmd',
280
+ '.dockerfile',
281
+ '.docker',
282
+ '.makefile',
283
+ '.mk',
284
+ '.toml',
285
+ '.ini',
286
+ '.cfg',
287
+ '.conf',
288
+ '.properties',
289
+ '.xml',
290
+ '.html',
291
+ '.htm',
292
+ '.css',
293
+ '.scss',
294
+ '.sass',
295
+ '.less',
296
+ '.vue',
297
+ '.svelte',
298
+ '.sql',
299
+ '.r',
300
+ '.R',
301
+ '.swift',
302
+ '.kt',
303
+ '.scala',
304
+ '.clj',
305
+ '.hs',
306
+ '.elm',
307
+ '.ex',
308
+ '.exs',
309
+ '.erl',
310
+ '.pl',
311
+ '.pm',
312
+ '.raku',
313
+ '.lua',
314
+ ];
315
+ const extension = path.extname(filePath).toLowerCase();
316
+ if (textExtensions.includes(extension)) {
317
+ return true;
318
+ }
319
+ // Check for files without extensions that are commonly text
320
+ const basename = path.basename(filePath).toLowerCase();
321
+ const textBasenames = [
322
+ 'readme',
323
+ 'license',
324
+ 'changelog',
325
+ 'makefile',
326
+ 'dockerfile',
327
+ 'gitignore',
328
+ 'gitattributes',
329
+ 'editorconfig',
330
+ 'eslintrc',
331
+ 'prettierrc',
332
+ ];
333
+ return textBasenames.some((name) => basename.includes(name));
334
+ }
335
+ /**
336
+ * Check if a file path represents important generated content
337
+ */
338
+ isImportantContent(filePath) {
339
+ const normalizedPath = path.normalize(filePath);
340
+ // Show files in important directories (agents, teams, queries, models)
341
+ const importantDirs = ['agents', 'teams', 'queries', 'models'];
342
+ const hasImportantDir = importantDirs.some((dir) => normalizedPath.includes(`${path.sep}${dir}${path.sep}`) ||
343
+ normalizedPath.includes(`/${dir}/`));
344
+ if (hasImportantDir) {
345
+ // Skip .keep files
346
+ return !path.basename(filePath).includes('.keep');
347
+ }
348
+ return false;
349
+ }
350
+ /**
351
+ * Get relative path from a full file path for display
352
+ */
353
+ getRelativePath(filePath) {
354
+ const normalizedPath = path.normalize(filePath);
355
+ // Find the project root by looking for common patterns
356
+ const pathParts = normalizedPath.split(path.sep);
357
+ // Look for agents, teams, queries, or models directory
358
+ const importantDirs = ['agents', 'teams', 'queries', 'models'];
359
+ for (let i = pathParts.length - 1; i >= 0; i--) {
360
+ if (importantDirs.includes(pathParts[i])) {
361
+ // Return from this directory onwards
362
+ return pathParts.slice(i).join('/');
363
+ }
364
+ }
365
+ // Fallback to just filename
366
+ return path.basename(filePath);
367
+ }
368
+ }
@@ -0,0 +1,35 @@
1
+ /**
2
+ * Utilities for name validation and normalization
3
+ */
4
+ /**
5
+ * Convert a string to lowercase kebab-case
6
+ * Examples:
7
+ * - "My Project" -> "my-project"
8
+ * - "MyProject" -> "my-project"
9
+ * - "my_project" -> "my-project"
10
+ * - "myProject123" -> "my-project123"
11
+ */
12
+ export declare function toKebabCase(str: string): string;
13
+ /**
14
+ * Validate that a name follows kebab-case format
15
+ */
16
+ export declare function isValidKebabCase(str: string): boolean;
17
+ /**
18
+ * Validate that a name is suitable for Kubernetes resources
19
+ */
20
+ export declare function isValidKubernetesName(str: string): boolean;
21
+ /**
22
+ * Get validation error message for invalid names
23
+ */
24
+ export declare function getNameValidationError(name: string): string | null;
25
+ /**
26
+ * Strict name validation that throws ValidationError with suggestions
27
+ */
28
+ export declare function validateNameStrict(name: string, type?: string): void;
29
+ /**
30
+ * Normalize and validate a name for use in generators
31
+ */
32
+ export declare function normalizeAndValidateName(input: string, type?: string): {
33
+ name: string;
34
+ wasTransformed: boolean;
35
+ };
@@ -0,0 +1,110 @@
1
+ /**
2
+ * Utilities for name validation and normalization
3
+ */
4
+ import { ValidationError } from '../../../lib/errors.js';
5
+ /**
6
+ * Convert a string to lowercase kebab-case
7
+ * Examples:
8
+ * - "My Project" -> "my-project"
9
+ * - "MyProject" -> "my-project"
10
+ * - "my_project" -> "my-project"
11
+ * - "myProject123" -> "my-project123"
12
+ */
13
+ export function toKebabCase(str) {
14
+ if (str.length > 1000) {
15
+ throw new Error('Input string too long for processing');
16
+ }
17
+ let result = str
18
+ .trim()
19
+ // Replace spaces and underscores with hyphens
20
+ .replace(/[\s_]+/g, '-')
21
+ // Insert hyphens before uppercase letters (camelCase -> kebab-case)
22
+ .replace(/([a-z])([A-Z])/g, '$1-$2')
23
+ // Convert to lowercase
24
+ .toLowerCase()
25
+ // Remove any double hyphens
26
+ .replace(/-+/g, '-');
27
+ // Remove leading/trailing hyphens using string methods to avoid regex backtracking
28
+ while (result.startsWith('-')) {
29
+ result = result.slice(1);
30
+ }
31
+ while (result.endsWith('-')) {
32
+ result = result.slice(0, -1);
33
+ }
34
+ return result;
35
+ }
36
+ /**
37
+ * Validate that a name follows kebab-case format
38
+ */
39
+ export function isValidKebabCase(str) {
40
+ // Must be lowercase, can contain letters, numbers, and hyphens
41
+ // Cannot start or end with hyphen, cannot have consecutive hyphens
42
+ const kebabRegex = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
43
+ return kebabRegex.test(str);
44
+ }
45
+ /**
46
+ * Validate that a name is suitable for Kubernetes resources
47
+ */
48
+ export function isValidKubernetesName(str) {
49
+ // Kubernetes names must be lowercase alphanumeric or '-'
50
+ // Must start and end with alphanumeric character
51
+ // Max length 63 characters
52
+ return (isValidKebabCase(str) &&
53
+ str.length <= 63 &&
54
+ str.length >= 1 &&
55
+ /^[a-z0-9]/.test(str) &&
56
+ /[a-z0-9]$/.test(str));
57
+ }
58
+ /**
59
+ * Get validation error message for invalid names
60
+ */
61
+ export function getNameValidationError(name) {
62
+ try {
63
+ validateNameStrict(name);
64
+ return null;
65
+ }
66
+ catch (error) {
67
+ if (error instanceof ValidationError) {
68
+ return error.message;
69
+ }
70
+ return error instanceof Error ? error.message : String(error);
71
+ }
72
+ }
73
+ /**
74
+ * Strict name validation that throws ValidationError with suggestions
75
+ */
76
+ export function validateNameStrict(name, type = 'name') {
77
+ if (!name || name.trim().length === 0) {
78
+ throw new ValidationError(`${type} cannot be empty`, 'name', [
79
+ `Provide a valid ${type}`,
80
+ ]);
81
+ }
82
+ const trimmed = name.trim();
83
+ if (trimmed.length > 63) {
84
+ throw new ValidationError(`${type} must be 63 characters or less (got ${trimmed.length})`, 'name', [`Shorten the ${type} to 63 characters or less`]);
85
+ }
86
+ if (!isValidKubernetesName(trimmed)) {
87
+ const suggested = toKebabCase(trimmed);
88
+ const suggestions = [];
89
+ if (suggested !== trimmed && isValidKubernetesName(suggested)) {
90
+ suggestions.push(`Try: "${suggested}"`);
91
+ }
92
+ suggestions.push(`${type} must be lowercase letters, numbers, and hyphens only`);
93
+ suggestions.push(`${type} cannot start or end with a hyphen`);
94
+ suggestions.push(`${type} cannot contain consecutive hyphens`);
95
+ throw new ValidationError(`Invalid ${type}: "${trimmed}"`, 'name', suggestions);
96
+ }
97
+ }
98
+ /**
99
+ * Normalize and validate a name for use in generators
100
+ */
101
+ export function normalizeAndValidateName(input, type = 'name') {
102
+ const original = input.trim();
103
+ const normalized = toKebabCase(original);
104
+ // Validate the normalized name
105
+ validateNameStrict(normalized, type);
106
+ return {
107
+ name: normalized,
108
+ wasTransformed: normalized !== original,
109
+ };
110
+ }
@@ -0,0 +1,28 @@
1
+ export interface ProjectValidationResult {
2
+ isValid: boolean;
3
+ projectName?: string;
4
+ error?: string;
5
+ }
6
+ /**
7
+ * Validate if the given directory is a valid ARK project
8
+ */
9
+ export declare function validateProjectStructure(projectDir: string): ProjectValidationResult;
10
+ /**
11
+ * Validate project structure and throw detailed error if invalid
12
+ */
13
+ export declare function validateProjectStructureStrict(projectDir: string): string;
14
+ /**
15
+ * Get the current project directory (current working directory)
16
+ */
17
+ export declare function getCurrentProjectDirectory(): string;
18
+ /**
19
+ * Check if the current directory is a valid ARK project
20
+ */
21
+ export declare function validateCurrentProject(): ProjectValidationResult;
22
+ /**
23
+ * Get current project info with strict validation
24
+ */
25
+ export declare function getCurrentProjectInfo(): {
26
+ projectName: string;
27
+ projectDir: string;
28
+ };
@@ -0,0 +1,133 @@
1
+ import fs from 'fs';
2
+ import path from 'path';
3
+ import { ProjectStructureError } from '../../../lib/errors.js';
4
+ /**
5
+ * Validate if the given directory is a valid ARK project
6
+ */
7
+ export function validateProjectStructure(projectDir) {
8
+ // Check for required project files
9
+ const chartYamlPath = path.join(projectDir, 'Chart.yaml');
10
+ const agentsDir = path.join(projectDir, 'agents');
11
+ if (!fs.existsSync(chartYamlPath)) {
12
+ return {
13
+ isValid: false,
14
+ error: 'Chart.yaml not found. This does not appear to be a valid ARK project directory.',
15
+ };
16
+ }
17
+ if (!fs.existsSync(agentsDir)) {
18
+ return {
19
+ isValid: false,
20
+ error: 'agents/ directory not found. This does not appear to be a valid ARK project directory.',
21
+ };
22
+ }
23
+ // Try to extract project name from Chart.yaml
24
+ try {
25
+ const chartContent = fs.readFileSync(chartYamlPath, 'utf-8');
26
+ const nameMatch = /^name:\s*([a-zA-Z0-9\s._-]{1,200}?)$/m.exec(chartContent);
27
+ if (nameMatch) {
28
+ return {
29
+ isValid: true,
30
+ projectName: nameMatch[1].trim(),
31
+ };
32
+ }
33
+ }
34
+ catch (error) {
35
+ return {
36
+ isValid: false,
37
+ error: `Failed to read Chart.yaml: ${error}`,
38
+ };
39
+ }
40
+ return {
41
+ isValid: true,
42
+ };
43
+ }
44
+ /**
45
+ * Check file/directory validation
46
+ */
47
+ function validateRequiredPath(projectDir, required, missing, wrongType) {
48
+ const fullPath = path.join(projectDir, required.path);
49
+ if (!fs.existsSync(fullPath)) {
50
+ missing.push(required.path);
51
+ return;
52
+ }
53
+ const stat = fs.statSync(fullPath);
54
+ if (required.type === 'file' && !stat.isFile()) {
55
+ wrongType.push(`${required.path} (expected file, found directory)`);
56
+ }
57
+ else if (required.type === 'directory' && !stat.isDirectory()) {
58
+ wrongType.push(`${required.path} (expected directory, found file)`);
59
+ }
60
+ }
61
+ /**
62
+ * Extract project name from Chart.yaml
63
+ */
64
+ function extractProjectName(projectDir) {
65
+ const chartYamlPath = path.join(projectDir, 'Chart.yaml');
66
+ const chartContent = fs.readFileSync(chartYamlPath, 'utf-8');
67
+ const nameMatch = /^name:\s*([a-zA-Z0-9\s._-]{1,200}?)$/m.exec(chartContent);
68
+ if (nameMatch) {
69
+ return nameMatch[1].trim();
70
+ }
71
+ throw new Error('No name field found in Chart.yaml');
72
+ }
73
+ /**
74
+ * Validate project structure and throw detailed error if invalid
75
+ */
76
+ export function validateProjectStructureStrict(projectDir) {
77
+ const requiredFiles = [
78
+ { path: 'Chart.yaml', type: 'file' },
79
+ { path: 'agents', type: 'directory' },
80
+ { path: 'teams', type: 'directory' },
81
+ { path: 'queries', type: 'directory' },
82
+ { path: 'models', type: 'directory' },
83
+ ];
84
+ const missing = [];
85
+ const wrongType = [];
86
+ for (const required of requiredFiles) {
87
+ validateRequiredPath(projectDir, required, missing, wrongType);
88
+ }
89
+ if (missing.length > 0 || wrongType.length > 0) {
90
+ const issues = [];
91
+ if (missing.length > 0) {
92
+ issues.push(`Missing: ${missing.join(', ')}`);
93
+ }
94
+ if (wrongType.length > 0) {
95
+ issues.push(`Wrong type: ${wrongType.join(', ')}`);
96
+ }
97
+ throw new ProjectStructureError(`Invalid ARK project structure: ${issues.join('; ')}`, projectDir, [
98
+ 'Ensure you are in a valid ARK project directory',
99
+ 'Run "ark generate project" to create a new project',
100
+ 'Check that all required files and directories exist',
101
+ ]);
102
+ }
103
+ try {
104
+ return extractProjectName(projectDir);
105
+ }
106
+ catch (error) {
107
+ throw new ProjectStructureError(`Failed to read project name from Chart.yaml: ${error instanceof Error ? error.message : String(error)}`, projectDir, [
108
+ 'Ensure Chart.yaml exists and is readable',
109
+ 'Check that Chart.yaml contains a valid name field',
110
+ 'Verify file permissions',
111
+ ]);
112
+ }
113
+ }
114
+ /**
115
+ * Get the current project directory (current working directory)
116
+ */
117
+ export function getCurrentProjectDirectory() {
118
+ return process.cwd();
119
+ }
120
+ /**
121
+ * Check if the current directory is a valid ARK project
122
+ */
123
+ export function validateCurrentProject() {
124
+ return validateProjectStructure(getCurrentProjectDirectory());
125
+ }
126
+ /**
127
+ * Get current project info with strict validation
128
+ */
129
+ export function getCurrentProjectInfo() {
130
+ const projectDir = getCurrentProjectDirectory();
131
+ const projectName = validateProjectStructureStrict(projectDir);
132
+ return { projectName, projectDir };
133
+ }
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env node
2
+ declare const DashboardCLI: () => import("react/jsx-runtime").JSX.Element;
3
+ export default DashboardCLI;