@agents-at-scale/ark 0.1.35-rc.1 → 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 (122) hide show
  1. package/dist/arkServices.d.ts +4 -12
  2. package/dist/arkServices.js +19 -34
  3. package/dist/arkServices.spec.d.ts +1 -0
  4. package/dist/arkServices.spec.js +24 -0
  5. package/dist/commands/agents/index.d.ts +2 -1
  6. package/dist/commands/agents/index.js +2 -7
  7. package/dist/commands/agents/index.spec.d.ts +1 -0
  8. package/dist/commands/agents/index.spec.js +67 -0
  9. package/dist/commands/chat/index.d.ts +2 -1
  10. package/dist/commands/chat/index.js +5 -21
  11. package/dist/commands/cluster/get.spec.d.ts +1 -0
  12. package/dist/commands/cluster/get.spec.js +92 -0
  13. package/dist/commands/cluster/index.d.ts +2 -1
  14. package/dist/commands/cluster/index.js +1 -1
  15. package/dist/commands/cluster/index.spec.d.ts +1 -0
  16. package/dist/commands/cluster/index.spec.js +24 -0
  17. package/dist/commands/completion/index.d.ts +2 -1
  18. package/dist/commands/completion/index.js +1 -1
  19. package/dist/commands/completion/index.spec.d.ts +1 -0
  20. package/dist/commands/completion/index.spec.js +34 -0
  21. package/dist/commands/config/index.d.ts +2 -1
  22. package/dist/commands/config/index.js +2 -2
  23. package/dist/commands/config/index.spec.d.ts +1 -0
  24. package/dist/commands/config/index.spec.js +78 -0
  25. package/dist/commands/dashboard/index.d.ts +2 -1
  26. package/dist/commands/dashboard/index.js +1 -1
  27. package/dist/commands/dev/index.d.ts +2 -1
  28. package/dist/commands/dev/index.js +1 -1
  29. package/dist/commands/dev/tool-generate.spec.d.ts +1 -0
  30. package/dist/commands/dev/tool-generate.spec.js +163 -0
  31. package/dist/commands/dev/tool.spec.d.ts +1 -0
  32. package/dist/commands/dev/tool.spec.js +48 -0
  33. package/dist/commands/generate/generators/project.js +22 -41
  34. package/dist/commands/generate/index.d.ts +2 -1
  35. package/dist/commands/generate/index.js +1 -1
  36. package/dist/commands/install/index.d.ts +4 -2
  37. package/dist/commands/install/index.js +215 -78
  38. package/dist/commands/install/index.spec.d.ts +1 -0
  39. package/dist/commands/install/index.spec.js +135 -0
  40. package/dist/commands/models/create.spec.d.ts +1 -0
  41. package/dist/commands/models/create.spec.js +125 -0
  42. package/dist/commands/models/index.d.ts +2 -1
  43. package/dist/commands/models/index.js +2 -7
  44. package/dist/commands/models/index.spec.d.ts +1 -0
  45. package/dist/commands/models/index.spec.js +76 -0
  46. package/dist/commands/routes/index.d.ts +2 -1
  47. package/dist/commands/routes/index.js +1 -9
  48. package/dist/commands/status/index.d.ts +3 -2
  49. package/dist/commands/status/index.js +210 -11
  50. package/dist/commands/targets/index.d.ts +2 -1
  51. package/dist/commands/targets/index.js +1 -1
  52. package/dist/commands/targets/index.spec.d.ts +1 -0
  53. package/dist/commands/targets/index.spec.js +105 -0
  54. package/dist/commands/teams/index.d.ts +2 -1
  55. package/dist/commands/teams/index.js +2 -7
  56. package/dist/commands/teams/index.spec.d.ts +1 -0
  57. package/dist/commands/teams/index.spec.js +70 -0
  58. package/dist/commands/tools/index.d.ts +2 -1
  59. package/dist/commands/tools/index.js +2 -7
  60. package/dist/commands/tools/index.spec.d.ts +1 -0
  61. package/dist/commands/tools/index.spec.js +70 -0
  62. package/dist/commands/uninstall/index.d.ts +2 -1
  63. package/dist/commands/uninstall/index.js +61 -38
  64. package/dist/commands/uninstall/index.spec.d.ts +1 -0
  65. package/dist/commands/uninstall/index.spec.js +117 -0
  66. package/dist/components/ChatUI.js +4 -4
  67. package/dist/components/statusChecker.d.ts +5 -12
  68. package/dist/components/statusChecker.js +172 -89
  69. package/dist/config.d.ts +3 -22
  70. package/dist/config.js +7 -151
  71. package/dist/index.js +22 -19
  72. package/dist/lib/arkServiceProxy.js +4 -2
  73. package/dist/lib/arkStatus.d.ts +5 -0
  74. package/dist/lib/arkStatus.js +61 -2
  75. package/dist/lib/arkStatus.spec.d.ts +1 -0
  76. package/dist/lib/arkStatus.spec.js +49 -0
  77. package/dist/lib/chatClient.js +1 -3
  78. package/dist/lib/cluster.js +11 -14
  79. package/dist/lib/cluster.spec.d.ts +1 -0
  80. package/dist/lib/cluster.spec.js +338 -0
  81. package/dist/lib/commandUtils.js +7 -7
  82. package/dist/lib/commands.d.ts +16 -0
  83. package/dist/lib/commands.js +29 -0
  84. package/dist/lib/commands.spec.d.ts +1 -0
  85. package/dist/lib/commands.spec.js +146 -0
  86. package/dist/lib/config.d.ts +2 -0
  87. package/dist/lib/config.js +6 -4
  88. package/dist/lib/config.spec.d.ts +1 -0
  89. package/dist/lib/config.spec.js +99 -0
  90. package/dist/lib/consts.d.ts +0 -1
  91. package/dist/lib/consts.js +0 -2
  92. package/dist/lib/consts.spec.d.ts +1 -0
  93. package/dist/lib/consts.spec.js +15 -0
  94. package/dist/lib/errors.js +1 -1
  95. package/dist/lib/errors.spec.d.ts +1 -0
  96. package/dist/lib/errors.spec.js +221 -0
  97. package/dist/lib/exec.d.ts +0 -4
  98. package/dist/lib/exec.js +0 -11
  99. package/dist/lib/output.spec.d.ts +1 -0
  100. package/dist/lib/output.spec.js +123 -0
  101. package/dist/lib/portUtils.d.ts +8 -0
  102. package/dist/lib/portUtils.js +39 -0
  103. package/dist/lib/startup.d.ts +5 -0
  104. package/dist/lib/startup.js +73 -0
  105. package/dist/lib/startup.spec.d.ts +1 -0
  106. package/dist/lib/startup.spec.js +168 -0
  107. package/dist/lib/types.d.ts +2 -0
  108. package/dist/ui/AgentSelector.d.ts +8 -0
  109. package/dist/ui/AgentSelector.js +53 -0
  110. package/dist/ui/MainMenu.d.ts +5 -1
  111. package/dist/ui/MainMenu.js +117 -54
  112. package/dist/ui/ModelSelector.d.ts +8 -0
  113. package/dist/ui/ModelSelector.js +53 -0
  114. package/dist/ui/TeamSelector.d.ts +8 -0
  115. package/dist/ui/TeamSelector.js +55 -0
  116. package/dist/ui/ToolSelector.d.ts +8 -0
  117. package/dist/ui/ToolSelector.js +53 -0
  118. package/dist/ui/statusFormatter.d.ts +22 -10
  119. package/dist/ui/statusFormatter.js +37 -109
  120. package/dist/ui/statusFormatter.spec.d.ts +1 -0
  121. package/dist/ui/statusFormatter.spec.js +58 -0
  122. package/package.json +3 -3
@@ -1,38 +1,68 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
2
  import { Text, Box, render, useInput } from 'ink';
3
+ import Spinner from 'ink-spinner';
3
4
  import * as React from 'react';
5
+ import { isArkReady } from '../lib/arkStatus.js';
4
6
  // Helper function to unmount the main ink app - used when we move from a
5
7
  // React TUI app to basic input/output.
6
8
  async function unmountInkApp() {
7
9
  const app = globalThis.inkApp;
8
10
  if (app) {
11
+ // Unmount the Ink app
9
12
  app.unmount();
10
- // Remove all existing signal listeners that might interfere with inquirer
11
- process.removeAllListeners('SIGINT');
12
- process.removeAllListeners('SIGTERM');
13
- process.removeAllListeners('SIGQUIT');
14
- process.removeAllListeners('exit');
15
- // Reset stdin completely
13
+ // Clear the global reference
14
+ delete globalThis.inkApp;
15
+ // Reset terminal to normal mode
16
16
  if (process.stdin.isTTY) {
17
17
  process.stdin.setRawMode(false);
18
18
  process.stdin.pause();
19
- process.stdin.removeAllListeners();
20
- process.stdin.resume();
21
19
  }
22
- // Reset stdout/stderr
23
- process.stdout.removeAllListeners();
24
- process.stderr.removeAllListeners();
20
+ // Clear screen
25
21
  console.clear();
26
- // Give terminal more time to fully reset
27
- await new Promise((resolve) => setTimeout(resolve, 200));
22
+ // Small delay to ensure everything is flushed
23
+ await new Promise((resolve) => setTimeout(resolve, 50));
28
24
  }
29
25
  }
30
- const MainMenu = () => {
26
+ const MainMenu = ({ config }) => {
31
27
  const [selectedIndex, setSelectedIndex] = React.useState(0);
32
- const choices = [
28
+ const [arkReady, setArkReady] = React.useState(null);
29
+ const [isChecking, setIsChecking] = React.useState(true);
30
+ React.useEffect(() => {
31
+ const checkArkStatus = async () => {
32
+ setIsChecking(true);
33
+ const ready = await isArkReady();
34
+ setArkReady(ready);
35
+ setIsChecking(false);
36
+ // Reset selected index to 0 after status check
37
+ setSelectedIndex(0);
38
+ };
39
+ checkArkStatus();
40
+ }, []);
41
+ // Handle Ctrl+C to properly unmount Ink and restore terminal
42
+ React.useEffect(() => {
43
+ const handleExit = () => {
44
+ const app = globalThis.inkApp;
45
+ if (app) {
46
+ app.unmount();
47
+ }
48
+ process.exit(0);
49
+ };
50
+ process.on('SIGINT', handleExit);
51
+ return () => {
52
+ process.removeListener('SIGINT', handleExit);
53
+ };
54
+ }, []);
55
+ // Check if upgrade is available
56
+ const hasUpgrade = React.useMemo(() => {
57
+ if (!config.currentVersion || !config.latestVersion)
58
+ return false;
59
+ // Simple version comparison
60
+ return config.currentVersion !== config.latestVersion;
61
+ }, [config.currentVersion, config.latestVersion]);
62
+ const allChoices = [
33
63
  {
34
64
  label: 'Chat',
35
- description: 'Interactive chat with ARK agents',
65
+ description: 'Interactive chat with Ark agents',
36
66
  value: 'chat',
37
67
  command: 'ark chat',
38
68
  },
@@ -42,27 +72,57 @@ const MainMenu = () => {
42
72
  value: 'install',
43
73
  command: 'ark install',
44
74
  },
75
+ {
76
+ label: 'Upgrade',
77
+ description: `Upgrade Ark from ${config.currentVersion} to ${config.latestVersion}`,
78
+ value: 'upgrade',
79
+ command: 'ark install -y',
80
+ },
45
81
  {
46
82
  label: 'Dashboard',
47
- description: 'Open ARK dashboard in browser',
83
+ description: 'Open Ark dashboard in browser',
48
84
  value: 'dashboard',
49
85
  command: 'ark dashboard',
50
86
  },
51
87
  {
52
- label: 'Status Check',
53
- description: 'Check ARK services status',
88
+ label: 'Status',
89
+ description: 'Check Ark services status',
54
90
  value: 'status',
55
91
  command: 'ark status',
56
92
  },
57
93
  {
58
94
  label: 'Generate',
59
- description: 'Generate new ARK components',
95
+ description: 'Generate new Ark components',
60
96
  value: 'generate',
61
97
  command: 'ark generate',
62
98
  },
63
- { label: 'Exit', description: 'Exit ARK CLI', value: 'exit' },
99
+ { label: 'Exit', description: 'Exit Ark CLI', value: 'exit' },
64
100
  ];
101
+ // Filter choices based on Ark readiness
102
+ const choices = React.useMemo(() => {
103
+ // Don't return any choices while checking
104
+ if (isChecking)
105
+ return [];
106
+ if (!arkReady) {
107
+ // Only show Install, Status, and Exit when Ark is not ready
108
+ return allChoices.filter((choice) => ['install', 'status', 'exit'].includes(choice.value));
109
+ }
110
+ // Ark is ready - filter out install (already installed) and conditionally show upgrade
111
+ const filteredChoices = allChoices.filter((choice) => {
112
+ // Never show install when Ark is ready (it's already installed)
113
+ if (choice.value === 'install')
114
+ return false;
115
+ // Only show upgrade if there's actually an upgrade available
116
+ if (choice.value === 'upgrade' && !hasUpgrade)
117
+ return false;
118
+ return true;
119
+ });
120
+ return filteredChoices;
121
+ }, [arkReady, isChecking, hasUpgrade, allChoices]);
65
122
  useInput((input, key) => {
123
+ // Don't process input while checking status
124
+ if (isChecking)
125
+ return;
66
126
  if (key.upArrow || input === 'k') {
67
127
  setSelectedIndex((prev) => (prev > 0 ? prev - 1 : choices.length - 1));
68
128
  }
@@ -102,7 +162,7 @@ const MainMenu = () => {
102
162
  const output = (await import('../lib/output.js')).default;
103
163
  output.error(error instanceof Error
104
164
  ? error.message
105
- : 'Failed to connect to ARK API');
165
+ : 'Failed to connect to Ark API');
106
166
  process.exit(1);
107
167
  }
108
168
  break;
@@ -110,36 +170,38 @@ const MainMenu = () => {
110
170
  case 'install': {
111
171
  // Unmount fullscreen app and clear screen.
112
172
  await unmountInkApp();
113
- // NOTE: We spawn the install command as a separate process to avoid
114
- // signal handling conflicts between Ink's useInput and inquirer's prompts.
115
- // The signal-exit library used by inquirer conflicts with Ink's signal handlers,
116
- // causing ExitPromptError even after proper cleanup. Spawning ensures
117
- // a clean process environment for inquirer to work correctly.
118
- const { spawn } = await import('child_process');
119
- const child = spawn(process.execPath, [process.argv[1], 'install'], {
120
- stdio: 'inherit',
121
- });
122
- await new Promise((resolve, reject) => {
123
- child.on('close', (code) => {
124
- if (code === 0) {
125
- resolve();
126
- }
127
- else if (code === 130) {
128
- // 130 is the exit code for SIGINT (Ctrl+C)
129
- process.exit(130);
130
- }
131
- else {
132
- reject(new Error(`Install command failed with code ${code}`));
133
- }
173
+ // Spawn as a new process to avoid Ink/inquirer signal conflicts
174
+ const { execFileSync } = await import('child_process');
175
+ try {
176
+ execFileSync(process.execPath, [process.argv[1], 'install'], {
177
+ stdio: 'inherit',
178
+ env: { ...process.env, FORCE_COLOR: '1' },
134
179
  });
135
- child.on('error', reject);
136
- // Forward SIGINT to child and exit
137
- process.on('SIGINT', () => {
138
- child.kill('SIGINT');
139
- process.exit(130);
180
+ }
181
+ catch (error) {
182
+ // execFileSync throws if the process exits with non-zero
183
+ process.exit(error.status || 1);
184
+ }
185
+ process.exit(0);
186
+ break; // Add break even though process.exit prevents reaching here
187
+ }
188
+ case 'upgrade': {
189
+ // Unmount fullscreen app and clear screen.
190
+ await unmountInkApp();
191
+ // Spawn as a new process with -y flag for automatic upgrade
192
+ const { execFileSync } = await import('child_process');
193
+ try {
194
+ execFileSync(process.execPath, [process.argv[1], 'install', '-y'], {
195
+ stdio: 'inherit',
196
+ env: { ...process.env, FORCE_COLOR: '1' },
140
197
  });
141
- });
142
- break;
198
+ }
199
+ catch (error) {
200
+ // execFileSync throws if the process exits with non-zero
201
+ process.exit(error.status || 1);
202
+ }
203
+ process.exit(0);
204
+ break; // Add break even though process.exit prevents reaching here
143
205
  }
144
206
  case 'dashboard': {
145
207
  // Unmount fullscreen app and clear screen.
@@ -152,8 +214,9 @@ const MainMenu = () => {
152
214
  // Unmount fullscreen app and clear screen.
153
215
  await unmountInkApp();
154
216
  const { checkStatus } = await import('../commands/status/index.js');
155
- await checkStatus();
156
- break;
217
+ await checkStatus(config);
218
+ process.exit(0);
219
+ break; // Add break even though process.exit prevents reaching here
157
220
  }
158
221
  case 'generate': {
159
222
  const GeneratorUI = (await import('../components/GeneratorUI.js'))
@@ -176,9 +239,9 @@ const MainMenu = () => {
176
239
  ║ Agents at Scale Platform ║
177
240
  ║ ║
178
241
  ╚═══════════════════════════════════════╝
179
- ` }), _jsx(Text, { color: "green", bold: true, children: "Welcome to ARK! \uD83D\uDE80" }), _jsx(Text, { color: "gray", children: "Interactive terminal interface for ARK agents" })] }), _jsx(Box, { flexDirection: "column", paddingX: 4, marginTop: 1, children: choices.map((choice, index) => {
242
+ ` }), isChecking ? (_jsxs(Text, { color: "gray", children: [_jsx(Spinner, { type: "dots" }), " Checking Ark status..."] })) : arkReady ? (_jsxs(Box, { children: [_jsx(Text, { color: "green", bold: true, children: "\u25CF Ark is ready" }), config.currentVersion && (_jsxs(Text, { color: "gray", children: [" (", config.currentVersion, ")"] }))] })) : (_jsx(Text, { color: "yellow", bold: true, children: "\u25CF Ark is not installed" })), _jsx(Text, { color: "gray", children: "Interactive terminal interface for Ark agents" })] }), !isChecking && (_jsx(Box, { flexDirection: "column", paddingX: 4, marginTop: 1, children: choices.map((choice, index) => {
180
243
  const isSelected = index === selectedIndex;
181
244
  return (_jsxs(Box, { flexDirection: "row", paddingY: 0, children: [_jsx(Text, { color: "gray", dimColor: true, children: isSelected ? '❯ ' : ' ' }), _jsxs(Text, { color: "gray", dimColor: true, children: [index + 1, "."] }), _jsx(Box, { marginLeft: 1, width: 20, children: _jsx(Text, { color: isSelected ? 'green' : 'white', bold: isSelected, children: choice.label }) }), _jsx(Text, { color: "gray", children: choice.description })] }, choice.value));
182
- }) })] }));
245
+ }) }))] }));
183
246
  };
184
247
  export default MainMenu;
@@ -0,0 +1,8 @@
1
+ import { Model, ArkApiClient } from '../lib/arkApiClient.js';
2
+ interface ModelSelectorProps {
3
+ arkApiClient: ArkApiClient;
4
+ onSelect: (model: Model) => void;
5
+ onExit: () => void;
6
+ }
7
+ export declare function ModelSelector({ arkApiClient, onSelect, onExit, }: ModelSelectorProps): import("react/jsx-runtime").JSX.Element;
8
+ export {};
@@ -0,0 +1,53 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useState, useEffect } from 'react';
3
+ import { Box, Text, useInput } from 'ink';
4
+ export function ModelSelector({ arkApiClient, onSelect, onExit, }) {
5
+ const [selectedIndex, setSelectedIndex] = useState(0);
6
+ const [models, setModels] = useState([]);
7
+ const [loading, setLoading] = useState(true);
8
+ const [error, setError] = useState(null);
9
+ useEffect(() => {
10
+ arkApiClient
11
+ .getModels()
12
+ .then((fetchedModels) => {
13
+ setModels(fetchedModels);
14
+ setLoading(false);
15
+ })
16
+ .catch((err) => {
17
+ setError(err.message || 'Failed to fetch models');
18
+ setLoading(false);
19
+ });
20
+ }, [arkApiClient]);
21
+ useInput((input, key) => {
22
+ if (key.escape) {
23
+ onExit();
24
+ }
25
+ else if (key.upArrow || input === 'k') {
26
+ setSelectedIndex((prev) => (prev === 0 ? models.length - 1 : prev - 1));
27
+ }
28
+ else if (key.downArrow || input === 'j') {
29
+ setSelectedIndex((prev) => (prev === models.length - 1 ? 0 : prev + 1));
30
+ }
31
+ else if (key.return) {
32
+ onSelect(models[selectedIndex]);
33
+ }
34
+ else {
35
+ // Handle number keys for quick selection
36
+ const num = parseInt(input, 10);
37
+ if (!isNaN(num) && num >= 1 && num <= models.length) {
38
+ onSelect(models[num - 1]);
39
+ }
40
+ }
41
+ });
42
+ if (loading) {
43
+ return (_jsx(Box, { children: _jsx(Text, { children: "Loading models..." }) }));
44
+ }
45
+ if (error) {
46
+ return (_jsx(Box, { children: _jsxs(Text, { color: "red", children: ["Error: ", error] }) }));
47
+ }
48
+ if (models.length === 0) {
49
+ return (_jsx(Box, { children: _jsx(Text, { children: "No models available" }) }));
50
+ }
51
+ const selectedModel = models[selectedIndex];
52
+ return (_jsxs(Box, { flexDirection: "column", borderStyle: "round", borderColor: "gray", paddingX: 2, paddingY: 1, children: [_jsx(Box, { marginBottom: 1, children: _jsx(Text, { bold: true, children: "Select Model" }) }), _jsx(Box, { marginBottom: 1, children: _jsx(Text, { dimColor: true, children: "Choose a model to start a conversation with" }) }), _jsx(Box, { flexDirection: "column", children: models.map((model, index) => (_jsx(Box, { marginBottom: 0, children: _jsxs(Text, { color: index === selectedIndex ? 'green' : undefined, children: [index === selectedIndex ? '❯ ' : ' ', index + 1, ". ", model.name, model.type ? ` (${model.type})` : ''] }) }, model.name))) }), selectedModel && selectedModel.model && (_jsx(Box, { marginTop: 1, paddingLeft: 2, children: _jsxs(Text, { dimColor: true, wrap: "wrap", children: ["Model: ", selectedModel.model] }) })), _jsx(Box, { marginTop: 1, children: _jsx(Text, { dimColor: true, children: "Enter to confirm \u00B7 Number to select \u00B7 Esc to exit" }) })] }));
53
+ }
@@ -0,0 +1,8 @@
1
+ import { Team, ArkApiClient } from '../lib/arkApiClient.js';
2
+ interface TeamSelectorProps {
3
+ arkApiClient: ArkApiClient;
4
+ onSelect: (team: Team) => void;
5
+ onExit: () => void;
6
+ }
7
+ export declare function TeamSelector({ arkApiClient, onSelect, onExit, }: TeamSelectorProps): import("react/jsx-runtime").JSX.Element;
8
+ export {};
@@ -0,0 +1,55 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useState, useEffect } from 'react';
3
+ import { Box, Text, useInput } from 'ink';
4
+ export function TeamSelector({ arkApiClient, onSelect, onExit, }) {
5
+ const [selectedIndex, setSelectedIndex] = useState(0);
6
+ const [teams, setTeams] = useState([]);
7
+ const [loading, setLoading] = useState(true);
8
+ const [error, setError] = useState(null);
9
+ useEffect(() => {
10
+ arkApiClient
11
+ .getTeams()
12
+ .then((fetchedTeams) => {
13
+ setTeams(fetchedTeams);
14
+ setLoading(false);
15
+ })
16
+ .catch((err) => {
17
+ setError(err.message || 'Failed to fetch teams');
18
+ setLoading(false);
19
+ });
20
+ }, [arkApiClient]);
21
+ useInput((input, key) => {
22
+ if (key.escape) {
23
+ onExit();
24
+ }
25
+ else if (key.upArrow || input === 'k') {
26
+ setSelectedIndex((prev) => (prev === 0 ? teams.length - 1 : prev - 1));
27
+ }
28
+ else if (key.downArrow || input === 'j') {
29
+ setSelectedIndex((prev) => (prev === teams.length - 1 ? 0 : prev + 1));
30
+ }
31
+ else if (key.return) {
32
+ onSelect(teams[selectedIndex]);
33
+ }
34
+ else {
35
+ // Handle number keys for quick selection
36
+ const num = parseInt(input, 10);
37
+ if (!isNaN(num) && num >= 1 && num <= teams.length) {
38
+ onSelect(teams[num - 1]);
39
+ }
40
+ }
41
+ });
42
+ if (loading) {
43
+ return (_jsx(Box, { children: _jsx(Text, { children: "Loading teams..." }) }));
44
+ }
45
+ if (error) {
46
+ return (_jsx(Box, { children: _jsxs(Text, { color: "red", children: ["Error: ", error] }) }));
47
+ }
48
+ if (teams.length === 0) {
49
+ return (_jsx(Box, { children: _jsx(Text, { children: "No teams available" }) }));
50
+ }
51
+ const selectedTeam = teams[selectedIndex];
52
+ return (_jsxs(Box, { flexDirection: "column", borderStyle: "round", borderColor: "gray", paddingX: 2, paddingY: 1, children: [_jsx(Box, { marginBottom: 1, children: _jsx(Text, { bold: true, children: "Select Team" }) }), _jsx(Box, { marginBottom: 1, children: _jsx(Text, { dimColor: true, children: "Choose a team to start a conversation with" }) }), _jsx(Box, { flexDirection: "column", children: teams.map((team, index) => (_jsx(Box, { marginBottom: 0, children: _jsxs(Text, { color: index === selectedIndex ? 'green' : undefined, children: [index === selectedIndex ? '❯ ' : ' ', index + 1, ". ", team.name, team.strategy ? ` (${team.strategy})` : ''] }) }, team.name))) }), selectedTeam &&
53
+ (selectedTeam.description || selectedTeam.members_count) && (_jsx(Box, { marginTop: 1, paddingLeft: 2, children: _jsx(Text, { dimColor: true, wrap: "wrap", children: selectedTeam.description ||
54
+ `Members: ${selectedTeam.members_count}` }) })), _jsx(Box, { marginTop: 1, children: _jsx(Text, { dimColor: true, children: "Enter to confirm \u00B7 Number to select \u00B7 Esc to exit" }) })] }));
55
+ }
@@ -0,0 +1,8 @@
1
+ import { Tool, ArkApiClient } from '../lib/arkApiClient.js';
2
+ interface ToolSelectorProps {
3
+ arkApiClient: ArkApiClient;
4
+ onSelect: (tool: Tool) => void;
5
+ onExit: () => void;
6
+ }
7
+ export declare function ToolSelector({ arkApiClient, onSelect, onExit, }: ToolSelectorProps): import("react/jsx-runtime").JSX.Element;
8
+ export {};
@@ -0,0 +1,53 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useState, useEffect } from 'react';
3
+ import { Box, Text, useInput } from 'ink';
4
+ export function ToolSelector({ arkApiClient, onSelect, onExit, }) {
5
+ const [selectedIndex, setSelectedIndex] = useState(0);
6
+ const [tools, setTools] = useState([]);
7
+ const [loading, setLoading] = useState(true);
8
+ const [error, setError] = useState(null);
9
+ useEffect(() => {
10
+ arkApiClient
11
+ .getTools()
12
+ .then((fetchedTools) => {
13
+ setTools(fetchedTools);
14
+ setLoading(false);
15
+ })
16
+ .catch((err) => {
17
+ setError(err.message || 'Failed to fetch tools');
18
+ setLoading(false);
19
+ });
20
+ }, [arkApiClient]);
21
+ useInput((input, key) => {
22
+ if (key.escape) {
23
+ onExit();
24
+ }
25
+ else if (key.upArrow || input === 'k') {
26
+ setSelectedIndex((prev) => (prev === 0 ? tools.length - 1 : prev - 1));
27
+ }
28
+ else if (key.downArrow || input === 'j') {
29
+ setSelectedIndex((prev) => (prev === tools.length - 1 ? 0 : prev + 1));
30
+ }
31
+ else if (key.return) {
32
+ onSelect(tools[selectedIndex]);
33
+ }
34
+ else {
35
+ // Handle number keys for quick selection
36
+ const num = parseInt(input, 10);
37
+ if (!isNaN(num) && num >= 1 && num <= tools.length) {
38
+ onSelect(tools[num - 1]);
39
+ }
40
+ }
41
+ });
42
+ if (loading) {
43
+ return (_jsx(Box, { children: _jsx(Text, { children: "Loading tools..." }) }));
44
+ }
45
+ if (error) {
46
+ return (_jsx(Box, { children: _jsxs(Text, { color: "red", children: ["Error: ", error] }) }));
47
+ }
48
+ if (tools.length === 0) {
49
+ return (_jsx(Box, { children: _jsx(Text, { children: "No tools available" }) }));
50
+ }
51
+ const selectedTool = tools[selectedIndex];
52
+ return (_jsxs(Box, { flexDirection: "column", borderStyle: "round", borderColor: "gray", paddingX: 2, paddingY: 1, children: [_jsx(Box, { marginBottom: 1, children: _jsx(Text, { bold: true, children: "Select Tool" }) }), _jsx(Box, { marginBottom: 1, children: _jsx(Text, { dimColor: true, children: "Choose a tool to start a conversation with" }) }), _jsx(Box, { flexDirection: "column", children: tools.map((tool, index) => (_jsx(Box, { marginBottom: 0, children: _jsxs(Text, { color: index === selectedIndex ? 'green' : undefined, children: [index === selectedIndex ? '❯ ' : ' ', index + 1, ". ", tool.name] }) }, tool.name))) }), selectedTool && selectedTool.description && (_jsx(Box, { marginTop: 1, paddingLeft: 2, children: _jsx(Text, { dimColor: true, wrap: "wrap", children: selectedTool.description }) })), _jsx(Box, { marginTop: 1, children: _jsx(Text, { dimColor: true, children: "Enter to confirm \u00B7 Number to select \u00B7 Esc to exit" }) })] }));
53
+ }
@@ -1,12 +1,24 @@
1
- import { StatusData } from '../lib/types.js';
1
+ export type StatusColor = 'green' | 'red' | 'yellow' | 'gray' | 'white' | 'cyan' | 'bold';
2
+ export interface StatusLine {
3
+ icon: string;
4
+ iconColor?: StatusColor;
5
+ status: string;
6
+ statusColor?: StatusColor;
7
+ name: string;
8
+ nameColor?: StatusColor;
9
+ details?: string;
10
+ subtext?: string;
11
+ }
12
+ export interface StatusSection {
13
+ title: string;
14
+ lines: StatusLine[];
15
+ }
16
+ /**
17
+ * Simple status formatter that just formats sections and lines
18
+ * The caller is responsible for deciding what to show
19
+ */
2
20
  export declare class StatusFormatter {
3
- /**
4
- * Print status check results to console
5
- */
6
- static printStatus(statusData: StatusData & {
7
- clusterAccess?: boolean;
8
- clusterInfo?: any;
9
- }): void;
10
- private static printService;
11
- private static printDependency;
21
+ static printSections(sections: StatusSection[]): void;
22
+ private static applyColor;
23
+ private static printLine;
12
24
  }
@@ -1,119 +1,47 @@
1
1
  import chalk from 'chalk';
2
+ /**
3
+ * Simple status formatter that just formats sections and lines
4
+ * The caller is responsible for deciding what to show
5
+ */
2
6
  export class StatusFormatter {
3
- /**
4
- * Print status check results to console
5
- */
6
- static printStatus(statusData) {
7
+ static printSections(sections) {
7
8
  console.log();
8
- // Print dependencies status first
9
- console.log(chalk.cyan.bold('system dependencies:'));
10
- for (const dep of statusData.dependencies) {
11
- StatusFormatter.printDependency(dep);
12
- }
13
- // Print cluster status
14
- console.log(chalk.cyan.bold('\ncluster access:'));
15
- if (statusData.clusterAccess && statusData.clusterInfo) {
16
- const clusterName = statusData.clusterInfo.context || 'kubernetes cluster';
17
- const clusterDetails = [];
18
- if (statusData.clusterInfo.type &&
19
- statusData.clusterInfo.type !== 'unknown') {
20
- clusterDetails.push(statusData.clusterInfo.type);
21
- }
22
- if (statusData.clusterInfo.ip) {
23
- clusterDetails.push(statusData.clusterInfo.ip);
24
- }
25
- console.log(` ${chalk.green('✓ accessible')} ${chalk.bold.white(clusterName)}${clusterDetails.length > 0 ? chalk.gray(' ' + clusterDetails.join(', ')) : ''}`);
26
- }
27
- else if (statusData.clusterAccess) {
28
- console.log(` ${chalk.green('✓ accessible')} ${chalk.bold('kubernetes cluster')}`);
29
- }
30
- else {
31
- console.log(` ${chalk.red('✗ unreachable')} ${chalk.bold('kubernetes cluster')}`);
32
- console.log(` ${chalk.gray('Install minikube: https://minikube.sigs.k8s.io/docs/start')}`);
33
- }
34
- // Only show ARK services if we have cluster access
35
- if (statusData.clusterAccess) {
36
- console.log(chalk.cyan.bold('\nark services:'));
37
- // Show all services except ark-controller (already shown in ark status)
38
- for (const service of statusData.services) {
39
- if (service.name !== 'ark-controller') {
40
- StatusFormatter.printService(service);
41
- }
42
- }
43
- }
44
- else {
45
- console.log(chalk.cyan.bold('\nark services:'));
46
- console.log(` ${chalk.gray('Cannot check ARK services - cluster not accessible')}`);
47
- }
48
- // Print ARK status section
49
- console.log(chalk.cyan.bold('\nark status:'));
50
- if (!statusData.clusterAccess) {
51
- console.log(` ${chalk.red('✗ no cluster access')}`);
52
- }
53
- else {
54
- // Show ark-controller status
55
- const controllerStatus = statusData.services?.find(s => s.name === 'ark-controller');
56
- if (controllerStatus) {
57
- // Show the actual status but replace "not installed" with "not ready" for display
58
- if (controllerStatus.status === 'not installed') {
59
- console.log(` ${chalk.yellow('○ not ready')} ${chalk.bold('ark-controller')} ${chalk.gray(controllerStatus.details || '')}`);
60
- }
61
- else if (controllerStatus.status === 'healthy') {
62
- // Controller is healthy - show ready status with model info
63
- if (!statusData.defaultModelExists) {
64
- console.log(` ${chalk.green('✓ ready')} ${chalk.bold('ark-controller')} ${chalk.gray(controllerStatus.details || '')}`);
65
- console.log(` ${chalk.gray(' (no default model configured)')}`);
66
- }
67
- else {
68
- console.log(` ${chalk.green('✓ ready')} ${chalk.bold('ark-controller')} ${chalk.gray(controllerStatus.details || '')}`);
69
- }
70
- }
71
- else {
72
- StatusFormatter.printService(controllerStatus);
73
- }
74
- }
75
- else {
76
- console.log(` ${chalk.yellow('○ not ready')} ${chalk.bold('ark-controller')}`);
9
+ sections.forEach((section, index) => {
10
+ console.log(chalk.cyan.bold(section.title));
11
+ section.lines.forEach((line) => this.printLine(line));
12
+ if (index < sections.length - 1) {
13
+ console.log();
77
14
  }
78
- }
15
+ });
79
16
  console.log();
80
17
  }
81
- static printService(service) {
82
- const statusColor = service.status === 'healthy'
83
- ? chalk.green('✓ healthy')
84
- : service.status === 'unhealthy'
85
- ? chalk.red('✗ unhealthy')
86
- : service.status === 'warning'
87
- ? chalk.yellow('⚠ warning')
88
- : service.status === 'not ready'
89
- ? chalk.yellow('○ not ready')
90
- : chalk.yellow('? not installed');
91
- // Show version and revision in grey after the name for healthy services
92
- let versionInfo = '';
93
- if (service.status === 'healthy' && (service.version || service.revision)) {
94
- const parts = [];
95
- if (service.version)
96
- parts.push(`v${service.version}`);
97
- if (service.revision)
98
- parts.push(`revision ${service.revision}`);
99
- versionInfo = chalk.gray(` ${parts.join(', ')}`);
100
- }
101
- // Show details inline in grey for all statuses
102
- let inlineDetails = '';
103
- if (service.details) {
104
- inlineDetails = chalk.gray(` ${service.details}`);
105
- }
106
- console.log(` ${statusColor} ${chalk.bold(service.name)}${versionInfo}${inlineDetails}`);
18
+ static applyColor(text, color) {
19
+ if (!color)
20
+ return text;
21
+ const colorMap = {
22
+ green: chalk.green,
23
+ red: chalk.red,
24
+ yellow: chalk.yellow,
25
+ gray: chalk.gray,
26
+ white: chalk.white,
27
+ cyan: chalk.cyan,
28
+ bold: chalk.bold,
29
+ };
30
+ return colorMap[color](text);
107
31
  }
108
- static printDependency(dep) {
109
- const statusColor = dep.installed
110
- ? chalk.green('✓ installed')
111
- : chalk.red('✗ missing');
112
- const versionText = dep.version ? chalk.gray(` ${dep.version}`) : '';
113
- console.log(` ${statusColor} ${chalk.bold(dep.name)}${versionText}`);
114
- if (dep.details && !dep.installed) {
115
- // Only show details if there's an issue
116
- console.log(` ${chalk.gray(dep.details)}`);
32
+ static printLine(line) {
33
+ const icon = this.applyColor(line.icon, line.iconColor);
34
+ const status = this.applyColor(line.status, line.statusColor);
35
+ // Name formatting is now handled where the name is assembled
36
+ const name = this.applyColor(line.name, line.nameColor || 'white');
37
+ const parts = [
38
+ ` ${icon} ${status}`,
39
+ name,
40
+ line.details ? chalk.gray(line.details) : '',
41
+ ].filter(Boolean);
42
+ console.log(parts.join(' '));
43
+ if (line.subtext) {
44
+ console.log(` ${chalk.gray(line.subtext)}`);
117
45
  }
118
46
  }
119
47
  }
@@ -0,0 +1 @@
1
+ export {};