cdd-cli 3.2.0 → 4.0.0

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 (136) hide show
  1. package/.atl/skill-registry.md +30 -0
  2. package/.editorconfig +12 -0
  3. package/.eslintrc.json +1 -1
  4. package/.prettierignore +4 -0
  5. package/.prettierrc +8 -0
  6. package/.~lock.ROADMAP.md# +1 -0
  7. package/CHANGELOG.md +80 -0
  8. package/README.es.md +198 -0
  9. package/README.md +81 -23
  10. package/ROADMAP.md +647 -0
  11. package/SECURITY.md +5 -0
  12. package/__mocks__/ink.cjs +17 -0
  13. package/coverage/clover.xml +471 -500
  14. package/coverage/coverage-final.json +16 -16
  15. package/coverage/lcov-report/ContainerCreationPrompt.jsx.html +400 -0
  16. package/coverage/lcov-report/ControlsHUD.jsx.html +256 -0
  17. package/coverage/lcov-report/components/ContainerCreationPrompt.jsx.html +394 -0
  18. package/coverage/lcov-report/components/PromptField.jsx.html +229 -0
  19. package/coverage/lcov-report/components/SuggestionPanel.jsx.html +268 -0
  20. package/coverage/lcov-report/components/index.html +146 -0
  21. package/coverage/lcov-report/helpers/constants.js.html +139 -22
  22. package/coverage/lcov-report/helpers/containerOptionsBuilder.js.html +75 -42
  23. package/coverage/lcov-report/helpers/dockerHubService.js.html +310 -0
  24. package/coverage/lcov-report/helpers/dockerService/serviceComponents/containerActions.js.html +162 -84
  25. package/coverage/lcov-report/helpers/dockerService/serviceComponents/containerList.js.html +50 -32
  26. package/coverage/lcov-report/helpers/dockerService/serviceComponents/index.html +21 -21
  27. package/coverage/lcov-report/helpers/imageNameUtils.js.html +181 -0
  28. package/coverage/lcov-report/helpers/index.html +84 -54
  29. package/coverage/lcov-report/helpers/logger.js.html +68 -68
  30. package/coverage/lcov-report/helpers/safeCall.js.html +15 -15
  31. package/coverage/lcov-report/helpers/validationHelpers.js.html +66 -114
  32. package/coverage/lcov-report/hooks/creation/index.html +20 -20
  33. package/coverage/lcov-report/hooks/creation/useContainerActions.js.html +38 -17
  34. package/coverage/lcov-report/hooks/creation/useContainerCreation.js.html +593 -89
  35. package/coverage/lcov-report/hooks/creation/useLogsViewer.js.html +7 -7
  36. package/coverage/lcov-report/hooks/debug/index.html +5 -5
  37. package/coverage/lcov-report/hooks/debug/useDebugLogs.js.html +35 -20
  38. package/coverage/lcov-report/hooks/index.html +40 -40
  39. package/coverage/lcov-report/hooks/navigation/index.html +19 -19
  40. package/coverage/lcov-report/hooks/navigation/useContainerSelection.js.html +38 -29
  41. package/coverage/lcov-report/hooks/useContainerCommandRouter.js.html +119 -80
  42. package/coverage/lcov-report/hooks/useControls.js.html +306 -123
  43. package/coverage/lcov-report/hooks/useEraseConfirmation.js.html +8 -8
  44. package/coverage/lcov-report/hooks/useExitHandler.js.html +50 -41
  45. package/coverage/lcov-report/index.html +52 -67
  46. package/coverage/lcov.info +899 -952
  47. package/dist/App.js +18 -12
  48. package/dist/components/ContainerCreationPrompt.js +50 -13
  49. package/dist/components/ContainerList.js +3 -3
  50. package/dist/components/ContainerRow.js +20 -20
  51. package/dist/components/ContainerSection.js +3 -3
  52. package/dist/components/ControlsHUD.js +106 -0
  53. package/dist/components/Footer.js +2 -2
  54. package/dist/components/Header.js +7 -7
  55. package/dist/components/LogViewer.js +3 -3
  56. package/dist/components/MessageFeedback.js +2 -2
  57. package/dist/components/PromptField.js +4 -4
  58. package/dist/components/StatsBar.js +5 -5
  59. package/dist/components/SuggestionPanel.js +53 -0
  60. package/dist/components/UsageMenu.js +3 -3
  61. package/dist/helpers/appInfo.js +7 -7
  62. package/dist/helpers/constants.js +154 -1
  63. package/dist/helpers/containerOptionsBuilder.js +3 -3
  64. package/dist/helpers/dockerHubService.js +111 -0
  65. package/dist/helpers/dockerService/dockerService.js +1 -1
  66. package/dist/helpers/dockerService/serviceComponents/containerActions.js +99 -40
  67. package/dist/helpers/dockerService/serviceComponents/containerList.js +11 -11
  68. package/dist/helpers/dockerService/serviceComponents/containerLogs.js +9 -9
  69. package/dist/helpers/dockerService/serviceComponents/containerStats.js +5 -5
  70. package/dist/helpers/dockerService/serviceComponents/imageUtils.js +1 -1
  71. package/dist/helpers/exitWithMessage.js +10 -10
  72. package/dist/helpers/imageNameUtils.js +32 -0
  73. package/dist/helpers/logger.js +13 -13
  74. package/dist/helpers/safeCall.js +2 -2
  75. package/dist/helpers/validationHelpers.js +76 -21
  76. package/dist/hooks/creation/useContainerActions.js +28 -11
  77. package/dist/hooks/creation/useContainerCreation.js +452 -62
  78. package/dist/hooks/creation/useLogsViewer.js +2 -2
  79. package/dist/hooks/debug/useDebugLogs.js +4 -4
  80. package/dist/hooks/useContainerCommandRouter.js +14 -14
  81. package/dist/hooks/useContainers.js +3 -3
  82. package/dist/hooks/useControls.js +85 -38
  83. package/dist/hooks/useEraseConfirmation.js +3 -3
  84. package/dist/index.js +9 -2
  85. package/jest.config.cjs +7 -0
  86. package/package.json +7 -3
  87. package/src/App.jsx +39 -16
  88. package/src/components/ContainerCreationPrompt.jsx +54 -10
  89. package/src/components/ContainerList.jsx +3 -3
  90. package/src/components/ContainerRow.jsx +19 -16
  91. package/src/components/ContainerSection.jsx +3 -3
  92. package/src/components/ControlsHUD.jsx +66 -0
  93. package/src/components/Footer.jsx +2 -2
  94. package/src/components/Header.jsx +7 -7
  95. package/src/components/LogViewer.jsx +5 -3
  96. package/src/components/MessageFeedback.jsx +2 -2
  97. package/src/components/PromptField.jsx +4 -4
  98. package/src/components/StatsBar.jsx +11 -7
  99. package/src/components/SuggestionPanel.jsx +61 -0
  100. package/src/components/UsageMenu.jsx +18 -13
  101. package/src/helpers/appInfo.js +7 -7
  102. package/src/helpers/constants.js +48 -9
  103. package/src/helpers/containerOptionsBuilder.js +16 -5
  104. package/src/helpers/dockerHubService.js +75 -0
  105. package/src/helpers/dockerService/dockerService.js +1 -1
  106. package/src/helpers/dockerService/serviceComponents/containerActions.js +87 -61
  107. package/src/helpers/dockerService/serviceComponents/containerList.js +31 -25
  108. package/src/helpers/dockerService/serviceComponents/containerLogs.js +41 -26
  109. package/src/helpers/dockerService/serviceComponents/containerStats.js +13 -11
  110. package/src/helpers/dockerService/serviceComponents/imageUtils.js +5 -4
  111. package/src/helpers/exitWithMessage.js +13 -7
  112. package/src/helpers/imageNameUtils.js +32 -0
  113. package/src/helpers/logger.js +15 -16
  114. package/src/helpers/safeCall.js +2 -2
  115. package/src/helpers/validationHelpers.js +13 -29
  116. package/src/hooks/creation/useContainerActions.js +32 -12
  117. package/src/hooks/creation/useContainerCreation.js +293 -47
  118. package/src/hooks/creation/useLogsViewer.js +2 -2
  119. package/src/hooks/debug/useDebugLogs.js +15 -10
  120. package/src/hooks/navigation/useContainerSelection.js +17 -14
  121. package/src/hooks/useContainerCommandRouter.js +71 -58
  122. package/src/hooks/useContainers.js +4 -4
  123. package/src/hooks/useControls.js +135 -72
  124. package/src/hooks/useEraseConfirmation.js +3 -3
  125. package/src/hooks/useExitHandler.js +29 -26
  126. package/src/index.js +8 -4
  127. package/test/ContainerCreationPrompt.dom.test.js +313 -0
  128. package/test/ControlsHUD.dom.test.js +125 -0
  129. package/test/SuggestionPanel.dom.test.js +83 -0
  130. package/test/constants.test.js +41 -0
  131. package/test/dockerHubService.test.js +213 -0
  132. package/test/logger.test.js +58 -0
  133. package/test/resolveImageTag.test.js +26 -0
  134. package/test/useContainerCreation.dom.test.js +428 -2
  135. package/test/useContainerCreationHub.dom.test.js +184 -0
  136. package/test/useControls.dom.test.js +406 -4
package/dist/App.js CHANGED
@@ -9,17 +9,17 @@
9
9
  * // Render the app
10
10
  * <App />
11
11
  */
12
- import React from "react";
13
- import { Box, Text, Spacer } from "ink";
14
- import { useContainers } from "./hooks/useContainers.js";
15
- import { useControls } from "./hooks/useControls.js";
16
- import ContainerSection from "./components/ContainerSection.js";
17
- import MessageFeedback from "./components/MessageFeedback.js";
18
- import Header from "./components/Header.js";
19
- import LogViewer from "./components/LogViewer.js";
20
- import ContainerCreationPrompt from "./components/ContainerCreationPrompt.js";
21
- import UsageMenu from "./components/UsageMenu.js";
22
- import Footer from "./components/Footer.js";
12
+ import React from 'react';
13
+ import { Box, Text, Spacer } from 'ink';
14
+ import { useContainers } from './hooks/useContainers.js';
15
+ import { useControls } from './hooks/useControls.js';
16
+ import ContainerSection from './components/ContainerSection.js';
17
+ import MessageFeedback from './components/MessageFeedback.js';
18
+ import Header from './components/Header.js';
19
+ import LogViewer from './components/LogViewer.js';
20
+ import ContainerCreationPrompt from './components/ContainerCreationPrompt.js';
21
+ import UsageMenu from './components/UsageMenu.js';
22
+ import Footer from './components/Footer.js';
23
23
  export default function App() {
24
24
  var _useContainers = useContainers(),
25
25
  containers = _useContainers.containers;
@@ -32,7 +32,13 @@ export default function App() {
32
32
  portInput: controls.portInput,
33
33
  envInput: controls.envInput,
34
34
  message: controls.message,
35
- messageColor: controls.messageColor
35
+ messageColor: controls.messageColor,
36
+ suggestions: controls.creation.suggestions,
37
+ selectedSuggestionIndex: controls.creation.selectedSuggestionIndex,
38
+ visibleOffset: controls.creation.visibleOffset,
39
+ isSearchingHub: controls.creation.isSearchingHub,
40
+ hubResults: controls.creation.hubResults,
41
+ hasSuggestedEnv: controls.creation.hasSuggestedEnv
36
42
  });
37
43
  }
38
44
  return /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement(Box, {
@@ -1,6 +1,8 @@
1
- import React from "react";
2
- import { Box, Text } from "ink";
3
- import { PromptField, PromptMessage } from "./PromptField.js";
1
+ import React from 'react';
2
+ import { Box } from 'ink';
3
+ import { PromptField, PromptMessage } from './PromptField.js';
4
+ import { SuggestionPanel } from './SuggestionPanel.js';
5
+ import { ControlsHUD } from './ControlsHUD.js';
4
6
  import PropTypes from 'prop-types';
5
7
 
6
8
  /**
@@ -16,6 +18,9 @@ import PropTypes from 'prop-types';
16
18
  * @param {string} props.envInput - Value for the environment variables field
17
19
  * @param {string} props.message - Contextual message to display (errors/hints)
18
20
  * @param {string} props.messageColor - Color for the contextual message
21
+ * @param {string[]} [props.suggestions] - Autocomplete suggestions (shown only on step 0)
22
+ * @param {number} [props.selectedSuggestionIndex] - Currently focused suggestion index
23
+ * @param {number} [props.visibleOffset] - First visible suggestion offset
19
24
  * @returns {JSX.Element}
20
25
  */
21
26
  export default function ContainerCreationPrompt(props) {
@@ -25,21 +30,33 @@ export default function ContainerCreationPrompt(props) {
25
30
  portInput = props.portInput,
26
31
  envInput = props.envInput,
27
32
  message = props.message,
28
- messageColor = props.messageColor;
33
+ messageColor = props.messageColor,
34
+ _props$suggestions = props.suggestions,
35
+ suggestions = _props$suggestions === void 0 ? [] : _props$suggestions,
36
+ _props$selectedSugges = props.selectedSuggestionIndex,
37
+ selectedSuggestionIndex = _props$selectedSugges === void 0 ? -1 : _props$selectedSugges,
38
+ _props$visibleOffset = props.visibleOffset,
39
+ visibleOffset = _props$visibleOffset === void 0 ? 0 : _props$visibleOffset,
40
+ _props$hubResults = props.hubResults,
41
+ hubResults = _props$hubResults === void 0 ? null : _props$hubResults,
42
+ _props$isSearchingHub = props.isSearchingHub,
43
+ isSearchingHub = _props$isSearchingHub === void 0 ? false : _props$isSearchingHub,
44
+ _props$hasSuggestedEn = props.hasSuggestedEnv,
45
+ hasSuggestedEnv = _props$hasSuggestedEn === void 0 ? false : _props$hasSuggestedEn;
29
46
  var prompts = [{
30
- label: "Name of the image to create (e.g., nginx:latest):",
47
+ label: 'Name of the image to create (e.g., nginx:1.27-alpine):',
31
48
  value: imageName,
32
49
  required: true
33
50
  }, {
34
- label: "Name of the container (optional):",
51
+ label: 'Name of the container (optional):',
35
52
  value: containerName,
36
53
  required: false
37
54
  }, {
38
- label: "Ports (optional, format 8080:80,443:443):",
55
+ label: 'Ports (optional, format 8080:80,443:443):',
39
56
  value: portInput,
40
57
  required: false
41
58
  }, {
42
- label: "Environment variables (optional, format VAR1=val1,VAR2=val2):",
59
+ label: 'Environment variables (optional, format VAR1=val1,VAR2=val2):',
43
60
  value: envInput,
44
61
  required: false
45
62
  }];
@@ -47,6 +64,9 @@ export default function ContainerCreationPrompt(props) {
47
64
  label = _ref.label,
48
65
  value = _ref.value,
49
66
  required = _ref.required;
67
+ var activeItems = hubResults !== null && hubResults !== void 0 ? hubResults : suggestions;
68
+ var showSuggestions = step === 0 && (isSearchingHub || activeItems.length > 0);
69
+ var hasSuggestions = (suggestions === null || suggestions === void 0 ? void 0 : suggestions.length) > 0 || (hubResults === null || hubResults === void 0 ? void 0 : hubResults.length) > 0;
50
70
  return /*#__PURE__*/React.createElement(Box, {
51
71
  flexDirection: "column",
52
72
  borderStyle: "round",
@@ -56,12 +76,20 @@ export default function ContainerCreationPrompt(props) {
56
76
  label: label,
57
77
  value: value,
58
78
  required: required
79
+ }), showSuggestions && /*#__PURE__*/React.createElement(SuggestionPanel, {
80
+ items: activeItems,
81
+ selectedIndex: selectedSuggestionIndex,
82
+ visibleOffset: visibleOffset,
83
+ isLoading: isSearchingHub
59
84
  }), /*#__PURE__*/React.createElement(PromptMessage, {
60
85
  message: message,
61
86
  color: messageColor
62
- }), /*#__PURE__*/React.createElement(Text, {
63
- dimColor: true
64
- }, "Press Enter to continue, Escape to cancel"));
87
+ }), /*#__PURE__*/React.createElement(ControlsHUD, {
88
+ step: step,
89
+ hasSuggestions: hasSuggestions,
90
+ isSearchingHub: isSearchingHub,
91
+ hasSuggestedEnv: hasSuggestedEnv
92
+ }));
65
93
  }
66
94
  ContainerCreationPrompt.propTypes = {
67
95
  step: PropTypes.number.isRequired,
@@ -70,5 +98,14 @@ ContainerCreationPrompt.propTypes = {
70
98
  portInput: PropTypes.string,
71
99
  envInput: PropTypes.string,
72
100
  message: PropTypes.string,
73
- messageColor: PropTypes.string
74
- };
101
+ messageColor: PropTypes.string,
102
+ suggestions: PropTypes.arrayOf(PropTypes.string),
103
+ selectedSuggestionIndex: PropTypes.number,
104
+ visibleOffset: PropTypes.number,
105
+ hubResults: PropTypes.arrayOf(PropTypes.string),
106
+ isSearchingHub: PropTypes.bool,
107
+ hasSuggestedEnv: PropTypes.bool
108
+ };
109
+
110
+ // Named export for test compatibility with jest ESM interop
111
+ export { ContainerCreationPrompt };
@@ -9,10 +9,10 @@
9
9
  * // Render with containers
10
10
  * <ContainerList containers={containers} />
11
11
  */
12
- import React from "react";
12
+ import React from 'react';
13
13
  import PropTypes from 'prop-types';
14
- import { Box } from "ink";
15
- import ContainerRow from "./ContainerRow.js";
14
+ import { Box } from 'ink';
15
+ import ContainerRow from './ContainerRow.js';
16
16
  export default function ContainerList(_ref) {
17
17
  var containers = _ref.containers,
18
18
  selected = _ref.selected;
@@ -1,24 +1,24 @@
1
- import React from "react";
2
- import { Box, Text } from "ink";
3
- import StatsBar from "./StatsBar.js";
1
+ import React from 'react';
2
+ import { Box, Text } from 'ink';
3
+ import StatsBar from './StatsBar.js';
4
4
  import PropTypes from 'prop-types';
5
5
  import { useContainerStats } from '../hooks/useContainerStats.js';
6
6
  var stateText = function stateText(state) {
7
- if (state === "running") return {
8
- text: "🟢 RUNNING",
9
- color: "green"
7
+ if (state === 'running') return {
8
+ text: '🟢 RUNNING',
9
+ color: 'green'
10
10
  };
11
- if (state === "exited") return {
12
- text: "🔴 EXITED",
13
- color: "red"
11
+ if (state === 'exited') return {
12
+ text: '🔴 EXITED',
13
+ color: 'red'
14
14
  };
15
- if (state === "paused") return {
16
- text: "🟠 PAUSED",
17
- color: "yellow"
15
+ if (state === 'paused') return {
16
+ text: '🟠 PAUSED',
17
+ color: 'yellow'
18
18
  };
19
19
  return {
20
20
  text: state.toUpperCase(),
21
- color: "gray"
21
+ color: 'gray'
22
22
  };
23
23
  };
24
24
 
@@ -43,11 +43,11 @@ export default function ContainerRow(_ref) {
43
43
 
44
44
  // Format ports for display (no leading space to avoid layout shifts)
45
45
  var formatPorts = function formatPorts(ports) {
46
- if (!ports || ports.length === 0) return "";
46
+ if (!ports || ports.length === 0) return '';
47
47
  if (Array.isArray(ports)) {
48
48
  return ports.map(function (p, _i) {
49
49
  return "\uD83D\uDD17 ".concat(p);
50
- }).join(" ");
50
+ }).join(' ');
51
51
  }
52
52
  return "\uD83D\uDD17 ".concat(ports);
53
53
  };
@@ -55,8 +55,8 @@ export default function ContainerRow(_ref) {
55
55
  // Helper to truncate long strings to a max length without adding trailing spaces
56
56
  var truncate = function truncate(s) {
57
57
  var max = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 20;
58
- if (!s) return "";
59
- return s.length > max ? s.slice(0, max - 1) + "" : s;
58
+ if (!s) return '';
59
+ return s.length > max ? s.slice(0, max - 1) + '' : s;
60
60
  };
61
61
  var stateInfo = stateText(state);
62
62
  return /*#__PURE__*/React.createElement(Box, {
@@ -71,8 +71,8 @@ export default function ContainerRow(_ref) {
71
71
  justifyContent: "flex-end",
72
72
  paddingRight: 1
73
73
  }, /*#__PURE__*/React.createElement(Text, {
74
- color: isSelected ? "green" : undefined
75
- }, isSelected ? "" : " ")), /*#__PURE__*/React.createElement(Box, {
74
+ color: isSelected ? 'green' : undefined
75
+ }, isSelected ? '' : ' ')), /*#__PURE__*/React.createElement(Box, {
76
76
  width: 18,
77
77
  flexShrink: 1,
78
78
  paddingRight: 1
@@ -100,7 +100,7 @@ export default function ContainerRow(_ref) {
100
100
  }, formatPorts(container.ports))), /*#__PURE__*/React.createElement(Box, {
101
101
  flexShrink: 0,
102
102
  paddingLeft: 1
103
- }, state === "running" ? /*#__PURE__*/React.createElement(StatsBar, {
103
+ }, state === 'running' ? /*#__PURE__*/React.createElement(StatsBar, {
104
104
  cpu: parseFloat(stats.cpuPercent),
105
105
  mem: parseFloat(stats.memPercent)
106
106
  }) : null)), statsError && /*#__PURE__*/React.createElement(Text, {
@@ -1,6 +1,6 @@
1
- import React from "react";
2
- import { Text } from "ink";
3
- import ContainerList from "./ContainerList.js";
1
+ import React from 'react';
2
+ import { Text } from 'ink';
3
+ import ContainerList from './ContainerList.js';
4
4
  import PropTypes from 'prop-types';
5
5
 
6
6
  /**
@@ -0,0 +1,106 @@
1
+ import React from 'react';
2
+ import { Box, Text } from 'ink';
3
+
4
+ /**
5
+ * Returns the contextual keyboard hints for the current wizard state.
6
+ *
7
+ * @param {number} step - Current wizard step (0-3)
8
+ * @param {boolean} hasSuggestions - Whether suggestions/hub results are visible
9
+ * @param {boolean} isSearchingHub - Whether a Hub search is in progress
10
+ * @param {boolean} [hasSuggestedEnv=false] - Whether the image has suggestedEnv entries
11
+ * @returns {{ key: string, label: string }[]}
12
+ */
13
+ function getHints(step, hasSuggestions, isSearchingHub) {
14
+ var hasSuggestedEnv = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : false;
15
+ if (step === 0) {
16
+ if (hasSuggestions) {
17
+ return [{
18
+ key: '↑↓',
19
+ label: 'Navigate'
20
+ }, {
21
+ key: 'Enter',
22
+ label: 'Select'
23
+ }, {
24
+ key: 'Esc',
25
+ label: 'Cancel'
26
+ }];
27
+ }
28
+ var hints = [];
29
+ if (!isSearchingHub) hints.push({
30
+ key: 'Tab',
31
+ label: 'Search Hub'
32
+ });
33
+ hints.push({
34
+ key: '↑↓',
35
+ label: 'Browse'
36
+ });
37
+ hints.push({
38
+ key: 'Enter',
39
+ label: 'Confirm'
40
+ });
41
+ hints.push({
42
+ key: 'Esc',
43
+ label: 'Cancel'
44
+ });
45
+ return hints;
46
+ }
47
+ if (step === 3) {
48
+ var _hints = [];
49
+ if (hasSuggestedEnv) _hints.push({
50
+ key: 'Tab',
51
+ label: 'Insert next env'
52
+ });
53
+ _hints.push({
54
+ key: 'Enter',
55
+ label: 'Continue'
56
+ });
57
+ _hints.push({
58
+ key: 'Esc',
59
+ label: 'Cancel'
60
+ });
61
+ return _hints;
62
+ }
63
+ return [{
64
+ key: 'Enter',
65
+ label: 'Continue'
66
+ }, {
67
+ key: 'Esc',
68
+ label: 'Cancel'
69
+ }];
70
+ }
71
+
72
+ /**
73
+ * Displays a row of contextual keyboard shortcut hints for the active wizard step.
74
+ *
75
+ * @param {Object} props
76
+ * @param {number} props.step - Current wizard step
77
+ * @param {boolean} [props.hasSuggestions=false] - Whether suggestions/hub results are visible
78
+ * @param {boolean} [props.isSearchingHub=false] - Whether a Hub search is in progress
79
+ * @param {boolean} [props.hasSuggestedEnv=false] - Whether the image has suggestedEnv entries
80
+ */
81
+ function ControlsHUD(_ref) {
82
+ var step = _ref.step,
83
+ _ref$hasSuggestions = _ref.hasSuggestions,
84
+ hasSuggestions = _ref$hasSuggestions === void 0 ? false : _ref$hasSuggestions,
85
+ _ref$isSearchingHub = _ref.isSearchingHub,
86
+ isSearchingHub = _ref$isSearchingHub === void 0 ? false : _ref$isSearchingHub,
87
+ _ref$hasSuggestedEnv = _ref.hasSuggestedEnv,
88
+ hasSuggestedEnv = _ref$hasSuggestedEnv === void 0 ? false : _ref$hasSuggestedEnv;
89
+ var hints = getHints(step, hasSuggestions, isSearchingHub, hasSuggestedEnv);
90
+ return /*#__PURE__*/React.createElement(Box, {
91
+ flexDirection: "row",
92
+ columnGap: 2
93
+ }, hints.map(function (_ref2) {
94
+ var key = _ref2.key,
95
+ label = _ref2.label;
96
+ return /*#__PURE__*/React.createElement(Box, {
97
+ key: key
98
+ }, /*#__PURE__*/React.createElement(Text, {
99
+ color: "cyan"
100
+ }, "[", key, "]"), /*#__PURE__*/React.createElement(Text, {
101
+ dimColor: true
102
+ }, " ", label));
103
+ }));
104
+ }
105
+ export default ControlsHUD;
106
+ export { getHints, ControlsHUD };
@@ -1,5 +1,5 @@
1
- import React from "react";
2
- import { Box, Text } from "ink";
1
+ import React from 'react';
2
+ import { Box, Text } from 'ink';
3
3
 
4
4
  /**
5
5
  * Simple footer displayed at the bottom of the interface.
@@ -1,8 +1,8 @@
1
- import React from "react";
2
- import { Box, Text } from "ink";
3
- import chalk from "chalk";
1
+ import React from 'react';
2
+ import { Box, Text } from 'ink';
3
+ import chalk from 'chalk';
4
4
  import PropTypes from 'prop-types';
5
- import { getAppVersion } from "../helpers/appInfo.js";
5
+ import { getAppVersion } from '../helpers/appInfo.js';
6
6
 
7
7
  /**
8
8
  * Header component displayed at the top of the CLI UI.
@@ -18,12 +18,12 @@ import { getAppVersion } from "../helpers/appInfo.js";
18
18
  var VERSION = getAppVersion();
19
19
  export default function Header(_ref) {
20
20
  var count = _ref.count;
21
- var formattedVersion = VERSION === "unknown" ? "unknown" : "v".concat(VERSION);
21
+ var formattedVersion = VERSION === 'unknown' ? 'unknown' : "v".concat(VERSION);
22
22
  return /*#__PURE__*/React.createElement(Box, {
23
23
  justifyContent: "space-between"
24
24
  }, /*#__PURE__*/React.createElement(Text, {
25
25
  color: "cyanBright"
26
- }, "\uD83D\uDC33 ", chalk.bold("CDD"), /*#__PURE__*/React.createElement(Text, {
26
+ }, "\uD83D\uDC33 ", chalk.bold('CDD'), /*#__PURE__*/React.createElement(Text, {
27
27
  color: "gray"
28
28
  }, " \u2014 CLI Docker Dashboard")), /*#__PURE__*/React.createElement(Box, {
29
29
  flexDirection: "column",
@@ -32,7 +32,7 @@ export default function Header(_ref) {
32
32
  color: "gray"
33
33
  }, formattedVersion), /*#__PURE__*/React.createElement(Text, {
34
34
  color: "gray"
35
- }, count, " container", count === 1 ? "" : "s", " found")));
35
+ }, count, " container", count === 1 ? '' : 's', " found")));
36
36
  }
37
37
  Header.propTypes = {
38
38
  count: PropTypes.number.isRequired
@@ -1,5 +1,5 @@
1
- import React from "react";
2
- import { Text } from "ink";
1
+ import React from 'react';
2
+ import { Text } from 'ink';
3
3
  import PropTypes from 'prop-types';
4
4
 
5
5
  /**
@@ -20,7 +20,7 @@ export default function LogViewer(_ref) {
20
20
  var visibleLogs = logs.slice(-15);
21
21
  return /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement(Text, {
22
22
  color: "green"
23
- }, (_container$name = container === null || container === void 0 ? void 0 : container.name) !== null && _container$name !== void 0 ? _container$name : "Container", " logs, press ESC to exit"), visibleLogs.length === 0 ? /*#__PURE__*/React.createElement(Text, {
23
+ }, (_container$name = container === null || container === void 0 ? void 0 : container.name) !== null && _container$name !== void 0 ? _container$name : 'Container', " logs, press ESC to exit"), visibleLogs.length === 0 ? /*#__PURE__*/React.createElement(Text, {
24
24
  dimColor: true
25
25
  }, "No logs...") : visibleLogs.map(function (line, idx) {
26
26
  return /*#__PURE__*/React.createElement(Text, {
@@ -1,5 +1,5 @@
1
- import React from "react";
2
- import { Box, Text } from "ink";
1
+ import React from 'react';
2
+ import { Box, Text } from 'ink';
3
3
  import PropTypes from 'prop-types';
4
4
 
5
5
  /**
@@ -1,5 +1,5 @@
1
- import React from "react";
2
- import { Text } from "ink";
1
+ import React from 'react';
2
+ import { Text } from 'ink';
3
3
  import PropTypes from 'prop-types';
4
4
 
5
5
  /**
@@ -19,7 +19,7 @@ export function PromptField(_ref) {
19
19
  // If the field is required and empty, show in red
20
20
  var isEmpty = required && !value.trim();
21
21
  return /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement(Text, null, label), /*#__PURE__*/React.createElement(Text, {
22
- color: isEmpty ? "red" : "cyan"
22
+ color: isEmpty ? 'red' : 'cyan'
23
23
  }, value, "_"));
24
24
  }
25
25
 
@@ -36,7 +36,7 @@ export function PromptMessage(_ref2) {
36
36
  color = _ref2.color;
37
37
  if (!message) return null;
38
38
  return /*#__PURE__*/React.createElement(Text, {
39
- color: color || "yellow"
39
+ color: color || 'yellow'
40
40
  }, message);
41
41
  }
42
42
  PromptField.propTypes = {
@@ -1,6 +1,6 @@
1
- import React from "react";
2
- import { Text } from "ink";
3
- import chalk from "chalk";
1
+ import React from 'react';
2
+ import { Text } from 'ink';
3
+ import chalk from 'chalk';
4
4
  import PropTypes from 'prop-types';
5
5
  var BAR_WIDTH = 8;
6
6
 
@@ -36,14 +36,14 @@ function makeBar(value, width) {
36
36
  var normalized = Number.isFinite(value) ? Math.min(Math.max(value, 0), 100) : 0;
37
37
  var filled = Math.round(normalized / 100 * safeWidth);
38
38
  var empty = Math.max(0, safeWidth - filled);
39
- var bar = "".repeat(filled) + "".repeat(empty);
39
+ var bar = ''.repeat(filled) + ''.repeat(empty);
40
40
  return colorize(normalized, bar);
41
41
  }
42
42
  function formatPercent(value) {
43
43
  if (!Number.isFinite(value)) return 0;
44
44
  var normalized = Math.min(Math.max(value, 0), 100);
45
45
  if (normalized >= 10) return Math.round(normalized);
46
- return normalized.toFixed(1).replace(/\.0$/, "");
46
+ return normalized.toFixed(1).replace(/\.0$/, '');
47
47
  }
48
48
 
49
49
  /**
@@ -0,0 +1,53 @@
1
+ import React from 'react';
2
+ import { Box, Text } from 'ink';
3
+ import PropTypes from 'prop-types';
4
+
5
+ /**
6
+ * Renders an autocomplete suggestion panel as a boxed list below the image input.
7
+ * Displays a window of `maxVisible` items starting at `visibleOffset`.
8
+ * The focused row (selectedIndex) is highlighted cyan with a › prefix.
9
+ *
10
+ * @param {Object} props
11
+ * @param {string[]} props.items - Full list of suggestion strings
12
+ * @param {number} props.selectedIndex - Currently focused index (-1 = none)
13
+ * @param {number} props.visibleOffset - First item index of the visible window
14
+ * @param {number} [props.maxVisible=6] - Maximum number of items visible at once
15
+ * @returns {JSX.Element|null}
16
+ */
17
+ export function SuggestionPanel(_ref) {
18
+ var items = _ref.items,
19
+ selectedIndex = _ref.selectedIndex,
20
+ visibleOffset = _ref.visibleOffset,
21
+ _ref$maxVisible = _ref.maxVisible,
22
+ maxVisible = _ref$maxVisible === void 0 ? 6 : _ref$maxVisible,
23
+ _ref$isLoading = _ref.isLoading,
24
+ isLoading = _ref$isLoading === void 0 ? false : _ref$isLoading;
25
+ var hasItems = items && items.length > 0;
26
+ if (!hasItems && !isLoading) return null;
27
+ var visible = hasItems ? items.slice(visibleOffset, visibleOffset + maxVisible) : [];
28
+ return /*#__PURE__*/React.createElement(Box, {
29
+ flexDirection: "column",
30
+ borderStyle: "single",
31
+ borderColor: "cyan",
32
+ paddingX: 1,
33
+ "data-testid": "suggestion-panel"
34
+ }, visible.map(function (item, localIdx) {
35
+ var globalIdx = visibleOffset + localIdx;
36
+ var isFocused = globalIdx === selectedIndex;
37
+ return /*#__PURE__*/React.createElement(Text, {
38
+ key: item,
39
+ color: isFocused ? 'cyan' : undefined,
40
+ dimColor: !isFocused
41
+ }, isFocused ? "\u203A ".concat(item) : " ".concat(item));
42
+ }), isLoading && /*#__PURE__*/React.createElement(Text, {
43
+ dimColor: true,
44
+ color: "gray"
45
+ }, ' [searching Docker Hub...]'));
46
+ }
47
+ SuggestionPanel.propTypes = {
48
+ items: PropTypes.arrayOf(PropTypes.string).isRequired,
49
+ selectedIndex: PropTypes.number.isRequired,
50
+ visibleOffset: PropTypes.number.isRequired,
51
+ maxVisible: PropTypes.number,
52
+ isLoading: PropTypes.bool
53
+ };
@@ -1,6 +1,6 @@
1
- import React from "react";
2
- import { Box, Text } from "ink";
3
- var SHORTCUTS = ["Use ↑/↓ for navigation", "I to initiate selected container", "P to stop selected container", "R to restart selected container", "C to create a container", "L to view logs of selected container", "D to toggle debug log panel", "E to remove selected container", "Q to quit"];
1
+ import React from 'react';
2
+ import { Box, Text } from 'ink';
3
+ var SHORTCUTS = ['Use ↑/↓ for navigation', 'I to initiate selected container', 'P to stop selected container', 'R to restart selected container', 'C to create a container', 'L to view logs of selected container', 'D to toggle debug log panel', 'E to remove selected container', 'Q to quit'];
4
4
  var GRID_COLUMNS = 3;
5
5
 
6
6
  /**
@@ -1,6 +1,6 @@
1
- import { readFileSync } from "fs";
2
- import path from "path";
3
- import { fileURLToPath } from "url";
1
+ import { readFileSync } from 'fs';
2
+ import path from 'path';
3
+ import { fileURLToPath } from 'url';
4
4
  var cachedVersion;
5
5
  export function getAppVersion() {
6
6
  if (cachedVersion) {
@@ -10,12 +10,12 @@ export function getAppVersion() {
10
10
  var _packageJson$version;
11
11
  var __filename = fileURLToPath(import.meta.url);
12
12
  var moduleDir = path.dirname(__filename);
13
- var packageJsonPath = path.resolve(moduleDir, "../../package.json");
14
- var packageJson = JSON.parse(readFileSync(packageJsonPath, "utf8"));
15
- cachedVersion = (_packageJson$version = packageJson.version) !== null && _packageJson$version !== void 0 ? _packageJson$version : "unknown";
13
+ var packageJsonPath = path.resolve(moduleDir, '../../package.json');
14
+ var packageJson = JSON.parse(readFileSync(packageJsonPath, 'utf8'));
15
+ cachedVersion = (_packageJson$version = packageJson.version) !== null && _packageJson$version !== void 0 ? _packageJson$version : 'unknown';
16
16
  return cachedVersion;
17
17
  } catch (error) {
18
- cachedVersion = "unknown";
18
+ cachedVersion = 'unknown';
19
19
  return cachedVersion;
20
20
  }
21
21
  }