@agents-at-scale/ark 0.1.34 → 0.1.35-rc1

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 (205) hide show
  1. package/dist/arkServices.d.ts +42 -0
  2. package/dist/arkServices.js +138 -0
  3. package/dist/arkServices.spec.d.ts +1 -0
  4. package/dist/arkServices.spec.js +24 -0
  5. package/dist/charts/charts.d.ts +5 -0
  6. package/dist/charts/charts.js +6 -0
  7. package/dist/charts/dependencies.d.ts +6 -0
  8. package/dist/charts/dependencies.js +50 -0
  9. package/dist/charts/types.d.ts +40 -0
  10. package/dist/charts/types.js +1 -0
  11. package/dist/commands/agents/index.d.ts +3 -0
  12. package/dist/commands/agents/index.js +51 -0
  13. package/dist/commands/agents/index.spec.d.ts +1 -0
  14. package/dist/commands/agents/index.spec.js +67 -0
  15. package/dist/commands/agents/selector.d.ts +8 -0
  16. package/dist/commands/agents/selector.js +53 -0
  17. package/dist/commands/agents.d.ts +2 -0
  18. package/dist/commands/agents.js +53 -0
  19. package/dist/commands/chat/index.d.ts +3 -0
  20. package/dist/commands/chat/index.js +29 -0
  21. package/dist/commands/chat.d.ts +2 -0
  22. package/dist/commands/chat.js +45 -0
  23. package/dist/commands/cluster/get.d.ts +2 -0
  24. package/dist/commands/cluster/get.js +39 -0
  25. package/dist/commands/cluster/get.spec.d.ts +1 -0
  26. package/dist/commands/cluster/get.spec.js +92 -0
  27. package/dist/commands/cluster/index.d.ts +2 -1
  28. package/dist/commands/cluster/index.js +3 -5
  29. package/dist/commands/cluster/index.spec.d.ts +1 -0
  30. package/dist/commands/cluster/index.spec.js +24 -0
  31. package/dist/commands/completion/index.d.ts +3 -0
  32. package/dist/commands/completion/index.js +268 -0
  33. package/dist/commands/completion/index.spec.d.ts +1 -0
  34. package/dist/commands/completion/index.spec.js +34 -0
  35. package/dist/commands/completion.js +159 -2
  36. package/dist/commands/config/index.d.ts +3 -0
  37. package/dist/commands/config/index.js +42 -0
  38. package/dist/commands/config/index.spec.d.ts +1 -0
  39. package/dist/commands/config/index.spec.js +78 -0
  40. package/dist/commands/config.d.ts +0 -3
  41. package/dist/commands/config.js +38 -321
  42. package/dist/commands/dashboard/index.d.ts +4 -0
  43. package/dist/commands/dashboard/index.js +39 -0
  44. package/dist/commands/dashboard.d.ts +3 -0
  45. package/dist/commands/dashboard.js +39 -0
  46. package/dist/commands/dev/index.d.ts +3 -0
  47. package/dist/commands/dev/index.js +9 -0
  48. package/dist/commands/dev/tool/check.d.ts +2 -0
  49. package/dist/commands/dev/tool/check.js +142 -0
  50. package/dist/commands/dev/tool/clean.d.ts +2 -0
  51. package/dist/commands/dev/tool/clean.js +153 -0
  52. package/dist/commands/dev/tool/generate.d.ts +2 -0
  53. package/dist/commands/dev/tool/generate.js +28 -0
  54. package/dist/commands/dev/tool/index.d.ts +2 -0
  55. package/dist/commands/dev/tool/index.js +14 -0
  56. package/dist/commands/dev/tool/init.d.ts +2 -0
  57. package/dist/commands/dev/tool/init.js +320 -0
  58. package/dist/commands/dev/tool/shared.d.ts +5 -0
  59. package/dist/commands/dev/tool/shared.js +256 -0
  60. package/dist/commands/dev/tool/status.d.ts +2 -0
  61. package/dist/commands/dev/tool/status.js +136 -0
  62. package/dist/commands/dev/tool-generate.spec.d.ts +1 -0
  63. package/dist/commands/dev/tool-generate.spec.js +163 -0
  64. package/dist/commands/dev/tool.d.ts +2 -0
  65. package/dist/commands/dev/tool.js +559 -0
  66. package/dist/commands/dev/tool.spec.d.ts +1 -0
  67. package/dist/commands/dev/tool.spec.js +48 -0
  68. package/dist/commands/generate/config.js +5 -24
  69. package/dist/commands/generate/generators/mcpserver.d.ts +2 -1
  70. package/dist/commands/generate/generators/mcpserver.js +26 -5
  71. package/dist/commands/generate/generators/project.js +22 -41
  72. package/dist/commands/generate/index.d.ts +2 -1
  73. package/dist/commands/generate/index.js +1 -1
  74. package/dist/commands/install/index.d.ts +8 -0
  75. package/dist/commands/install/index.js +302 -0
  76. package/dist/commands/install/index.spec.d.ts +1 -0
  77. package/dist/commands/install/index.spec.js +135 -0
  78. package/dist/commands/install.d.ts +3 -0
  79. package/dist/commands/install.js +147 -0
  80. package/dist/commands/models/create.d.ts +1 -0
  81. package/dist/commands/models/create.js +213 -0
  82. package/dist/commands/models/create.spec.d.ts +1 -0
  83. package/dist/commands/models/create.spec.js +125 -0
  84. package/dist/commands/models/index.d.ts +3 -0
  85. package/dist/commands/models/index.js +60 -0
  86. package/dist/commands/models/index.spec.d.ts +1 -0
  87. package/dist/commands/models/index.spec.js +76 -0
  88. package/dist/commands/models/selector.d.ts +8 -0
  89. package/dist/commands/models/selector.js +53 -0
  90. package/dist/commands/routes/index.d.ts +3 -0
  91. package/dist/commands/routes/index.js +93 -0
  92. package/dist/commands/routes.d.ts +2 -0
  93. package/dist/commands/routes.js +101 -0
  94. package/dist/commands/status/index.d.ts +4 -0
  95. package/dist/commands/status/index.js +232 -0
  96. package/dist/commands/status.d.ts +3 -0
  97. package/dist/commands/status.js +33 -0
  98. package/dist/commands/targets/index.d.ts +3 -0
  99. package/dist/commands/targets/index.js +65 -0
  100. package/dist/commands/targets/index.spec.d.ts +1 -0
  101. package/dist/commands/targets/index.spec.js +105 -0
  102. package/dist/commands/targets.d.ts +2 -0
  103. package/dist/commands/targets.js +65 -0
  104. package/dist/commands/teams/index.d.ts +3 -0
  105. package/dist/commands/teams/index.js +49 -0
  106. package/dist/commands/teams/index.spec.d.ts +1 -0
  107. package/dist/commands/teams/index.spec.js +70 -0
  108. package/dist/commands/teams/selector.d.ts +8 -0
  109. package/dist/commands/teams/selector.js +55 -0
  110. package/dist/commands/tools/index.d.ts +3 -0
  111. package/dist/commands/tools/index.js +49 -0
  112. package/dist/commands/tools/index.spec.d.ts +1 -0
  113. package/dist/commands/tools/index.spec.js +70 -0
  114. package/dist/commands/tools/selector.d.ts +8 -0
  115. package/dist/commands/tools/selector.js +53 -0
  116. package/dist/commands/uninstall/index.d.ts +3 -0
  117. package/dist/commands/uninstall/index.js +107 -0
  118. package/dist/commands/uninstall/index.spec.d.ts +1 -0
  119. package/dist/commands/uninstall/index.spec.js +117 -0
  120. package/dist/commands/uninstall.d.ts +2 -0
  121. package/dist/commands/uninstall.js +83 -0
  122. package/dist/components/ChatUI.d.ts +16 -0
  123. package/dist/components/ChatUI.js +801 -0
  124. package/dist/components/StatusView.d.ts +10 -0
  125. package/dist/components/StatusView.js +39 -0
  126. package/dist/components/statusChecker.d.ts +13 -23
  127. package/dist/components/statusChecker.js +275 -129
  128. package/dist/config.d.ts +3 -22
  129. package/dist/config.js +10 -161
  130. package/dist/index.d.ts +1 -1
  131. package/dist/index.js +40 -42
  132. package/dist/lib/arkApiClient.d.ts +53 -0
  133. package/dist/lib/arkApiClient.js +102 -0
  134. package/dist/lib/arkApiProxy.d.ts +9 -0
  135. package/dist/lib/arkApiProxy.js +22 -0
  136. package/dist/lib/arkServiceProxy.d.ts +14 -0
  137. package/dist/lib/arkServiceProxy.js +95 -0
  138. package/dist/lib/arkStatus.d.ts +10 -0
  139. package/dist/lib/arkStatus.js +79 -0
  140. package/dist/lib/arkStatus.spec.d.ts +1 -0
  141. package/dist/lib/arkStatus.spec.js +49 -0
  142. package/dist/lib/chatClient.d.ts +33 -0
  143. package/dist/lib/chatClient.js +99 -0
  144. package/dist/lib/cluster.d.ts +2 -1
  145. package/dist/lib/cluster.js +37 -16
  146. package/dist/lib/cluster.spec.d.ts +1 -0
  147. package/dist/lib/cluster.spec.js +338 -0
  148. package/dist/lib/commandUtils.d.ts +4 -0
  149. package/dist/lib/commandUtils.js +18 -0
  150. package/dist/lib/commandUtils.test.d.ts +1 -0
  151. package/dist/lib/commandUtils.test.js +44 -0
  152. package/dist/lib/commands.d.ts +16 -0
  153. package/dist/lib/commands.js +29 -0
  154. package/dist/lib/commands.spec.d.ts +1 -0
  155. package/dist/lib/commands.spec.js +146 -0
  156. package/dist/lib/config.d.ts +26 -80
  157. package/dist/lib/config.js +70 -205
  158. package/dist/lib/config.spec.d.ts +1 -0
  159. package/dist/lib/config.spec.js +99 -0
  160. package/dist/lib/config.test.d.ts +1 -0
  161. package/dist/lib/config.test.js +93 -0
  162. package/dist/lib/consts.d.ts +0 -1
  163. package/dist/lib/consts.js +0 -2
  164. package/dist/lib/consts.spec.d.ts +1 -0
  165. package/dist/lib/consts.spec.js +15 -0
  166. package/dist/lib/dev/tools/analyzer.d.ts +30 -0
  167. package/dist/lib/dev/tools/analyzer.js +190 -0
  168. package/dist/lib/dev/tools/discover_tools.py +392 -0
  169. package/dist/lib/dev/tools/mcp-types.d.ts +28 -0
  170. package/dist/lib/dev/tools/mcp-types.js +86 -0
  171. package/dist/lib/dev/tools/types.d.ts +50 -0
  172. package/dist/lib/dev/tools/types.js +1 -0
  173. package/dist/lib/errors.js +1 -1
  174. package/dist/lib/errors.spec.d.ts +1 -0
  175. package/dist/lib/errors.spec.js +221 -0
  176. package/dist/lib/exec.d.ts +0 -4
  177. package/dist/lib/exec.js +0 -11
  178. package/dist/lib/output.d.ts +36 -0
  179. package/dist/lib/output.js +89 -0
  180. package/dist/lib/output.spec.d.ts +1 -0
  181. package/dist/lib/output.spec.js +123 -0
  182. package/dist/lib/portUtils.d.ts +8 -0
  183. package/dist/lib/portUtils.js +39 -0
  184. package/dist/lib/startup.d.ts +5 -0
  185. package/dist/lib/startup.js +73 -0
  186. package/dist/lib/startup.spec.d.ts +1 -0
  187. package/dist/lib/startup.spec.js +168 -0
  188. package/dist/lib/types.d.ts +10 -3
  189. package/dist/types/types.d.ts +40 -0
  190. package/dist/types/types.js +1 -0
  191. package/dist/ui/AgentSelector.d.ts +8 -0
  192. package/dist/ui/AgentSelector.js +53 -0
  193. package/dist/ui/MainMenu.d.ts +5 -1
  194. package/dist/ui/MainMenu.js +222 -91
  195. package/dist/ui/ModelSelector.d.ts +8 -0
  196. package/dist/ui/ModelSelector.js +53 -0
  197. package/dist/ui/TeamSelector.d.ts +8 -0
  198. package/dist/ui/TeamSelector.js +55 -0
  199. package/dist/ui/ToolSelector.d.ts +8 -0
  200. package/dist/ui/ToolSelector.js +53 -0
  201. package/dist/ui/statusFormatter.d.ts +22 -7
  202. package/dist/ui/statusFormatter.js +39 -39
  203. package/dist/ui/statusFormatter.spec.d.ts +1 -0
  204. package/dist/ui/statusFormatter.spec.js +58 -0
  205. package/package.json +17 -5
@@ -159,7 +159,7 @@ class McpServerGenerator {
159
159
  // Get tool definitions
160
160
  const tools = await this.getToolDefinitions();
161
161
  // Check if destination exists
162
- if (fs.existsSync(answers.destination)) {
162
+ if (answers.destination && fs.existsSync(answers.destination)) {
163
163
  const overwrite = await inquirer.prompt([
164
164
  {
165
165
  type: 'confirm',
@@ -174,9 +174,18 @@ class McpServerGenerator {
174
174
  }
175
175
  }
176
176
  return {
177
- ...answers,
177
+ mcpServerName: answers.mcpServerName || name,
178
+ description: answers.description || '',
179
+ technology: answers.technology || 'node',
180
+ packageSource: answers.packageSource || 'local',
181
+ packageName: answers.packageName,
182
+ destination: answers.destination || defaultDestination,
183
+ requiresAuth: answers.requiresAuth || false,
184
+ hasCustomConfig: answers.hasCustomConfig || false,
185
+ maintainerName: answers.maintainerName || '',
186
+ homeUrl: answers.homeUrl,
178
187
  tools,
179
- packageManager: this.getPackageManager(answers.technology),
188
+ packageManager: this.getPackageManager(answers.technology || 'node'),
180
189
  sourceUrls: answers.homeUrl ? [answers.homeUrl] : [],
181
190
  sampleQuery: this.generateSampleQuery(tools),
182
191
  };
@@ -307,7 +316,19 @@ class McpServerGenerator {
307
316
  }
308
317
  // Generate from template
309
318
  // Set template variables using the same structure expected by templates
310
- this.templateEngine.setVariables(config);
319
+ // Convert config to template variables format
320
+ const templateVars = {};
321
+ Object.entries(config).forEach(([key, value]) => {
322
+ if (typeof value === 'string' ||
323
+ typeof value === 'number' ||
324
+ typeof value === 'boolean') {
325
+ templateVars[key] = value;
326
+ }
327
+ else {
328
+ templateVars[key] = JSON.stringify(value);
329
+ }
330
+ });
331
+ this.templateEngine.setVariables(templateVars);
311
332
  await this.templateEngine.processTemplate(templatePath, config.destination);
312
333
  // Make build script executable (owner only for security)
313
334
  const buildScriptPath = path.join(config.destination, 'build.sh');
@@ -317,7 +338,7 @@ class McpServerGenerator {
317
338
  console.log(chalk.green(`✅ MCP server generated successfully at ${config.destination}`));
318
339
  }
319
340
  catch (error) {
320
- console.error(chalk.red('Failed to generate MCP server:'), error.message);
341
+ console.error(chalk.red('Failed to generate MCP server:'), error instanceof Error ? error.message : 'Unknown error');
321
342
  process.exit(1);
322
343
  }
323
344
  }
@@ -8,7 +8,7 @@ import { TemplateDiscovery } from '../templateDiscovery.js';
8
8
  import { toKebabCase, validateNameStrict, isValidKubernetesName, } from '../utils/nameUtils.js';
9
9
  import { getInquirerProjectTypeChoices, GENERATOR_DEFAULTS, CLI_CONFIG, } from '../config.js';
10
10
  import { SecurityUtils } from '../../../lib/security.js';
11
- import { EnhancedPrompts, ProgressIndicator } from '../../../lib/progress.js';
11
+ import ora from 'ora';
12
12
  export function createProjectGenerator() {
13
13
  return {
14
14
  name: 'project',
@@ -38,61 +38,40 @@ class ProjectGenerator {
38
38
  }
39
39
  }
40
40
  async generate(name, destination, options) {
41
- const progress = new ProgressIndicator('ARK Agent Project Generator');
42
- // Add steps to progress indicator
43
- progress.addStep('prerequisites', 'Checking prerequisites');
44
- progress.addStep('configuration', 'Gathering project configuration');
45
- if (!options.skipModels) {
46
- progress.addStep('models', 'Configuring model providers');
47
- }
48
- if (!options.skipGit) {
49
- progress.addStep('git', 'Setting up git repository');
50
- }
51
- progress.addStep('generation', 'Generating project files');
52
- progress.addStep('completion', 'Finalizing project setup');
41
+ console.log(chalk.blue(`\n🚀 ARK Agent Project Generator\n`));
42
+ const spinner = ora('Checking prerequisites').start();
53
43
  try {
54
44
  // Check prerequisites
55
- progress.startStep('prerequisites');
56
45
  await this.checkPrerequisites();
57
- progress.completeStep('prerequisites', 'Prerequisites validated');
46
+ spinner.succeed('Prerequisites validated');
58
47
  // Get project configuration
59
- progress.startStep('configuration');
48
+ spinner.start('Gathering project configuration');
60
49
  const config = await this.getProjectConfig(name, destination, options);
61
- progress.completeStep('configuration', `Project "${config.name}" configured`);
50
+ spinner.succeed(`Project "${config.name}" configured`);
62
51
  // Discover and configure models (only if not skipped)
63
52
  if (config.configureModels) {
64
- progress.startStep('models');
53
+ spinner.start('Configuring model providers');
65
54
  await this.configureModels(config);
66
- progress.completeStep('models', `Model provider: ${config.selectedModels || 'none'}`);
67
- }
68
- else {
69
- progress.skipStep('models', 'Model configuration skipped');
55
+ spinner.succeed(`Model provider: ${config.selectedModels || 'none'}`);
70
56
  }
71
57
  // Configure git if requested (only if not skipped)
72
58
  if (config.initGit) {
73
- progress.startStep('git');
59
+ spinner.start('Setting up git repository');
74
60
  await this.configureGit(config);
75
- progress.completeStep('git', 'Git repository configured');
76
- }
77
- else {
78
- progress.skipStep('git', 'Git setup skipped');
61
+ spinner.succeed('Git repository configured');
79
62
  }
80
63
  // Generate the project
81
- progress.startStep('generation');
64
+ spinner.start('Generating project files');
82
65
  await this.generateProject(config);
83
- progress.completeStep('generation', 'Project files created');
66
+ spinner.succeed('Project files created');
84
67
  // Finalize
85
- progress.startStep('completion');
68
+ spinner.start('Finalizing project setup');
86
69
  this.showNextSteps(config);
87
- progress.completeStep('completion', 'Project ready');
88
- progress.complete('Project generation');
70
+ spinner.succeed('Project ready');
71
+ console.log(chalk.green(`\n✅ Project generation completed\n`));
89
72
  }
90
73
  catch (error) {
91
- // Find the current step and mark it as failed
92
- const currentStep = progress['steps'].find((s) => s.status === 'running');
93
- if (currentStep) {
94
- progress.failStep(currentStep.name, `Failed: ${error instanceof Error ? error.message : String(error)}`);
95
- }
74
+ spinner.fail(`Failed: ${error instanceof Error ? error.message : String(error)}`);
96
75
  throw error;
97
76
  }
98
77
  }
@@ -105,7 +84,7 @@ class ProjectGenerator {
105
84
  }
106
85
  catch {
107
86
  requirements.push({ tool: 'git', available: false, required: false });
108
- EnhancedPrompts.showWarning('Git not found - git features will be disabled');
87
+ console.log(chalk.yellow('⚠️ Warning: Git not found - git features will be disabled'));
109
88
  }
110
89
  // Check for deployment tools (optional for project generation)
111
90
  const deploymentTools = ['kubectl', 'helm'];
@@ -121,12 +100,14 @@ class ProjectGenerator {
121
100
  }
122
101
  }
123
102
  if (missingDeploymentTools.length > 0) {
124
- EnhancedPrompts.showInfo(`Optional tools not found: ${missingDeploymentTools.join(', ')}`);
125
- EnhancedPrompts.showTip('Install kubectl and helm later to deploy your project to a cluster');
103
+ console.log(chalk.blue(`ℹ️ Optional tools not found: ${missingDeploymentTools.join(', ')}`));
104
+ console.log(chalk.cyan('💡 Tip: Install kubectl and helm later to deploy your project to a cluster'));
126
105
  }
127
106
  }
128
107
  async getProjectConfig(name, destination, options) {
129
- EnhancedPrompts.showSeparator('Project Configuration');
108
+ console.log(chalk.gray(`\n${''.repeat(50)}`));
109
+ console.log(chalk.cyan('Project Configuration'));
110
+ console.log(chalk.gray(`${'─'.repeat(50)}\n`));
130
111
  // Use command line options if provided, otherwise prompt
131
112
  let projectType = options.projectType;
132
113
  let parentDir = destination;
@@ -1,4 +1,5 @@
1
1
  import { Command } from 'commander';
2
+ import type { ArkConfig } from '../../lib/config.js';
2
3
  export interface GeneratorOptions {
3
4
  name?: string;
4
5
  destination?: string;
@@ -21,4 +22,4 @@ export interface Generator {
21
22
  templatePath: string;
22
23
  generate(name: string, destination: string, options: GeneratorOptions): Promise<void>;
23
24
  }
24
- export declare function createGenerateCommand(): Command;
25
+ export declare function createGenerateCommand(_: ArkConfig): Command;
@@ -22,7 +22,7 @@ function getDefaultDestination() {
22
22
  return process.cwd();
23
23
  }
24
24
  }
25
- export function createGenerateCommand() {
25
+ export function createGenerateCommand(_) {
26
26
  const generate = new Command('generate');
27
27
  generate
28
28
  .alias('g')
@@ -0,0 +1,8 @@
1
+ import { Command } from 'commander';
2
+ import type { ArkConfig } from '../../lib/config.js';
3
+ export declare function installArk(serviceName?: string, options?: {
4
+ yes?: boolean;
5
+ waitForReady?: string;
6
+ verbose?: boolean;
7
+ }): Promise<void>;
8
+ export declare function createInstallCommand(_: ArkConfig): Command;
@@ -0,0 +1,302 @@
1
+ import { Command } from 'commander';
2
+ import chalk from 'chalk';
3
+ import { execute } from '../../lib/commands.js';
4
+ import inquirer from 'inquirer';
5
+ import { getClusterInfo } from '../../lib/cluster.js';
6
+ import output from '../../lib/output.js';
7
+ import { getInstallableServices, arkDependencies } from '../../arkServices.js';
8
+ import { isArkReady } from '../../lib/arkStatus.js';
9
+ import ora from 'ora';
10
+ async function installService(service, verbose = false) {
11
+ const helmArgs = [
12
+ 'upgrade',
13
+ '--install',
14
+ service.helmReleaseName,
15
+ service.chartPath,
16
+ ];
17
+ // Only add namespace flag if service has explicit namespace
18
+ if (service.namespace) {
19
+ helmArgs.push('--namespace', service.namespace);
20
+ }
21
+ // Add any additional install args
22
+ helmArgs.push(...(service.installArgs || []));
23
+ await execute('helm', helmArgs, { stdio: 'inherit' }, { verbose });
24
+ }
25
+ export async function installArk(serviceName, options = {}) {
26
+ // Validate that --wait-for-ready requires -y
27
+ if (options.waitForReady && !options.yes) {
28
+ output.error('--wait-for-ready requires -y flag for non-interactive mode');
29
+ process.exit(1);
30
+ }
31
+ // Check cluster connectivity
32
+ const clusterInfo = await getClusterInfo();
33
+ if (clusterInfo.error) {
34
+ output.error('no kubernetes cluster detected');
35
+ output.info('please ensure you have a running cluster and kubectl is configured.');
36
+ output.info('');
37
+ output.info('for local development, we recommend minikube:');
38
+ output.info('• install: https://minikube.sigs.k8s.io/docs/start');
39
+ output.info('• start cluster: minikube start');
40
+ output.info('');
41
+ output.info('alternatively, you can use kind or docker desktop.');
42
+ process.exit(1);
43
+ }
44
+ // Show cluster info
45
+ output.success(`connected to cluster: ${chalk.bold(clusterInfo.context)}`);
46
+ output.info(`type: ${clusterInfo.type}`);
47
+ output.info(`namespace: ${clusterInfo.namespace}`);
48
+ if (clusterInfo.ip) {
49
+ output.info(`ip: ${clusterInfo.ip}`);
50
+ }
51
+ console.log(); // Add blank line after cluster info
52
+ // If a specific service is requested, install only that service
53
+ if (serviceName) {
54
+ const services = getInstallableServices();
55
+ const service = Object.values(services).find((s) => s.name === serviceName);
56
+ if (!service) {
57
+ output.error(`service '${serviceName}' not found`);
58
+ output.info('available services:');
59
+ for (const s of Object.values(services)) {
60
+ output.info(` ${s.name}`);
61
+ }
62
+ process.exit(1);
63
+ }
64
+ output.info(`installing ${service.name}...`);
65
+ try {
66
+ await installService(service, options.verbose);
67
+ output.success(`${service.name} installed successfully`);
68
+ }
69
+ catch (error) {
70
+ output.error(`failed to install ${service.name}`);
71
+ console.error(error);
72
+ process.exit(1);
73
+ }
74
+ return;
75
+ }
76
+ // If not using -y flag, show checklist interface
77
+ if (!options.yes) {
78
+ console.log(chalk.cyan.bold('\nSelect components to install:'));
79
+ console.log(chalk.gray('Use arrow keys to navigate, space to toggle, enter to confirm\n'));
80
+ // Build choices for the checkbox prompt
81
+ const allChoices = [
82
+ new inquirer.Separator(chalk.bold('──── Dependencies ────')),
83
+ {
84
+ name: `cert-manager ${chalk.gray('- Certificate management')}`,
85
+ value: 'cert-manager',
86
+ checked: true,
87
+ },
88
+ {
89
+ name: `gateway-api ${chalk.gray('- Gateway API CRDs')}`,
90
+ value: 'gateway-api',
91
+ checked: true,
92
+ },
93
+ new inquirer.Separator(chalk.bold('──── Ark Core ────')),
94
+ {
95
+ name: `ark-controller ${chalk.gray('- Core Ark controller')}`,
96
+ value: 'ark-controller',
97
+ checked: true,
98
+ },
99
+ new inquirer.Separator(chalk.bold('──── Ark Services ────')),
100
+ {
101
+ name: `ark-api ${chalk.gray('- API service')}`,
102
+ value: 'ark-api',
103
+ checked: true,
104
+ },
105
+ {
106
+ name: `ark-dashboard ${chalk.gray('- Web dashboard')}`,
107
+ value: 'ark-dashboard',
108
+ checked: true,
109
+ },
110
+ {
111
+ name: `ark-mcp ${chalk.gray('- MCP services')}`,
112
+ value: 'ark-mcp',
113
+ checked: true,
114
+ },
115
+ {
116
+ name: `localhost-gateway ${chalk.gray('- Gateway for local access')}`,
117
+ value: 'localhost-gateway',
118
+ checked: true,
119
+ },
120
+ ];
121
+ let selectedComponents = [];
122
+ try {
123
+ const answers = await inquirer.prompt([
124
+ {
125
+ type: 'checkbox',
126
+ name: 'components',
127
+ message: 'Components to install:',
128
+ choices: allChoices,
129
+ pageSize: 15,
130
+ },
131
+ ]);
132
+ selectedComponents = answers.components;
133
+ if (selectedComponents.length === 0) {
134
+ output.warning('No components selected. Exiting.');
135
+ process.exit(0);
136
+ }
137
+ }
138
+ catch (error) {
139
+ // Handle Ctrl-C gracefully
140
+ if (error && error.name === 'ExitPromptError') {
141
+ console.log('\nInstallation cancelled');
142
+ process.exit(130);
143
+ }
144
+ throw error;
145
+ }
146
+ // Install dependencies if selected
147
+ const shouldInstallDeps = selectedComponents.includes('cert-manager') ||
148
+ selectedComponents.includes('gateway-api');
149
+ // Install selected dependencies
150
+ if (shouldInstallDeps) {
151
+ // Always install cert-manager repo and update if installing any dependency
152
+ if (selectedComponents.includes('cert-manager') ||
153
+ selectedComponents.includes('gateway-api')) {
154
+ for (const depKey of ['cert-manager-repo', 'helm-repo-update']) {
155
+ const dep = arkDependencies[depKey];
156
+ output.info(`installing ${dep.description || dep.name}...`);
157
+ try {
158
+ await execute(dep.command, dep.args, {
159
+ stdio: 'inherit',
160
+ }, { verbose: options.verbose });
161
+ output.success(`${dep.name} completed`);
162
+ console.log();
163
+ }
164
+ catch {
165
+ console.log();
166
+ process.exit(1);
167
+ }
168
+ }
169
+ }
170
+ // Install cert-manager if selected
171
+ if (selectedComponents.includes('cert-manager')) {
172
+ const dep = arkDependencies['cert-manager'];
173
+ output.info(`installing ${dep.description || dep.name}...`);
174
+ try {
175
+ await execute(dep.command, dep.args, {
176
+ stdio: 'inherit',
177
+ }, { verbose: options.verbose });
178
+ output.success(`${dep.name} completed`);
179
+ console.log();
180
+ }
181
+ catch {
182
+ console.log();
183
+ process.exit(1);
184
+ }
185
+ }
186
+ // Install gateway-api if selected
187
+ if (selectedComponents.includes('gateway-api')) {
188
+ const dep = arkDependencies['gateway-api-crds'];
189
+ output.info(`installing ${dep.description || dep.name}...`);
190
+ try {
191
+ await execute(dep.command, dep.args, {
192
+ stdio: 'inherit',
193
+ }, { verbose: options.verbose });
194
+ output.success(`${dep.name} completed`);
195
+ console.log();
196
+ }
197
+ catch {
198
+ console.log();
199
+ process.exit(1);
200
+ }
201
+ }
202
+ }
203
+ // Install selected services
204
+ const services = getInstallableServices();
205
+ for (const service of Object.values(services)) {
206
+ // Check if this service was selected
207
+ const serviceKey = service.helmReleaseName;
208
+ if (!selectedComponents.includes(serviceKey)) {
209
+ continue;
210
+ }
211
+ output.info(`installing ${service.name}...`);
212
+ try {
213
+ await installService(service, options.verbose);
214
+ console.log(); // Add blank line after command output
215
+ }
216
+ catch {
217
+ // Continue with remaining services on error
218
+ console.log(); // Add blank line after error output
219
+ }
220
+ }
221
+ }
222
+ else {
223
+ // -y flag was used, install everything
224
+ // Install all dependencies
225
+ for (const dep of Object.values(arkDependencies)) {
226
+ output.info(`installing ${dep.description || dep.name}...`);
227
+ try {
228
+ await execute(dep.command, dep.args, {
229
+ stdio: 'inherit',
230
+ }, { verbose: options.verbose });
231
+ output.success(`${dep.name} completed`);
232
+ console.log(); // Add blank line after dependency
233
+ }
234
+ catch {
235
+ console.log(); // Add blank line after error
236
+ process.exit(1);
237
+ }
238
+ }
239
+ // Install all services
240
+ const services = getInstallableServices();
241
+ for (const service of Object.values(services)) {
242
+ output.info(`installing ${service.name}...`);
243
+ try {
244
+ await installService(service, options.verbose);
245
+ console.log(); // Add blank line after command output
246
+ }
247
+ catch {
248
+ // Continue with remaining services on error
249
+ console.log(); // Add blank line after error output
250
+ }
251
+ }
252
+ }
253
+ // Wait for Ark to be ready if requested
254
+ if (options.waitForReady) {
255
+ // Parse timeout value (e.g., '30s', '2m', '60')
256
+ const parseTimeout = (value) => {
257
+ const match = value.match(/^(\d+)([sm])?$/);
258
+ if (!match) {
259
+ throw new Error('Invalid timeout format. Use format like 30s or 2m');
260
+ }
261
+ const num = parseInt(match[1], 10);
262
+ const unit = match[2] || 's';
263
+ return unit === 'm' ? num * 60 : num;
264
+ };
265
+ try {
266
+ const timeoutSeconds = parseTimeout(options.waitForReady);
267
+ const startTime = Date.now();
268
+ const endTime = startTime + timeoutSeconds * 1000;
269
+ const spinner = ora(`Waiting for Ark to be ready (timeout: ${timeoutSeconds}s)...`).start();
270
+ while (Date.now() < endTime) {
271
+ if (await isArkReady()) {
272
+ spinner.succeed('Ark is ready!');
273
+ return;
274
+ }
275
+ const elapsed = Math.floor((Date.now() - startTime) / 1000);
276
+ spinner.text = `Waiting for Ark to be ready (${elapsed}/${timeoutSeconds}s)...`;
277
+ // Wait 2 seconds before checking again
278
+ await new Promise((resolve) => setTimeout(resolve, 2000));
279
+ }
280
+ // Timeout reached
281
+ spinner.fail(`Ark did not become ready within ${timeoutSeconds} seconds`);
282
+ process.exit(1);
283
+ }
284
+ catch (error) {
285
+ output.error(`Failed to wait for ready: ${error instanceof Error ? error.message : 'Unknown error'}`);
286
+ process.exit(1);
287
+ }
288
+ }
289
+ }
290
+ export function createInstallCommand(_) {
291
+ const command = new Command('install');
292
+ command
293
+ .description('Install ARK components using Helm')
294
+ .argument('[service]', 'specific service to install, or all if omitted')
295
+ .option('-y, --yes', 'automatically confirm all installations')
296
+ .option('--wait-for-ready <timeout>', 'wait for Ark to be ready after installation (e.g., 30s, 2m)')
297
+ .option('-v, --verbose', 'show commands being executed')
298
+ .action(async (service, options) => {
299
+ await installArk(service, options);
300
+ });
301
+ return command;
302
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,135 @@
1
+ import { jest } from '@jest/globals';
2
+ import { Command } from 'commander';
3
+ const mockExeca = jest.fn(() => Promise.resolve());
4
+ jest.unstable_mockModule('execa', () => ({
5
+ execa: mockExeca,
6
+ }));
7
+ const mockGetClusterInfo = jest.fn();
8
+ jest.unstable_mockModule('../../lib/cluster.js', () => ({
9
+ getClusterInfo: mockGetClusterInfo,
10
+ }));
11
+ const mockGetInstallableServices = jest.fn();
12
+ const mockArkDependencies = {};
13
+ jest.unstable_mockModule('../../arkServices.js', () => ({
14
+ getInstallableServices: mockGetInstallableServices,
15
+ arkDependencies: mockArkDependencies,
16
+ }));
17
+ const mockOutput = {
18
+ error: jest.fn(),
19
+ info: jest.fn(),
20
+ success: jest.fn(),
21
+ warning: jest.fn(),
22
+ };
23
+ jest.unstable_mockModule('../../lib/output.js', () => ({
24
+ default: mockOutput,
25
+ }));
26
+ const mockExit = jest.spyOn(process, 'exit').mockImplementation((() => {
27
+ throw new Error('process.exit called');
28
+ }));
29
+ jest.spyOn(console, 'log').mockImplementation(() => { });
30
+ jest.spyOn(console, 'error').mockImplementation(() => { });
31
+ const { createInstallCommand } = await import('./index.js');
32
+ describe('install command', () => {
33
+ beforeEach(() => {
34
+ jest.clearAllMocks();
35
+ mockGetClusterInfo.mockResolvedValue({
36
+ context: 'test-cluster',
37
+ type: 'minikube',
38
+ namespace: 'default',
39
+ });
40
+ });
41
+ it('creates command with correct structure', () => {
42
+ const command = createInstallCommand({});
43
+ expect(command).toBeInstanceOf(Command);
44
+ expect(command.name()).toBe('install');
45
+ });
46
+ it('installs single service with correct helm parameters', async () => {
47
+ const mockService = {
48
+ name: 'ark-api',
49
+ helmReleaseName: 'ark-api',
50
+ chartPath: './charts/ark-api',
51
+ namespace: 'ark-system',
52
+ installArgs: ['--set', 'image.tag=latest'],
53
+ };
54
+ mockGetInstallableServices.mockReturnValue({
55
+ 'ark-api': mockService,
56
+ });
57
+ const command = createInstallCommand({});
58
+ await command.parseAsync(['node', 'test', 'ark-api']);
59
+ expect(mockExeca).toHaveBeenCalledWith('helm', [
60
+ 'upgrade',
61
+ '--install',
62
+ 'ark-api',
63
+ './charts/ark-api',
64
+ '--namespace',
65
+ 'ark-system',
66
+ '--set',
67
+ 'image.tag=latest',
68
+ ], { stdio: 'inherit' });
69
+ expect(mockOutput.success).toHaveBeenCalledWith('ark-api installed successfully');
70
+ });
71
+ it('shows error when service not found', async () => {
72
+ mockGetInstallableServices.mockReturnValue({
73
+ 'ark-api': { name: 'ark-api' },
74
+ 'ark-controller': { name: 'ark-controller' },
75
+ });
76
+ const command = createInstallCommand({});
77
+ await expect(command.parseAsync(['node', 'test', 'invalid-service'])).rejects.toThrow('process.exit called');
78
+ expect(mockOutput.error).toHaveBeenCalledWith("service 'invalid-service' not found");
79
+ expect(mockOutput.info).toHaveBeenCalledWith('available services:');
80
+ expect(mockOutput.info).toHaveBeenCalledWith(' ark-api');
81
+ expect(mockOutput.info).toHaveBeenCalledWith(' ark-controller');
82
+ expect(mockExit).toHaveBeenCalledWith(1);
83
+ });
84
+ it('handles service without namespace (uses current context)', async () => {
85
+ const mockService = {
86
+ name: 'ark-dashboard',
87
+ helmReleaseName: 'ark-dashboard',
88
+ chartPath: './charts/ark-dashboard',
89
+ // namespace is undefined - should use current context
90
+ installArgs: ['--set', 'replicas=2'],
91
+ };
92
+ mockGetInstallableServices.mockReturnValue({
93
+ 'ark-dashboard': mockService,
94
+ });
95
+ const command = createInstallCommand({});
96
+ await command.parseAsync(['node', 'test', 'ark-dashboard']);
97
+ // Should NOT include --namespace flag
98
+ expect(mockExeca).toHaveBeenCalledWith('helm', [
99
+ 'upgrade',
100
+ '--install',
101
+ 'ark-dashboard',
102
+ './charts/ark-dashboard',
103
+ '--set',
104
+ 'replicas=2',
105
+ ], { stdio: 'inherit' });
106
+ });
107
+ it('handles service without installArgs', async () => {
108
+ const mockService = {
109
+ name: 'simple-service',
110
+ helmReleaseName: 'simple-service',
111
+ chartPath: './charts/simple',
112
+ namespace: 'default',
113
+ };
114
+ mockGetInstallableServices.mockReturnValue({
115
+ 'simple-service': mockService,
116
+ });
117
+ const command = createInstallCommand({});
118
+ await command.parseAsync(['node', 'test', 'simple-service']);
119
+ expect(mockExeca).toHaveBeenCalledWith('helm', [
120
+ 'upgrade',
121
+ '--install',
122
+ 'simple-service',
123
+ './charts/simple',
124
+ '--namespace',
125
+ 'default',
126
+ ], { stdio: 'inherit' });
127
+ });
128
+ it('exits when cluster not connected', async () => {
129
+ mockGetClusterInfo.mockResolvedValue({ error: true });
130
+ const command = createInstallCommand({});
131
+ await expect(command.parseAsync(['node', 'test', 'ark-api'])).rejects.toThrow('process.exit called');
132
+ expect(mockOutput.error).toHaveBeenCalledWith('no kubernetes cluster detected');
133
+ expect(mockExit).toHaveBeenCalledWith(1);
134
+ });
135
+ });
@@ -0,0 +1,3 @@
1
+ import { Command } from 'commander';
2
+ export declare function installArk(): Promise<void>;
3
+ export declare function createInstallCommand(): Command;