cdd-cli 4.0.0 → 4.2.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 (135) hide show
  1. package/README.es.md +60 -26
  2. package/README.md +60 -26
  3. package/dist/App.js +1 -0
  4. package/dist/components/ContainerCreationPrompt.js +14 -0
  5. package/dist/components/PromptField.js +13 -6
  6. package/dist/components/TextField.js +54 -0
  7. package/dist/components/UsageMenu.js +1 -1
  8. package/dist/helpers/appState.js +26 -0
  9. package/dist/helpers/dockerService/serviceComponents/containerExec.js +211 -0
  10. package/dist/helpers/textEditing.js +293 -0
  11. package/dist/hooks/creation/useContainerCreation.js +132 -15
  12. package/dist/hooks/useContainerCommandRouter.js +12 -2
  13. package/dist/hooks/useControls.js +21 -26
  14. package/dist/hooks/useShellMode.js +101 -0
  15. package/dist/index.js +3 -1
  16. package/package.json +4 -1
  17. package/.atl/skill-registry.md +0 -30
  18. package/.editorconfig +0 -12
  19. package/.eslintrc.json +0 -30
  20. package/.github/ISSUE_TEMPLATE/bug_report.md +0 -30
  21. package/.github/ISSUE_TEMPLATE/feature_request.md +0 -19
  22. package/.github/PULL_REQUEST_TEMPLATE/pull_request.md +0 -14
  23. package/.github/workflows/ci.yml +0 -32
  24. package/.github/workflows/docs.yml +0 -42
  25. package/.prettierignore +0 -4
  26. package/.prettierrc +0 -8
  27. package/.~lock.ROADMAP.md# +0 -1
  28. package/CHANGELOG.md +0 -216
  29. package/CODE_OF_CONDUCT.md +0 -12
  30. package/CONTRIBUTING.md +0 -35
  31. package/ROADMAP.md +0 -647
  32. package/SECURITY.md +0 -5
  33. package/__mocks__/ink.cjs +0 -17
  34. package/_config.yml +0 -13
  35. package/babel.config.cjs +0 -23
  36. package/coverage/clover.xml +0 -575
  37. package/coverage/coverage-final.json +0 -17
  38. package/coverage/lcov-report/ContainerCreationPrompt.jsx.html +0 -400
  39. package/coverage/lcov-report/ControlsHUD.jsx.html +0 -256
  40. package/coverage/lcov-report/base.css +0 -224
  41. package/coverage/lcov-report/block-navigation.js +0 -87
  42. package/coverage/lcov-report/components/ContainerCreationPrompt.jsx.html +0 -394
  43. package/coverage/lcov-report/components/PromptField.jsx.html +0 -229
  44. package/coverage/lcov-report/components/SuggestionPanel.jsx.html +0 -268
  45. package/coverage/lcov-report/components/index.html +0 -146
  46. package/coverage/lcov-report/favicon.png +0 -0
  47. package/coverage/lcov-report/helpers/constants.js.html +0 -352
  48. package/coverage/lcov-report/helpers/containerOptionsBuilder.js.html +0 -244
  49. package/coverage/lcov-report/helpers/dockerHubService.js.html +0 -310
  50. package/coverage/lcov-report/helpers/dockerService/serviceComponents/containerActions.js.html +0 -931
  51. package/coverage/lcov-report/helpers/dockerService/serviceComponents/containerList.js.html +0 -307
  52. package/coverage/lcov-report/helpers/dockerService/serviceComponents/index.html +0 -131
  53. package/coverage/lcov-report/helpers/imageNameUtils.js.html +0 -181
  54. package/coverage/lcov-report/helpers/index.html +0 -206
  55. package/coverage/lcov-report/helpers/logger.js.html +0 -424
  56. package/coverage/lcov-report/helpers/safeCall.js.html +0 -139
  57. package/coverage/lcov-report/helpers/validationHelpers.js.html +0 -346
  58. package/coverage/lcov-report/hooks/creation/index.html +0 -146
  59. package/coverage/lcov-report/hooks/creation/useContainerActions.js.html +0 -313
  60. package/coverage/lcov-report/hooks/creation/useContainerCreation.js.html +0 -982
  61. package/coverage/lcov-report/hooks/creation/useLogsViewer.js.html +0 -238
  62. package/coverage/lcov-report/hooks/debug/index.html +0 -116
  63. package/coverage/lcov-report/hooks/debug/useDebugLogs.js.html +0 -187
  64. package/coverage/lcov-report/hooks/index.html +0 -161
  65. package/coverage/lcov-report/hooks/navigation/index.html +0 -116
  66. package/coverage/lcov-report/hooks/navigation/useContainerSelection.js.html +0 -169
  67. package/coverage/lcov-report/hooks/useContainerCommandRouter.js.html +0 -454
  68. package/coverage/lcov-report/hooks/useControls.js.html +0 -868
  69. package/coverage/lcov-report/hooks/useEraseConfirmation.js.html +0 -202
  70. package/coverage/lcov-report/hooks/useExitHandler.js.html +0 -292
  71. package/coverage/lcov-report/index.html +0 -176
  72. package/coverage/lcov-report/prettify.css +0 -1
  73. package/coverage/lcov-report/prettify.js +0 -2
  74. package/coverage/lcov-report/sort-arrow-sprite.png +0 -0
  75. package/coverage/lcov-report/sorter.js +0 -210
  76. package/coverage/lcov.info +0 -1163
  77. package/fix-imports.cjs +0 -54
  78. package/jest.config.cjs +0 -14
  79. package/src/App.jsx +0 -102
  80. package/src/components/ContainerCreationPrompt.jsx +0 -107
  81. package/src/components/ContainerList.jsx +0 -32
  82. package/src/components/ContainerRow.jsx +0 -84
  83. package/src/components/ContainerSection.jsx +0 -24
  84. package/src/components/ControlsHUD.jsx +0 -66
  85. package/src/components/Footer.jsx +0 -15
  86. package/src/components/Header.jsx +0 -41
  87. package/src/components/LogViewer.jsx +0 -36
  88. package/src/components/MessageFeedback.jsx +0 -25
  89. package/src/components/PromptField.jsx +0 -48
  90. package/src/components/StatsBar.jsx +0 -70
  91. package/src/components/SuggestionPanel.jsx +0 -61
  92. package/src/components/UsageMenu.jsx +0 -52
  93. package/src/helpers/appInfo.js +0 -23
  94. package/src/helpers/constants.js +0 -89
  95. package/src/helpers/containerOptionsBuilder.js +0 -53
  96. package/src/helpers/dockerHubService.js +0 -75
  97. package/src/helpers/dockerService/dockerService.js +0 -28
  98. package/src/helpers/dockerService/serviceComponents/containerActions.js +0 -282
  99. package/src/helpers/dockerService/serviceComponents/containerList.js +0 -74
  100. package/src/helpers/dockerService/serviceComponents/containerLogs.js +0 -60
  101. package/src/helpers/dockerService/serviceComponents/containerStats.js +0 -69
  102. package/src/helpers/dockerService/serviceComponents/imageUtils.js +0 -32
  103. package/src/helpers/exitWithMessage.js +0 -32
  104. package/src/helpers/imageNameUtils.js +0 -32
  105. package/src/helpers/logger.js +0 -112
  106. package/src/helpers/safeCall.js +0 -18
  107. package/src/helpers/validationHelpers.js +0 -87
  108. package/src/hooks/creation/useContainerActions.js +0 -89
  109. package/src/hooks/creation/useContainerCreation.js +0 -377
  110. package/src/hooks/creation/useLogsViewer.js +0 -51
  111. package/src/hooks/debug/useDebugLogs.js +0 -34
  112. package/src/hooks/navigation/useContainerSelection.js +0 -28
  113. package/src/hooks/useContainerCommandRouter.js +0 -123
  114. package/src/hooks/useContainerStats.js +0 -44
  115. package/src/hooks/useContainers.js +0 -26
  116. package/src/hooks/useControls.js +0 -263
  117. package/src/hooks/useEraseConfirmation.js +0 -39
  118. package/src/hooks/useExitHandler.js +0 -69
  119. package/src/index.js +0 -20
  120. package/test/ContainerCreationPrompt.dom.test.js +0 -313
  121. package/test/ControlsHUD.dom.test.js +0 -125
  122. package/test/SuggestionPanel.dom.test.js +0 -83
  123. package/test/constants.test.js +0 -41
  124. package/test/containerActions.test.js +0 -229
  125. package/test/containerList.test.js +0 -79
  126. package/test/containerOptionsBuilder.test.js +0 -36
  127. package/test/dockerHubService.test.js +0 -213
  128. package/test/jest.setup.js +0 -7
  129. package/test/logger.test.js +0 -58
  130. package/test/resolveImageTag.test.js +0 -26
  131. package/test/safeCall.test.js +0 -25
  132. package/test/useContainerCreation.dom.test.js +0 -567
  133. package/test/useContainerCreationHub.dom.test.js +0 -184
  134. package/test/useControls.dom.test.js +0 -519
  135. package/test/validationHelpers.test.js +0 -111
@@ -0,0 +1,293 @@
1
+ function _typeof(o) { "@babel/helpers - typeof"; return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (o) { return typeof o; } : function (o) { return o && "function" == typeof Symbol && o.constructor === Symbol && o !== Symbol.prototype ? "symbol" : typeof o; }, _typeof(o); }
2
+ function ownKeys(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbols) { var o = Object.getOwnPropertySymbols(e); r && (o = o.filter(function (r) { return Object.getOwnPropertyDescriptor(e, r).enumerable; })), t.push.apply(t, o); } return t; }
3
+ function _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? ownKeys(Object(t), !0).forEach(function (r) { _defineProperty(e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; }
4
+ function _defineProperty(e, r, t) { return (r = _toPropertyKey(r)) in e ? Object.defineProperty(e, r, { value: t, enumerable: !0, configurable: !0, writable: !0 }) : e[r] = t, e; }
5
+ function _toPropertyKey(t) { var i = _toPrimitive(t, "string"); return "symbol" == _typeof(i) ? i : i + ""; }
6
+ function _toPrimitive(t, r) { if ("object" != _typeof(t) || !t) return t; var e = t[Symbol.toPrimitive]; if (void 0 !== e) { var i = e.call(t, r || "default"); if ("object" != _typeof(i)) return i; throw new TypeError("@@toPrimitive must return a primitive value."); } return ("string" === r ? String : Number)(t); }
7
+ function _toConsumableArray(r) { return _arrayWithoutHoles(r) || _iterableToArray(r) || _unsupportedIterableToArray(r) || _nonIterableSpread(); }
8
+ function _nonIterableSpread() { throw new TypeError("Invalid attempt to spread non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); }
9
+ function _unsupportedIterableToArray(r, a) { if (r) { if ("string" == typeof r) return _arrayLikeToArray(r, a); var t = {}.toString.call(r).slice(8, -1); return "Object" === t && r.constructor && (t = r.constructor.name), "Map" === t || "Set" === t ? Array.from(r) : "Arguments" === t || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(t) ? _arrayLikeToArray(r, a) : void 0; } }
10
+ function _iterableToArray(r) { if ("undefined" != typeof Symbol && null != r[Symbol.iterator] || null != r["@@iterator"]) return Array.from(r); }
11
+ function _arrayWithoutHoles(r) { if (Array.isArray(r)) return _arrayLikeToArray(r); }
12
+ function _arrayLikeToArray(r, a) { (null == a || a > r.length) && (a = r.length); for (var e = 0, n = Array(a); e < a; e++) n[e] = r[e]; return n; }
13
+ /**
14
+ * @typedef {Object} TextState
15
+ * @property {string} value - Current field content
16
+ * @property {number} cursor - Cursor position (0..value.length)
17
+ */
18
+
19
+ /**
20
+ * @typedef {Object} EditResult
21
+ * @property {TextState} state - Resulting state (same object if no change)
22
+ * @property {boolean} handled - true if the key was consumed by the editor
23
+ */
24
+
25
+ var BREAK_CHARS = ' \t./\\,;:|(){}[]<>!@#$%^&*+=~`"\'-';
26
+
27
+ /**
28
+ * Splits a string into code points (handles emojis via Array.from).
29
+ * @param {string} s
30
+ * @returns {string[]}
31
+ */
32
+ function chars(s) {
33
+ return Array.from(s);
34
+ }
35
+
36
+ /**
37
+ * Returns the index of the start of the word before cursor.
38
+ * A word boundary is any non-alphanumeric, non-underscore character.
39
+ *
40
+ * @param {string} value
41
+ * @param {number} cursor
42
+ * @returns {number}
43
+ */
44
+ export function wordStartBefore(value, cursor) {
45
+ var arr = chars(value);
46
+ if (cursor <= 0) return 0;
47
+ var i = cursor - 1;
48
+ // Skip trailing non-word chars
49
+ while (i > 0 && BREAK_CHARS.includes(arr[i])) i--;
50
+ // Skip word chars
51
+ while (i > 0 && !BREAK_CHARS.includes(arr[i - 1])) i--;
52
+ return i;
53
+ }
54
+
55
+ /**
56
+ * Returns the index of the start of the word after cursor.
57
+ *
58
+ * @param {string} value
59
+ * @param {number} cursor
60
+ * @returns {number}
61
+ */
62
+ export function wordStartAfter(value, cursor) {
63
+ var arr = chars(value);
64
+ if (cursor >= arr.length) return arr.length;
65
+ var i = cursor;
66
+ // Skip current word chars
67
+ while (i < arr.length && !BREAK_CHARS.includes(arr[i])) i++;
68
+ // Skip non-word chars (whitespace, punctuation)
69
+ while (i < arr.length && BREAK_CHARS.includes(arr[i])) i++;
70
+ return i;
71
+ }
72
+
73
+ /**
74
+ * Inserts text at the cursor position and leaves the cursor after the insertion.
75
+ *
76
+ * @param {TextState} state
77
+ * @param {string} text
78
+ * @returns {TextState}
79
+ */
80
+ export function insertAt(state, text) {
81
+ if (!text) return state;
82
+ var arr = chars(state.value);
83
+ var before = arr.slice(0, state.cursor);
84
+ var after = arr.slice(state.cursor);
85
+ var result = [].concat(_toConsumableArray(before), _toConsumableArray(chars(text)), _toConsumableArray(after)).join('');
86
+ return {
87
+ value: result,
88
+ cursor: state.cursor + chars(text).length
89
+ };
90
+ }
91
+
92
+ /**
93
+ * Deletes the character before the cursor.
94
+ *
95
+ * @param {TextState} state
96
+ * @returns {TextState}
97
+ */
98
+ export function deleteBackward(state) {
99
+ var arr = chars(state.value);
100
+ if (state.cursor <= 0) return state;
101
+ var before = arr.slice(0, state.cursor - 1);
102
+ var after = arr.slice(state.cursor);
103
+ return {
104
+ value: before.concat(after).join(''),
105
+ cursor: state.cursor - 1
106
+ };
107
+ }
108
+
109
+ /**
110
+ * Deletes the character under the cursor.
111
+ *
112
+ * @param {TextState} state
113
+ * @returns {TextState}
114
+ */
115
+ export function deleteForward(state) {
116
+ var arr = chars(state.value);
117
+ if (state.cursor >= arr.length) return state;
118
+ var before = arr.slice(0, state.cursor);
119
+ var after = arr.slice(state.cursor + 1);
120
+ return {
121
+ value: before.concat(after).join(''),
122
+ cursor: state.cursor
123
+ };
124
+ }
125
+
126
+ /**
127
+ * Applies a keypress to a text state.
128
+ * Pure function: reads and writes nothing outside its arguments.
129
+ *
130
+ * @param {TextState} state
131
+ * @param {string} input - Raw Ink input (1 char, a paste, or '')
132
+ * @param {import('ink').Key} key - Ink special key object
133
+ * @returns {EditResult}
134
+ */
135
+ export function applyKeyToText(state, input, key) {
136
+ // No key combinations from this function
137
+ if (key.ctrl) {
138
+ if (input === 'a') {
139
+ // Ctrl+A → move to start
140
+ return {
141
+ state: _objectSpread(_objectSpread({}, state), {}, {
142
+ cursor: 0
143
+ }),
144
+ handled: true
145
+ };
146
+ }
147
+ if (input === 'e') {
148
+ // Ctrl+E → move to end
149
+ return {
150
+ state: _objectSpread(_objectSpread({}, state), {}, {
151
+ cursor: chars(state.value).length
152
+ }),
153
+ handled: true
154
+ };
155
+ }
156
+ if (input === 'w') {
157
+ // Ctrl+W → delete word before cursor
158
+ var newCursor = wordStartBefore(state.value, state.cursor);
159
+ var arr = chars(state.value);
160
+ var before = arr.slice(0, newCursor);
161
+ var after = arr.slice(state.cursor);
162
+ return {
163
+ state: {
164
+ value: before.concat(after).join(''),
165
+ cursor: newCursor
166
+ },
167
+ handled: true
168
+ };
169
+ }
170
+ if (input === 'u') {
171
+ // Ctrl+U → delete from start to cursor
172
+ var _arr = chars(state.value);
173
+ var _after = _arr.slice(state.cursor);
174
+ return {
175
+ state: {
176
+ value: _after.join(''),
177
+ cursor: 0
178
+ },
179
+ handled: true
180
+ };
181
+ }
182
+ if (input === 'k') {
183
+ // Ctrl+K → delete from cursor to end
184
+ var _arr2 = chars(state.value);
185
+ var _before = _arr2.slice(0, state.cursor);
186
+ return {
187
+ state: {
188
+ value: _before.join(''),
189
+ cursor: state.cursor
190
+ },
191
+ handled: true
192
+ };
193
+ }
194
+ // Other ctrl combos: not handled
195
+ return {
196
+ state: state,
197
+ handled: false
198
+ };
199
+ }
200
+ if (key.meta) {
201
+ return {
202
+ state: state,
203
+ handled: false
204
+ };
205
+ }
206
+ if (key.leftArrow) {
207
+ var _newCursor = Math.max(0, state.cursor - 1);
208
+ return {
209
+ state: _objectSpread(_objectSpread({}, state), {}, {
210
+ cursor: _newCursor
211
+ }),
212
+ handled: true
213
+ };
214
+ }
215
+ if (key.rightArrow) {
216
+ var len = chars(state.value).length;
217
+ var _newCursor2 = Math.min(len, state.cursor + 1);
218
+ return {
219
+ state: _objectSpread(_objectSpread({}, state), {}, {
220
+ cursor: _newCursor2
221
+ }),
222
+ handled: true
223
+ };
224
+ }
225
+ if (key.home) {
226
+ return {
227
+ state: _objectSpread(_objectSpread({}, state), {}, {
228
+ cursor: 0
229
+ }),
230
+ handled: true
231
+ };
232
+ }
233
+ if (key.end) {
234
+ return {
235
+ state: _objectSpread(_objectSpread({}, state), {}, {
236
+ cursor: chars(state.value).length
237
+ }),
238
+ handled: true
239
+ };
240
+ }
241
+ if (key.backspace) {
242
+ var newState = deleteBackward(state);
243
+ return {
244
+ state: newState,
245
+ handled: true
246
+ };
247
+ }
248
+ if (key["delete"]) {
249
+ var _newState = deleteForward(state);
250
+ return {
251
+ state: _newState,
252
+ handled: true
253
+ };
254
+ }
255
+
256
+ // Safety net: \x7f (DEL) is what most terminals send for Backspace.
257
+ // Ink v6 maps it to key.delete, but if we receive the raw char directly
258
+ // (e.g. from tests or non-Ink callers), handle it as backspace.
259
+ if (input === '\x7f' || input === '\b') {
260
+ var _newState2 = deleteBackward(state);
261
+ return {
262
+ state: _newState2,
263
+ handled: true
264
+ };
265
+ }
266
+
267
+ // Regular text input (single char or paste)
268
+ if (input && input.length >= 1) {
269
+ var _newState3 = insertAt(state, input);
270
+ return {
271
+ state: _newState3,
272
+ handled: true
273
+ };
274
+ }
275
+ return {
276
+ state: state,
277
+ handled: false
278
+ };
279
+ }
280
+
281
+ /**
282
+ * Creates a TextState from a string, with cursor at the end.
283
+ *
284
+ * @param {string} value
285
+ * @returns {TextState}
286
+ */
287
+ export function textStateOf() {
288
+ var value = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : '';
289
+ return {
290
+ value: value,
291
+ cursor: chars(value).length
292
+ };
293
+ }
@@ -19,6 +19,7 @@ import { validatePorts, validateEnvVars } from '../../helpers/validationHelpers.
19
19
  import { IMAGE_PROFILES, resolveImageTag } from '../../helpers/constants.js';
20
20
  import { safeCall } from '../../helpers/safeCall.js';
21
21
  import { searchDockerHub, formatHubResult } from '../../helpers/dockerHubService.js';
22
+ import { applyKeyToText } from '../../helpers/textEditing.js';
22
23
  var MAX_VISIBLE = 6;
23
24
  var INITIAL_FORM = {
24
25
  step: 0,
@@ -26,6 +27,12 @@ var INITIAL_FORM = {
26
27
  containerName: '',
27
28
  portInput: '',
28
29
  envInput: '',
30
+ cursors: {
31
+ imageName: 0,
32
+ containerName: 0,
33
+ portInput: 0,
34
+ envInput: 0
35
+ },
29
36
  message: '',
30
37
  messageColor: 'yellow',
31
38
  suggestions: [],
@@ -76,6 +83,7 @@ export function useContainerCreation(_ref) {
76
83
  containerName = form.containerName,
77
84
  portInput = form.portInput,
78
85
  envInput = form.envInput,
86
+ cursors = form.cursors,
79
87
  message = form.message,
80
88
  messageColor = form.messageColor,
81
89
  suggestions = form.suggestions,
@@ -183,6 +191,76 @@ export function useContainerCreation(_ref) {
183
191
  }
184
192
  };
185
193
 
194
+ /**
195
+ * Sets a field's text value and cursor position together.
196
+ * @param {'imageName'|'containerName'|'portInput'|'envInput'} fieldName
197
+ * @param {string} value
198
+ * @param {number} cursor
199
+ */
200
+ function setFieldText(fieldName, value, cursor) {
201
+ dispatch({
202
+ type: 'SET',
203
+ payload: _defineProperty(_defineProperty({}, fieldName, value), "cursors", _objectSpread(_objectSpread({}, form.cursors), {}, _defineProperty({}, fieldName, cursor)))
204
+ });
205
+ }
206
+
207
+ /**
208
+ * Returns the field name associated with a wizard step.
209
+ * @param {number} step
210
+ * @returns {'imageName'|'containerName'|'portInput'|'envInput'|null}
211
+ */
212
+ function fieldForStep(step) {
213
+ var _step;
214
+ return (_step = ['imageName', 'containerName', 'portInput', 'envInput'][step]) !== null && _step !== void 0 ? _step : null;
215
+ }
216
+
217
+ /**
218
+ * Applies a keypress to the field associated with the current step.
219
+ * When the edited field is imageName, recalculates suggestions.
220
+ * @param {string} input - Raw Ink input
221
+ * @param {import('ink').Key} key - Ink key object
222
+ * @returns {boolean} true if the key was consumed
223
+ */
224
+ function handleFieldKey(input, key) {
225
+ var fieldName = fieldForStep(step);
226
+ if (!fieldName) return false;
227
+ var currentState = {
228
+ value: form[fieldName],
229
+ cursor: cursors[fieldName]
230
+ };
231
+ var _applyKeyToText = applyKeyToText(currentState, input, key),
232
+ newState = _applyKeyToText.state,
233
+ handled = _applyKeyToText.handled;
234
+ if (!handled) return false;
235
+ if (fieldName === 'imageName') {
236
+ var _activeHubRequestRef$;
237
+ // Recalculate suggestions when image name changes
238
+ (_activeHubRequestRef$ = activeHubRequestRef.current.controller) === null || _activeHubRequestRef$ === void 0 || _activeHubRequestRef$.abort();
239
+ activeHubRequestRef.current.requestId += 1;
240
+ setIsSearchingHub(false);
241
+ setHubResults(null);
242
+ var lower = newState.value.toLowerCase();
243
+ var matches = newState.value.trim() ? Object.keys(imageProfiles).filter(function (key) {
244
+ return key.includes(lower);
245
+ }) : [];
246
+ dispatch({
247
+ type: 'SET',
248
+ payload: {
249
+ imageName: newState.value,
250
+ cursors: _objectSpread(_objectSpread({}, form.cursors), {}, {
251
+ imageName: newState.cursor
252
+ }),
253
+ selectedSuggestionIndex: -1,
254
+ visibleOffset: 0,
255
+ suggestions: matches
256
+ }
257
+ });
258
+ } else {
259
+ setFieldText(fieldName, newState.value, newState.cursor);
260
+ }
261
+ return true;
262
+ }
263
+
186
264
  // Docker Hub search state (kept as useState because they are independent of form)
187
265
  var _useState = useState(false),
188
266
  _useState2 = _slicedToArray(_useState, 2),
@@ -200,6 +278,25 @@ export function useContainerCreation(_ref) {
200
278
 
201
279
  // Timer for auto-clearing messages
202
280
  var messageTimerRef = useRef(null);
281
+
282
+ /**
283
+ * Sets a permanent message (e.g. step instructions). No auto-clear.
284
+ */
285
+ function setStepMessage(msg) {
286
+ var color = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 'yellow';
287
+ clearTimeout(messageTimerRef.current);
288
+ dispatch({
289
+ type: 'SET',
290
+ payload: {
291
+ message: msg,
292
+ messageColor: color
293
+ }
294
+ });
295
+ }
296
+
297
+ /**
298
+ * Sets a transient feedback message (e.g. validation errors). Auto-clears after `ms`.
299
+ */
203
300
  function setTimedMessage(msg) {
204
301
  var color = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 'yellow';
205
302
  var ms = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : 4000;
@@ -230,15 +327,19 @@ export function useContainerCreation(_ref) {
230
327
 
231
328
  /**
232
329
  * Updates the image name input and recalculates autocomplete suggestions.
330
+ * Accepts either a plain string (backward compatible) or a TextState object.
233
331
  * Resets selectedSuggestionIndex to -1 on every keystroke.
234
332
  * Also aborts any in-flight Hub search and clears Hub results.
235
333
  *
236
- * @param {string} value - New raw image name typed by user
334
+ * @param {string|TextState} input - New image name (string or {value, cursor})
237
335
  */
238
- function updateImageInput(value) {
239
- var _activeHubRequestRef$;
336
+ function updateImageInput(input) {
337
+ var _input$cursor, _activeHubRequestRef$2;
338
+ var value = typeof input === 'string' ? input : input.value;
339
+ var cursor = typeof input === 'string' ? value.length : (_input$cursor = input.cursor) !== null && _input$cursor !== void 0 ? _input$cursor : value.length;
340
+
240
341
  // Abort any in-flight Hub search and clear its state
241
- (_activeHubRequestRef$ = activeHubRequestRef.current.controller) === null || _activeHubRequestRef$ === void 0 || _activeHubRequestRef$.abort();
342
+ (_activeHubRequestRef$2 = activeHubRequestRef.current.controller) === null || _activeHubRequestRef$2 === void 0 || _activeHubRequestRef$2.abort();
242
343
  activeHubRequestRef.current.requestId += 1;
243
344
  setIsSearchingHub(false);
244
345
  setHubResults(null);
@@ -250,6 +351,9 @@ export function useContainerCreation(_ref) {
250
351
  type: 'SET',
251
352
  payload: {
252
353
  imageName: value,
354
+ cursors: _objectSpread(_objectSpread({}, form.cursors), {}, {
355
+ imageName: cursor
356
+ }),
253
357
  selectedSuggestionIndex: -1,
254
358
  visibleOffset: 0,
255
359
  suggestions: matches
@@ -274,7 +378,7 @@ export function useContainerCreation(_ref) {
274
378
  */
275
379
  function _triggerHubSearch() {
276
380
  _triggerHubSearch = _asyncToGenerator(/*#__PURE__*/_regenerator().m(function _callee() {
277
- var _activeHubRequestRef$2;
381
+ var _activeHubRequestRef$3;
278
382
  var query, requestId, controller, results, _t;
279
383
  return _regenerator().w(function (_context) {
280
384
  while (1) switch (_context.p = _context.n) {
@@ -294,7 +398,7 @@ export function useContainerCreation(_ref) {
294
398
  case 2:
295
399
  // Abort previous request (should already be aborted from updateImageInput,
296
400
  // but guard here too)
297
- (_activeHubRequestRef$2 = activeHubRequestRef.current.controller) === null || _activeHubRequestRef$2 === void 0 || _activeHubRequestRef$2.abort();
401
+ (_activeHubRequestRef$3 = activeHubRequestRef.current.controller) === null || _activeHubRequestRef$3 === void 0 || _activeHubRequestRef$3.abort();
298
402
  requestId = activeHubRequestRef.current.requestId + 1;
299
403
  activeHubRequestRef.current.requestId = requestId;
300
404
  controller = new AbortController();
@@ -367,10 +471,14 @@ export function useContainerCreation(_ref) {
367
471
  function applyFocusedSuggestion() {
368
472
  if (selectedSuggestionIndex < 0 || selectedSuggestionIndex >= suggestions.length) return;
369
473
  var chosen = suggestions[selectedSuggestionIndex];
474
+ var resolved = resolveImageTag(chosen, imageProfiles);
370
475
  dispatch({
371
476
  type: 'SET',
372
477
  payload: {
373
- imageName: resolveImageTag(chosen, imageProfiles),
478
+ imageName: resolved,
479
+ cursors: _objectSpread(_objectSpread({}, form.cursors), {}, {
480
+ imageName: resolved.length
481
+ }),
374
482
  suggestions: [],
375
483
  selectedSuggestionIndex: -1,
376
484
  visibleOffset: 0
@@ -392,10 +500,13 @@ export function useContainerCreation(_ref) {
392
500
  type: 'SET',
393
501
  payload: {
394
502
  imageName: resolved,
503
+ cursors: _objectSpread(_objectSpread({}, form.cursors), {}, {
504
+ imageName: resolved.length
505
+ }),
395
506
  step: 1
396
507
  }
397
508
  });
398
- setTimedMessage('Optional: Enter container name or leave empty and press Enter', 'yellow');
509
+ setStepMessage('Optional: Enter container name or leave empty and press Enter', 'yellow');
399
510
  return;
400
511
  }
401
512
  if (step === 1) {
@@ -405,7 +516,7 @@ export function useContainerCreation(_ref) {
405
516
  step: 2
406
517
  }
407
518
  });
408
- setTimedMessage('Optional: Enter ports (format 8080:80,443:443) or leave empty and press Enter', 'yellow');
519
+ setStepMessage('Optional: Enter ports (format 8080:80,443:443) or leave empty and press Enter', 'yellow');
409
520
  return;
410
521
  }
411
522
  if (step === 2) {
@@ -428,13 +539,13 @@ export function useContainerCreation(_ref) {
428
539
  var profile = imageProfiles[_baseName];
429
540
  if (profile && profile.requiredEnv && profile.requiredEnv.length) {
430
541
  var suggestedPart = profile.suggestedEnv && profile.suggestedEnv.length ? " | Suggested: ".concat(profile.suggestedEnv.join(', ')) : '';
431
- setTimedMessage("Required env vars for ".concat(_baseName, ": ").concat(profile.requiredEnv.join(', '), ". Enter as VAR=val,VAR2=val2").concat(suggestedPart), 'yellow');
542
+ setStepMessage("Required env vars for ".concat(_baseName, ": ").concat(profile.requiredEnv.join(', '), ". Enter as VAR=val,VAR2=val2").concat(suggestedPart), 'yellow');
432
543
  } else if (profile && profile.suggestedEnv && profile.suggestedEnv.length) {
433
- setTimedMessage("Suggested env vars for ".concat(_baseName, ": ").concat(profile.suggestedEnv.join(', '), ". Enter as VAR=val,VAR2=val2 or leave empty."), 'yellow');
544
+ setStepMessage("Suggested env vars for ".concat(_baseName, ": ").concat(profile.suggestedEnv.join(', '), ". Enter as VAR=val,VAR2=val2 or leave empty."), 'yellow');
434
545
  } else if (isDb) {
435
- setTimedMessage('Warning: This image usually requires environment variables (e.g. MYSQL_ROOT_PASSWORD=my-secret-pw for MySQL, POSTGRES_PASSWORD=yourpassword for Postgres). Enter them as VAR=val,VAR2=val2 or leave empty and press Enter.', 'yellow');
546
+ setStepMessage('Warning: This image usually requires environment variables (e.g. MYSQL_ROOT_PASSWORD=my-secret-pw for MySQL, POSTGRES_PASSWORD=yourpassword for Postgres). Enter them as VAR=val,VAR2=val2 or leave empty and press Enter.', 'yellow');
436
547
  } else {
437
- setTimedMessage('Optional: Enter environment variables (format VAR1=val1,VAR2=val2) or leave empty and press Enter', 'yellow');
548
+ setStepMessage('Optional: Enter environment variables (format VAR1=val1,VAR2=val2) or leave empty and press Enter', 'yellow');
438
549
  }
439
550
  return;
440
551
  }
@@ -500,7 +611,10 @@ export function useContainerCreation(_ref) {
500
611
  dispatch({
501
612
  type: 'SET',
502
613
  payload: {
503
- envInput: newEnvInput
614
+ envInput: newEnvInput,
615
+ cursors: _objectSpread(_objectSpread({}, form.cursors), {}, {
616
+ envInput: newEnvInput.length
617
+ })
504
618
  }
505
619
  });
506
620
  }
@@ -521,6 +635,7 @@ export function useContainerCreation(_ref) {
521
635
  setPortInput: setPortInput,
522
636
  envInput: envInput,
523
637
  setEnvInput: setEnvInput,
638
+ cursors: cursors,
524
639
  message: message,
525
640
  setMessage: setMessage,
526
641
  messageColor: messageColor,
@@ -538,6 +653,8 @@ export function useContainerCreation(_ref) {
538
653
  cancelCreation: cancelCreation,
539
654
  resetCreation: resetCreation,
540
655
  insertNextSuggestedEnv: insertNextSuggestedEnv,
541
- hasSuggestedEnv: hasSuggestedEnv
656
+ hasSuggestedEnv: hasSuggestedEnv,
657
+ fieldForStep: fieldForStep,
658
+ handleFieldKey: handleFieldKey
542
659
  };
543
660
  }
@@ -16,6 +16,7 @@ import { useCallback } from 'react';
16
16
  * l — open logs viewer and start log stream
17
17
  * c — open container creation wizard (reset + activate)
18
18
  * d — toggle debug log panel
19
+ * s — open interactive shell in container
19
20
  *
20
21
  * @param {Object} params
21
22
  * @param {Object} params.actions - API from useContainerActions
@@ -27,6 +28,7 @@ import { useCallback } from 'react';
27
28
  * @param {Function} params.onStartErase - Callback: activates confirmErase state
28
29
  * @param {Function} params.onToggleDebug - Callback: toggles showDebugLogs state
29
30
  * @param {Function} params.onStartCreate - Callback: sets creatingContainer = true
31
+ * @param {Function} params.onOpenShell - Callback: opens interactive shell
30
32
  * @returns {{ handleDockerCommands: Function }}
31
33
  */
32
34
  export function useContainerCommandRouter(_ref) {
@@ -38,7 +40,8 @@ export function useContainerCommandRouter(_ref) {
38
40
  startLogsStream = _ref.startLogsStream,
39
41
  onStartErase = _ref.onStartErase,
40
42
  onToggleDebug = _ref.onToggleDebug,
41
- onStartCreate = _ref.onStartCreate;
43
+ onStartCreate = _ref.onStartCreate,
44
+ onOpenShell = _ref.onOpenShell;
42
45
  var handleDockerCommands = useCallback(function (input) {
43
46
  var container = containers[selected];
44
47
  if (input === 'i') {
@@ -143,8 +146,15 @@ export function useContainerCommandRouter(_ref) {
143
146
  onToggleDebug();
144
147
  return true;
145
148
  }
149
+ if (input === 's') {
150
+ if (!container) {
151
+ return true;
152
+ }
153
+ onOpenShell(container);
154
+ return true;
155
+ }
146
156
  return false;
147
- }, [actions, containers, creation, logsViewer, onStartCreate, onStartErase, onToggleDebug, selected, startLogsStream]);
157
+ }, [actions, containers, creation, logsViewer, onStartCreate, onStartErase, onToggleDebug, onOpenShell, selected, startLogsStream]);
148
158
  return {
149
159
  handleDockerCommands: handleDockerCommands
150
160
  };
@@ -1,3 +1,9 @@
1
+ function _typeof(o) { "@babel/helpers - typeof"; return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (o) { return typeof o; } : function (o) { return o && "function" == typeof Symbol && o.constructor === Symbol && o !== Symbol.prototype ? "symbol" : typeof o; }, _typeof(o); }
2
+ function ownKeys(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbols) { var o = Object.getOwnPropertySymbols(e); r && (o = o.filter(function (r) { return Object.getOwnPropertyDescriptor(e, r).enumerable; })), t.push.apply(t, o); } return t; }
3
+ function _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? ownKeys(Object(t), !0).forEach(function (r) { _defineProperty(e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; }
4
+ function _defineProperty(e, r, t) { return (r = _toPropertyKey(r)) in e ? Object.defineProperty(e, r, { value: t, enumerable: !0, configurable: !0, writable: !0 }) : e[r] = t, e; }
5
+ function _toPropertyKey(t) { var i = _toPrimitive(t, "string"); return "symbol" == _typeof(i) ? i : i + ""; }
6
+ function _toPrimitive(t, r) { if ("object" != _typeof(t) || !t) return t; var e = t[Symbol.toPrimitive]; if (void 0 !== e) { var i = e.call(t, r || "default"); if ("object" != _typeof(i)) return i; throw new TypeError("@@toPrimitive must return a primitive value."); } return ("string" === r ? String : Number)(t); }
1
7
  function _toConsumableArray(r) { return _arrayWithoutHoles(r) || _iterableToArray(r) || _unsupportedIterableToArray(r) || _nonIterableSpread(); }
2
8
  function _nonIterableSpread() { throw new TypeError("Invalid attempt to spread non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); }
3
9
  function _iterableToArray(r) { if ("undefined" != typeof Symbol && null != r[Symbol.iterator] || null != r["@@iterator"]) return Array.from(r); }
@@ -22,6 +28,7 @@ import { useDebugLogs } from './debug/useDebugLogs.js';
22
28
  import { useEraseConfirmation } from './useEraseConfirmation.js';
23
29
  import { useExitHandler } from './useExitHandler.js';
24
30
  import { useContainerCommandRouter } from './useContainerCommandRouter.js';
31
+ import { useShellMode } from './useShellMode.js';
25
32
  import { getLogsStream } from '../helpers/dockerService/serviceComponents/containerLogs.js';
26
33
  import { createContainer as svcCreateContainer } from '../helpers/dockerService/serviceComponents/containerActions.js';
27
34
  import { buildContainerOptions } from '../helpers/containerOptionsBuilder.js';
@@ -48,6 +55,7 @@ export function useControls() {
48
55
  var actions = useContainerActions({
49
56
  containers: containers
50
57
  });
58
+ var shellMode = useShellMode();
51
59
  var creation = useContainerCreation({
52
60
  onCreate: function () {
53
61
  var _onCreate = _asyncToGenerator(/*#__PURE__*/_regenerator().m(function _callee(_ref) {
@@ -176,6 +184,9 @@ export function useControls() {
176
184
  },
177
185
  onStartCreate: function onStartCreate() {
178
186
  return setCreatingContainer(true);
187
+ },
188
+ onOpenShell: function onOpenShell(container) {
189
+ return shellMode.openShell(container);
179
190
  }
180
191
  });
181
192
  var exitHandler = useExitHandler({
@@ -192,14 +203,6 @@ export function useControls() {
192
203
  */
193
204
  var processCreationInput = React.useCallback(function (input, key) {
194
205
  var step = creation.step;
195
- var removeLastChar = function removeLastChar(setter) {
196
- return setter(function (value) {
197
- return (value || '').slice(0, -1);
198
- });
199
- };
200
- var appendChar = function appendChar(setter, current, ch) {
201
- return setter((current || '') + ch);
202
- };
203
206
  if (key.escape) {
204
207
  creation.cancelCreation();
205
208
  setCreatingContainer(false);
@@ -238,24 +241,16 @@ export function useControls() {
238
241
  return;
239
242
  }
240
243
  }
241
- if (key.backspace || key["delete"]) {
242
- if (step === 0) {
243
- var newValue = (creation.imageName || '').slice(0, -1);
244
- creation.updateImageInput(newValue);
245
- }
246
- if (step === 1) removeLastChar(creation.setContainerName);
247
- if (step === 2) removeLastChar(creation.setPortInput);
248
- if (step === 3) removeLastChar(creation.setEnvInput);
249
- return;
250
- }
251
- if (input && input.length === 1 && !key.ctrl && !key.meta) {
252
- if (step === 0) {
253
- creation.updateImageInput((creation.imageName || '') + input);
254
- }
255
- if (step === 1) appendChar(creation.setContainerName, creation.containerName, input);
256
- if (step === 2) appendChar(creation.setPortInput, creation.portInput, input);
257
- if (step === 3) appendChar(creation.setEnvInput, creation.envInput, input);
258
- }
244
+
245
+ // Ink v6 maps \x7f (Backspace on most terminals) to key.delete instead
246
+ // of key.backspace. Normalize so text editing always receives backspace.
247
+ var normalizedKey = key["delete"] && !key.backspace ? _objectSpread(_objectSpread({}, key), {}, {
248
+ "delete": false,
249
+ backspace: true
250
+ }) : key;
251
+
252
+ // Delegate to the text editor for all field input
253
+ if (creation.handleFieldKey(input, normalizedKey)) return;
259
254
  }, [creation, setCreatingContainer, triggerHubSearch, isSearchingHub, insertNextSuggestedEnv]);
260
255
 
261
256
  // Single keyboard entry point