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
package/README.es.md CHANGED
@@ -3,12 +3,6 @@
3
3
  <p align="center">
4
4
  <img src="https://img.shields.io/npm/v/cdd-cli?color=blue&label=npm%20package" alt="npm version"/>
5
5
  <img src="https://img.shields.io/npm/dt/cdd-cli?color=green&label=downloads" alt="npm downloads"/>
6
- <a href="https://github.com/caertos/cdd/actions/workflows/ci.yml">
7
- <img src="https://github.com/caertos/cdd/actions/workflows/ci.yml/badge.svg?branch=main" alt="CI status" />
8
- </a>
9
- <a href="https://caertos.github.io/cdd/">
10
- <img src="https://img.shields.io/badge/docs-GitHub%20Pages-blue" alt="Docs" />
11
- </a>
12
6
  <a href="https://deepwiki.com/Caertos/cdd"><img src="https://deepwiki.com/badge.svg" alt="Ask DeepWiki"></a>
13
7
  </p>
14
8
 
@@ -16,9 +10,34 @@
16
10
 
17
11
  ---
18
12
 
19
- ## 🎉 Novedades en v3.2
13
+ ## 🎉 Novedades en v4.2
20
14
 
21
- **v3.2 trae el asistente de creación interactivo y cambia las reglas del juego.**
15
+ **Campos de texto editables con cursor visible en el asistente de creación.**
16
+
17
+ Ahora los campos de texto del asistente se comportan como un campo de texto normal: un cursor que se ve, que se mueve con las flechas, que inserta y borra donde está.
18
+
19
+ - **Navegación con cursor** — `←` / `→` mueven el cursor dentro del campo
20
+ - **Inicio / Fin** — saltan al principio o al final del texto
21
+ - **Edición completa** — `Backspace` y `Delete` funcionan en cualquier posición
22
+ - **Atajos de teclado** — `Ctrl+A/E/W/U/K` para edición rápida
23
+ - **Pegado** — pegar texto lo inserta completo en la posición del cursor
24
+ - **Acentos y emojis** — manejo correcto por punto de código
25
+
26
+ ---
27
+
28
+ ## Versiones anteriores
29
+
30
+ ### v4.1 — Shell interactivo
31
+
32
+ **Abre un shell dentro de cualquier contenedor con una sola tecla.**
33
+
34
+ Presiona `S` y CDD te lleva a un shell interactivo completo (`bash` o `sh`) dentro del contenedor seleccionado — sin necesidad de escribir `docker exec`.
35
+
36
+ - **Shell auto-detectado** — CDD sondea el contenedor y elige `bash` o `sh` automáticamente
37
+ - **Soporte de terminal completo** — ejecuta `psql`, `python3`, `node`, `redis-cli`, o cualquier comando dentro del contenedor
38
+ - **Salida limpia** — escribe `exit` o presiona `Ctrl+D` para volver al dashboard
39
+
40
+ ### v3.2 — Asistente de creación interactivo
22
41
 
23
42
  Olvídate de los flags de `docker run`, las variables de entorno olvidadas y los tags `:latest` que fallan en silencio. Presiona `C` y CDD te guía para crear un contenedor en segundos:
24
43
 
@@ -76,17 +95,18 @@ cdd
76
95
 
77
96
  Usa `↑` / `↓` para navegar por los contenedores. Atajos de teclado disponibles:
78
97
 
79
- | Tecla | Acción |
80
- |-------|--------|
81
- | `↑` / `↓` | Navegar la lista de contenedores |
82
- | `I` | Iniciar el contenedor seleccionado |
83
- | `P` | Detener el contenedor seleccionado |
84
- | `R` | Reiniciar el contenedor seleccionado |
85
- | `C` | Abrir el asistente de creación |
86
- | `L` | Ver logs del contenedor seleccionado en tiempo real |
87
- | `E` | Eliminar el contenedor seleccionado requiere confirmación |
88
- | `D` | Activar/desactivar panel de debug en vivo |
89
- | `Q` | Salir |
98
+ | Tecla | Acción |
99
+ | --------- | ----------------------------------------------------------- |
100
+ | `↑` / `↓` | Navegar la lista de contenedores |
101
+ | `I` | Iniciar el contenedor seleccionado |
102
+ | `P` | Detener el contenedor seleccionado |
103
+ | `R` | Reiniciar el contenedor seleccionado |
104
+ | `C` | Abrir el asistente de creación |
105
+ | `L` | Ver logs del contenedor seleccionado en tiempo real |
106
+ | `S` | Abrir shell interactivo dentro del contenedor seleccionado |
107
+ | `E` | Eliminar el contenedor seleccionado requiere confirmación |
108
+ | `D` | Activar/desactivar panel de debug en vivo |
109
+ | `Q` | Salir |
90
110
 
91
111
  ---
92
112
 
@@ -114,18 +134,32 @@ Ingresa un mapeo de puertos en formato `HOST:CONTENEDOR`, por ejemplo `8080:80`,
114
134
 
115
135
  Ingresa pares `CLAVE=VALOR` de a uno. Las **sugerencias contextuales** muestran variables recomendadas para la imagen seleccionada:
116
136
 
117
- | Imagen | Variables sugeridas |
118
- |--------|---------------------|
119
- | postgres | `POSTGRES_USER`, `POSTGRES_PASSWORD`, `POSTGRES_DB` |
120
- | mysql | `MYSQL_ROOT_PASSWORD`, `MYSQL_DATABASE` |
121
- | redis | *(sin variables requeridas)* |
122
- | mongo | `MONGO_INITDB_ROOT_USERNAME`, `MONGO_INITDB_ROOT_PASSWORD` |
123
- | node / nginx / otros | Variables de runtime comunes según corresponda |
137
+ | Imagen | Variables sugeridas |
138
+ | -------------------- | ---------------------------------------------------------- |
139
+ | postgres | `POSTGRES_USER`, `POSTGRES_PASSWORD`, `POSTGRES_DB` |
140
+ | mysql | `MYSQL_ROOT_PASSWORD`, `MYSQL_DATABASE` |
141
+ | redis | _(sin variables requeridas)_ |
142
+ | mongo | `MONGO_INITDB_ROOT_USERNAME`, `MONGO_INITDB_ROOT_PASSWORD` |
143
+ | node / nginx / otros | Variables de runtime comunes según corresponda |
124
144
 
125
145
  Presiona `Enter` en una línea vacía para terminar y crear el contenedor.
126
146
 
127
147
  ---
128
148
 
149
+ ## Shell Interactivo
150
+
151
+ Presiona `S` desde el dashboard con un contenedor en ejecución seleccionado. CDD hará lo siguiente:
152
+
153
+ 1. Detectará el shell disponible dentro del contenedor (`bash` o `sh`)
154
+ 2. Abrirá una sesión de terminal interactiva completa
155
+ 3. Te dejará en el shell del contenedor
156
+
157
+ Desde ahí puedes ejecutar cualquier comando — `psql` para PostgreSQL, `python3` para Python, `node` para Node.js, `redis-cli` para Redis, etc.
158
+
159
+ Escribe `exit` o presiona `Ctrl+D` para salir del shell y volver al dashboard de CDD.
160
+
161
+ ---
162
+
129
163
  ## Requisitos
130
164
 
131
165
  - Node.js >= 18
package/README.md CHANGED
@@ -3,12 +3,6 @@
3
3
  <p align="center">
4
4
  <img src="https://img.shields.io/npm/v/cdd-cli?color=blue&label=npm%20package" alt="npm version"/>
5
5
  <img src="https://img.shields.io/npm/dt/cdd-cli?color=green&label=downloads" alt="npm downloads"/>
6
- <a href="https://github.com/caertos/cdd/actions/workflows/ci.yml">
7
- <img src="https://github.com/caertos/cdd/actions/workflows/ci.yml/badge.svg?branch=main" alt="CI status" />
8
- </a>
9
- <a href="https://caertos.github.io/cdd/">
10
- <img src="https://img.shields.io/badge/docs-GitHub%20Pages-blue" alt="Docs" />
11
- </a>
12
6
  <a href="https://deepwiki.com/Caertos/cdd"><img src="https://deepwiki.com/badge.svg" alt="Ask DeepWiki"></a>
13
7
  </p>
14
8
 
@@ -16,9 +10,34 @@
16
10
 
17
11
  ---
18
12
 
19
- ## 🎉 What's new in v3.2
13
+ ## 🎉 What's new in v4.2
20
14
 
21
- **v3.2 ships the interactive creation wizard and it's a game changer.**
15
+ **Editable text fields with visible cursor in the creation wizard.**
16
+
17
+ Text fields in the wizard now behave like a proper text input: a visible cursor that moves with arrow keys, inserts and deletes where it stands.
18
+
19
+ - **Cursor navigation** — `←` / `→` move the cursor within the field
20
+ - **Home / End** — jump to the start or end of the text
21
+ - **Full editing** — `Backspace` and `Delete` work at any cursor position
22
+ - **Keyboard shortcuts** — `Ctrl+A/E/W/U/K` for fast text editing
23
+ - **Paste support** — pasted text inserts at the cursor position
24
+ - **Accents and emojis** — correct handling via code-point segmentation
25
+
26
+ ---
27
+
28
+ ## Previous releases
29
+
30
+ ### v4.1 — Interactive shell
31
+
32
+ **Open a shell inside any container with a single keystroke.**
33
+
34
+ Press `S` and CDD drops you into a full interactive shell (`bash` or `sh`) inside the selected container — no `docker exec` typing needed.
35
+
36
+ - **Auto-detected shell** — CDD probes the container and picks `bash` or `sh` automatically
37
+ - **Full terminal support** — run `psql`, `python3`, `node`, `redis-cli`, or any command inside the container
38
+ - **Clean exit** — type `exit` or press `Ctrl+D` to return to the dashboard
39
+
40
+ ### v3.2 — Interactive creation wizard
22
41
 
23
42
  Forget `docker run` flags, forgotten env vars, and broken `:latest` tags. Press `C` and CDD guides you through creating a container in seconds:
24
43
 
@@ -76,17 +95,18 @@ cdd
76
95
 
77
96
  Use `↑` / `↓` to navigate containers. Available keyboard shortcuts:
78
97
 
79
- | Key | Action |
80
- |-----|--------|
81
- | `↑` / `↓` | Navigate container list |
82
- | `I` | Start selected container |
83
- | `P` | Stop selected container |
84
- | `R` | Restart selected container |
85
- | `C` | Open creation wizard |
86
- | `L` | Stream logs for selected container |
87
- | `E` | Erase (remove) selected container — confirmation required |
88
- | `D` | Toggle live debug panel |
89
- | `Q` | Quit |
98
+ | Key | Action |
99
+ | --------- | --------------------------------------------------------- |
100
+ | `↑` / `↓` | Navigate container list |
101
+ | `I` | Start selected container |
102
+ | `P` | Stop selected container |
103
+ | `R` | Restart selected container |
104
+ | `C` | Open creation wizard |
105
+ | `L` | Stream logs for selected container |
106
+ | `S` | Open interactive shell inside selected container |
107
+ | `E` | Erase (remove) selected container — confirmation required |
108
+ | `D` | Toggle live debug panel |
109
+ | `Q` | Quit |
90
110
 
91
111
  ---
92
112
 
@@ -114,18 +134,32 @@ Enter a port mapping in `HOST:CONTAINER` format, e.g. `8080:80`, `5432:5432`. Le
114
134
 
115
135
  Enter `KEY=VALUE` pairs one at a time. **Contextual hints** show recommended variables for the selected image:
116
136
 
117
- | Image | Suggested vars |
118
- |-------|---------------|
119
- | postgres | `POSTGRES_USER`, `POSTGRES_PASSWORD`, `POSTGRES_DB` |
120
- | mysql | `MYSQL_ROOT_PASSWORD`, `MYSQL_DATABASE` |
121
- | redis | *(no required vars)* |
122
- | mongo | `MONGO_INITDB_ROOT_USERNAME`, `MONGO_INITDB_ROOT_PASSWORD` |
123
- | node / nginx / others | Common runtime vars as applicable |
137
+ | Image | Suggested vars |
138
+ | --------------------- | ---------------------------------------------------------- |
139
+ | postgres | `POSTGRES_USER`, `POSTGRES_PASSWORD`, `POSTGRES_DB` |
140
+ | mysql | `MYSQL_ROOT_PASSWORD`, `MYSQL_DATABASE` |
141
+ | redis | _(no required vars)_ |
142
+ | mongo | `MONGO_INITDB_ROOT_USERNAME`, `MONGO_INITDB_ROOT_PASSWORD` |
143
+ | node / nginx / others | Common runtime vars as applicable |
124
144
 
125
145
  Press `Enter` on an empty line to finish and create the container.
126
146
 
127
147
  ---
128
148
 
149
+ ## Interactive Shell
150
+
151
+ Press `S` from the dashboard while a running container is selected. CDD will:
152
+
153
+ 1. Detect the available shell inside the container (`bash` or `sh`)
154
+ 2. Open a full interactive terminal session
155
+ 3. Drop you into the container's shell
156
+
157
+ From there you can run any command — `psql` for PostgreSQL, `python3` for Python, `node` for Node.js, `redis-cli` for Redis, etc.
158
+
159
+ Type `exit` or press `Ctrl+D` to leave the shell and return to the CDD dashboard.
160
+
161
+ ---
162
+
129
163
  ## Requirements
130
164
 
131
165
  - Node.js >= 18
package/dist/App.js CHANGED
@@ -31,6 +31,7 @@ export default function App() {
31
31
  containerName: controls.containerNameInput,
32
32
  portInput: controls.portInput,
33
33
  envInput: controls.envInput,
34
+ cursors: controls.creation.cursors,
34
35
  message: controls.message,
35
36
  messageColor: controls.messageColor,
36
37
  suggestions: controls.creation.suggestions,
@@ -29,6 +29,8 @@ export default function ContainerCreationPrompt(props) {
29
29
  containerName = props.containerName,
30
30
  portInput = props.portInput,
31
31
  envInput = props.envInput,
32
+ _props$cursors = props.cursors,
33
+ cursors = _props$cursors === void 0 ? {} : _props$cursors,
32
34
  message = props.message,
33
35
  messageColor = props.messageColor,
34
36
  _props$suggestions = props.suggestions,
@@ -46,23 +48,28 @@ export default function ContainerCreationPrompt(props) {
46
48
  var prompts = [{
47
49
  label: 'Name of the image to create (e.g., nginx:1.27-alpine):',
48
50
  value: imageName,
51
+ cursor: cursors.imageName,
49
52
  required: true
50
53
  }, {
51
54
  label: 'Name of the container (optional):',
52
55
  value: containerName,
56
+ cursor: cursors.containerName,
53
57
  required: false
54
58
  }, {
55
59
  label: 'Ports (optional, format 8080:80,443:443):',
56
60
  value: portInput,
61
+ cursor: cursors.portInput,
57
62
  required: false
58
63
  }, {
59
64
  label: 'Environment variables (optional, format VAR1=val1,VAR2=val2):',
60
65
  value: envInput,
66
+ cursor: cursors.envInput,
61
67
  required: false
62
68
  }];
63
69
  var _ref = prompts[step] || {},
64
70
  label = _ref.label,
65
71
  value = _ref.value,
72
+ cursor = _ref.cursor,
66
73
  required = _ref.required;
67
74
  var activeItems = hubResults !== null && hubResults !== void 0 ? hubResults : suggestions;
68
75
  var showSuggestions = step === 0 && (isSearchingHub || activeItems.length > 0);
@@ -75,6 +82,7 @@ export default function ContainerCreationPrompt(props) {
75
82
  }, /*#__PURE__*/React.createElement(PromptField, {
76
83
  label: label,
77
84
  value: value,
85
+ cursor: cursor,
78
86
  required: required
79
87
  }), showSuggestions && /*#__PURE__*/React.createElement(SuggestionPanel, {
80
88
  items: activeItems,
@@ -97,6 +105,12 @@ ContainerCreationPrompt.propTypes = {
97
105
  containerName: PropTypes.string,
98
106
  portInput: PropTypes.string,
99
107
  envInput: PropTypes.string,
108
+ cursors: PropTypes.shape({
109
+ imageName: PropTypes.number,
110
+ containerName: PropTypes.number,
111
+ portInput: PropTypes.number,
112
+ envInput: PropTypes.number
113
+ }),
100
114
  message: PropTypes.string,
101
115
  messageColor: PropTypes.string,
102
116
  suggestions: PropTypes.arrayOf(PropTypes.string),
@@ -1,26 +1,32 @@
1
1
  import React from 'react';
2
+ import { TextField } from './TextField.js';
2
3
  import { Text } from 'ink';
3
4
  import PropTypes from 'prop-types';
4
5
 
5
6
  /**
6
7
  * PromptField shows a label and the current typed value for an input field.
7
- * The cursor is represented with a trailing underscore.
8
+ * Wraps TextField with cursor rendering.
8
9
  *
9
10
  * @param {Object} props
10
11
  * @param {string} props.label - Label shown above the input value
11
12
  * @param {string} props.value - Current value of the input
13
+ * @param {number} [props.cursor] - Cursor position (0..value.length). When
14
+ * omitted the cursor defaults to the end of the value.
12
15
  * @param {boolean} [props.required=false] - Whether the field is required
13
16
  * @returns {JSX.Element}
14
17
  */
15
18
  export function PromptField(_ref) {
16
19
  var label = _ref.label,
17
20
  value = _ref.value,
21
+ cursor = _ref.cursor,
18
22
  required = _ref.required;
19
- // If the field is required and empty, show in red
20
- var isEmpty = required && !value.trim();
21
- return /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement(Text, null, label), /*#__PURE__*/React.createElement(Text, {
22
- color: isEmpty ? 'red' : 'cyan'
23
- }, value, "_"));
23
+ var cursorPos = cursor !== undefined ? cursor : (value || '').length;
24
+ return /*#__PURE__*/React.createElement(TextField, {
25
+ label: label,
26
+ value: value || '',
27
+ cursor: cursorPos,
28
+ required: required
29
+ });
24
30
  }
25
31
 
26
32
  /**
@@ -42,6 +48,7 @@ export function PromptMessage(_ref2) {
42
48
  PromptField.propTypes = {
43
49
  label: PropTypes.string,
44
50
  value: PropTypes.string,
51
+ cursor: PropTypes.number,
45
52
  required: PropTypes.bool
46
53
  };
47
54
  PromptMessage.propTypes = {
@@ -0,0 +1,54 @@
1
+ import React from 'react';
2
+ import { Text } from 'ink';
3
+ import PropTypes from 'prop-types';
4
+
5
+ /**
6
+ * TextField renders a single-line text input with a visible cursor.
7
+ * Purely presentational — does not capture keyboard input.
8
+ *
9
+ * The cursor inverts the character under it. When the cursor is at the end
10
+ * of the text, a block cursor (inverted space) is shown.
11
+ *
12
+ * @param {Object} props
13
+ * @param {string} props.label - Label shown above the field
14
+ * @param {string} props.value - Current text content
15
+ * @param {number} props.cursor - Cursor position (0..value.length)
16
+ * @param {boolean} [props.required=false] - Show in red if empty
17
+ * @param {boolean} [props.masked=false] - Reserved for TASK-5 (secrets)
18
+ * @param {string} [props.placeholder] - Dimmed text when empty
19
+ */
20
+ export function TextField(_ref) {
21
+ var _chars$clampedCursor;
22
+ var label = _ref.label,
23
+ _ref$value = _ref.value,
24
+ value = _ref$value === void 0 ? '' : _ref$value,
25
+ _ref$cursor = _ref.cursor,
26
+ cursor = _ref$cursor === void 0 ? 0 : _ref$cursor,
27
+ _ref$required = _ref.required,
28
+ required = _ref$required === void 0 ? false : _ref$required,
29
+ _ref$masked = _ref.masked,
30
+ masked = _ref$masked === void 0 ? false : _ref$masked;
31
+ var isEmpty = required && !value.trim();
32
+ var displayValue = masked ? '*'.repeat(value.length) : value;
33
+ var chars = Array.from(displayValue);
34
+ var clampedCursor = Math.max(0, Math.min(chars.length, cursor));
35
+ var before = chars.slice(0, clampedCursor).join('');
36
+ var atCursor = (_chars$clampedCursor = chars[clampedCursor]) !== null && _chars$clampedCursor !== void 0 ? _chars$clampedCursor : '';
37
+ var after = chars.slice(clampedCursor + 1).join('');
38
+ var color = isEmpty ? 'red' : 'cyan';
39
+ return /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement(Text, null, label), /*#__PURE__*/React.createElement(Text, {
40
+ color: color
41
+ }, before, atCursor ? /*#__PURE__*/React.createElement(Text, {
42
+ inverse: true
43
+ }, atCursor) : /*#__PURE__*/React.createElement(Text, {
44
+ inverse: true
45
+ }, " "), after));
46
+ }
47
+ TextField.propTypes = {
48
+ label: PropTypes.string.isRequired,
49
+ value: PropTypes.string,
50
+ cursor: PropTypes.number,
51
+ required: PropTypes.bool,
52
+ masked: PropTypes.bool,
53
+ placeholder: PropTypes.string
54
+ };
@@ -1,6 +1,6 @@
1
1
  import React from 'react';
2
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'];
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', 'S to open shell in 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
  /**
@@ -0,0 +1,26 @@
1
+ /**
2
+ * Shared reference to the Ink render instance.
3
+ * Set once in index.js after render(<App />) and read by useShellMode
4
+ * to unmount/remount the UI around interactive shell sessions.
5
+ *
6
+ * @module appState
7
+ */
8
+
9
+ /** @type {import('ink').RenderReturnType | null} */
10
+ var inkApp = null;
11
+
12
+ /**
13
+ * Store the Ink render return value.
14
+ * @param {import('ink').RenderReturnType} app
15
+ */
16
+ export function setInkApp(app) {
17
+ inkApp = app;
18
+ }
19
+
20
+ /**
21
+ * Get the Ink render return value.
22
+ * @returns {import('ink').RenderReturnType | null}
23
+ */
24
+ export function getInkApp() {
25
+ return inkApp;
26
+ }
@@ -0,0 +1,211 @@
1
+ function _regenerator() { /*! regenerator-runtime -- Copyright (c) 2014-present, Facebook, Inc. -- license (MIT): https://github.com/babel/babel/blob/main/packages/babel-helpers/LICENSE */ var e, t, r = "function" == typeof Symbol ? Symbol : {}, n = r.iterator || "@@iterator", o = r.toStringTag || "@@toStringTag"; function i(r, n, o, i) { var c = n && n.prototype instanceof Generator ? n : Generator, u = Object.create(c.prototype); return _regeneratorDefine2(u, "_invoke", function (r, n, o) { var i, c, u, f = 0, p = o || [], y = !1, G = { p: 0, n: 0, v: e, a: d, f: d.bind(e, 4), d: function d(t, r) { return i = t, c = 0, u = e, G.n = r, a; } }; function d(r, n) { for (c = r, u = n, t = 0; !y && f && !o && t < p.length; t++) { var o, i = p[t], d = G.p, l = i[2]; r > 3 ? (o = l === n) && (u = i[(c = i[4]) ? 5 : (c = 3, 3)], i[4] = i[5] = e) : i[0] <= d && ((o = r < 2 && d < i[1]) ? (c = 0, G.v = n, G.n = i[1]) : d < l && (o = r < 3 || i[0] > n || n > l) && (i[4] = r, i[5] = n, G.n = l, c = 0)); } if (o || r > 1) return a; throw y = !0, n; } return function (o, p, l) { if (f > 1) throw TypeError("Generator is already running"); for (y && 1 === p && d(p, l), c = p, u = l; (t = c < 2 ? e : u) || !y;) { i || (c ? c < 3 ? (c > 1 && (G.n = -1), d(c, u)) : G.n = u : G.v = u); try { if (f = 2, i) { if (c || (o = "next"), t = i[o]) { if (!(t = t.call(i, u))) throw TypeError("iterator result is not an object"); if (!t.done) return t; u = t.value, c < 2 && (c = 0); } else 1 === c && (t = i["return"]) && t.call(i), c < 2 && (u = TypeError("The iterator does not provide a '" + o + "' method"), c = 1); i = e; } else if ((t = (y = G.n < 0) ? u : r.call(n, G)) !== a) break; } catch (t) { i = e, c = 1, u = t; } finally { f = 1; } } return { value: t, done: y }; }; }(r, o, i), !0), u; } var a = {}; function Generator() {} function GeneratorFunction() {} function GeneratorFunctionPrototype() {} t = Object.getPrototypeOf; var c = [][n] ? t(t([][n]())) : (_regeneratorDefine2(t = {}, n, function () { return this; }), t), u = GeneratorFunctionPrototype.prototype = Generator.prototype = Object.create(c); function f(e) { return Object.setPrototypeOf ? Object.setPrototypeOf(e, GeneratorFunctionPrototype) : (e.__proto__ = GeneratorFunctionPrototype, _regeneratorDefine2(e, o, "GeneratorFunction")), e.prototype = Object.create(u), e; } return GeneratorFunction.prototype = GeneratorFunctionPrototype, _regeneratorDefine2(u, "constructor", GeneratorFunctionPrototype), _regeneratorDefine2(GeneratorFunctionPrototype, "constructor", GeneratorFunction), GeneratorFunction.displayName = "GeneratorFunction", _regeneratorDefine2(GeneratorFunctionPrototype, o, "GeneratorFunction"), _regeneratorDefine2(u), _regeneratorDefine2(u, o, "Generator"), _regeneratorDefine2(u, n, function () { return this; }), _regeneratorDefine2(u, "toString", function () { return "[object Generator]"; }), (_regenerator = function _regenerator() { return { w: i, m: f }; })(); }
2
+ function _regeneratorDefine2(e, r, n, t) { var i = Object.defineProperty; try { i({}, "", {}); } catch (e) { i = 0; } _regeneratorDefine2 = function _regeneratorDefine(e, r, n, t) { function o(r, n) { _regeneratorDefine2(e, r, function (e) { return this._invoke(r, n, e); }); } r ? i ? i(e, r, { value: n, enumerable: !t, configurable: !t, writable: !t }) : e[r] = n : (o("next", 0), o("throw", 1), o("return", 2)); }, _regeneratorDefine2(e, r, n, t); }
3
+ function asyncGeneratorStep(n, t, e, r, o, a, c) { try { var i = n[a](c), u = i.value; } catch (n) { return void e(n); } i.done ? t(u) : Promise.resolve(u).then(r, o); }
4
+ function _asyncToGenerator(n) { return function () { var t = this, e = arguments; return new Promise(function (r, o) { var a = n.apply(t, e); function _next(n) { asyncGeneratorStep(a, r, o, _next, _throw, "next", n); } function _throw(n) { asyncGeneratorStep(a, r, o, _next, _throw, "throw", n); } _next(void 0); }); }; }
5
+ import { spawn } from 'child_process';
6
+ import { docker } from '../dockerService.js';
7
+ import { logger } from '../../logger.js';
8
+ import { TIMEOUTS } from '../../constants.js';
9
+
10
+ /**
11
+ * Detect the default shell available inside a running container.
12
+ * Tries `bash` first, falls back to `sh`.
13
+ *
14
+ * @param {string} containerId - Docker container id
15
+ * @returns {Promise<string>} The shell command (e.g. 'bash' or 'sh')
16
+ */
17
+ export function detectShell(_x) {
18
+ return _detectShell.apply(this, arguments);
19
+ }
20
+
21
+ /**
22
+ * Spawn an interactive shell inside a running container using `docker exec -it`.
23
+ * Returns a promise that resolves when the user exits the shell.
24
+ *
25
+ * @param {string} containerId - Docker container id
26
+ * @param {Object} [options]
27
+ * @param {string} [options.shell] - Shell to use (auto-detected if omitted)
28
+ * @param {string} [options.workingDir] - Working directory inside the container
29
+ * @returns {Promise<{code: number|null, signal: string|null}>} Exit info
30
+ */
31
+ function _detectShell() {
32
+ _detectShell = _asyncToGenerator(/*#__PURE__*/_regenerator().m(function _callee(containerId) {
33
+ var container, tryShell, hasBash, _t;
34
+ return _regenerator().w(function (_context) {
35
+ while (1) switch (_context.p = _context.n) {
36
+ case 0:
37
+ container = docker.getContainer(containerId);
38
+ tryShell = function tryShell(shell) {
39
+ return new Promise(function (resolve) {
40
+ container.exec({
41
+ Cmd: ['which', shell],
42
+ AttachStdout: true,
43
+ AttachStderr: false,
44
+ Tty: false
45
+ }, function (err, exec) {
46
+ if (err) {
47
+ resolve(false);
48
+ return;
49
+ }
50
+ exec.start({
51
+ Tty: false
52
+ }, function (startErr, stream) {
53
+ if (startErr) {
54
+ resolve(false);
55
+ return;
56
+ }
57
+ var output = '';
58
+ stream.on('data', function (chunk) {
59
+ output += chunk.toString();
60
+ });
61
+ stream.on('end', function () {
62
+ resolve(output.trim().length > 0);
63
+ });
64
+ stream.on('error', function () {
65
+ return resolve(false);
66
+ });
67
+ });
68
+ });
69
+ });
70
+ };
71
+ _context.p = 1;
72
+ _context.n = 2;
73
+ return Promise.race([tryShell('bash'), new Promise(function (r) {
74
+ return setTimeout(function () {
75
+ return r(false);
76
+ }, TIMEOUTS.CONTAINER_OP);
77
+ })]);
78
+ case 2:
79
+ hasBash = _context.v;
80
+ if (!hasBash) {
81
+ _context.n = 3;
82
+ break;
83
+ }
84
+ logger.debug('Detected bash in container %s', containerId);
85
+ return _context.a(2, 'bash');
86
+ case 3:
87
+ _context.n = 5;
88
+ break;
89
+ case 4:
90
+ _context.p = 4;
91
+ _t = _context.v;
92
+ case 5:
93
+ logger.debug('Falling back to sh in container %s', containerId);
94
+ return _context.a(2, 'sh');
95
+ }
96
+ }, _callee, null, [[1, 4]]);
97
+ }));
98
+ return _detectShell.apply(this, arguments);
99
+ }
100
+ export function execInteractive(_x2) {
101
+ return _execInteractive.apply(this, arguments);
102
+ }
103
+
104
+ /**
105
+ * Execute a non-interactive command inside a running container.
106
+ * Returns stdout and stderr as strings.
107
+ *
108
+ * @param {string} containerId - Docker container id
109
+ * @param {string[]} cmd - Command to execute (e.g. ['ls', '-la'])
110
+ * @param {Object} [options]
111
+ * @param {number} [options.timeout] - Timeout in ms
112
+ * @returns {Promise<{stdout: string, stderr: string, exitCode: number}>}
113
+ */
114
+ function _execInteractive() {
115
+ _execInteractive = _asyncToGenerator(/*#__PURE__*/_regenerator().m(function _callee2(containerId) {
116
+ var options,
117
+ shell,
118
+ args,
119
+ _args2 = arguments,
120
+ _t2;
121
+ return _regenerator().w(function (_context2) {
122
+ while (1) switch (_context2.n) {
123
+ case 0:
124
+ options = _args2.length > 1 && _args2[1] !== undefined ? _args2[1] : {};
125
+ _t2 = options.shell;
126
+ if (_t2) {
127
+ _context2.n = 2;
128
+ break;
129
+ }
130
+ _context2.n = 1;
131
+ return detectShell(containerId);
132
+ case 1:
133
+ _t2 = _context2.v;
134
+ case 2:
135
+ shell = _t2;
136
+ args = ['exec', '-it', containerId, shell];
137
+ if (options.workingDir) {
138
+ args.splice(3, 0, '-w', options.workingDir);
139
+ }
140
+ logger.info('Opening interactive shell in container %s (shell=%s)', containerId, shell);
141
+ return _context2.a(2, new Promise(function (resolve, reject) {
142
+ var child = spawn('docker', args, {
143
+ stdio: 'inherit',
144
+ shell: false
145
+ });
146
+ child.on('error', function (err) {
147
+ logger.error('Failed to spawn docker exec', err);
148
+ reject(new Error("Failed to open shell: ".concat(err.message)));
149
+ });
150
+ child.on('close', function (code, signal) {
151
+ logger.info('Interactive shell exited (code=%s, signal=%s)', code, signal);
152
+ resolve({
153
+ code: code,
154
+ signal: signal
155
+ });
156
+ });
157
+ }));
158
+ }
159
+ }, _callee2);
160
+ }));
161
+ return _execInteractive.apply(this, arguments);
162
+ }
163
+ export function execCommand(containerId, cmd) {
164
+ var options = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {};
165
+ return new Promise(function (resolve, reject) {
166
+ var container = docker.getContainer(containerId);
167
+ container.exec({
168
+ Cmd: cmd,
169
+ AttachStdout: true,
170
+ AttachStderr: true,
171
+ Tty: false
172
+ }, function (err, exec) {
173
+ if (err) {
174
+ return reject(new Error("Exec create failed: ".concat(err.message)));
175
+ }
176
+ exec.start({
177
+ Tty: false
178
+ }, function (startErr, stream) {
179
+ if (startErr) {
180
+ return reject(new Error("Exec start failed: ".concat(startErr.message)));
181
+ }
182
+ var stdout = '';
183
+ var stderr = '';
184
+ stream.on('data', function (chunk) {
185
+ var str = chunk.toString();
186
+ // Docker multiplexed streams use 8-byte headers.
187
+ // For Tty=false, output is multiplexed; for simplicity we accumulate.
188
+ stdout += str;
189
+ });
190
+ stream.on('end', function () {
191
+ exec.inspect(function (inspectErr, info) {
192
+ var _info$ExitCode;
193
+ resolve({
194
+ stdout: stdout.trim(),
195
+ stderr: stderr.trim(),
196
+ exitCode: (_info$ExitCode = info === null || info === void 0 ? void 0 : info.ExitCode) !== null && _info$ExitCode !== void 0 ? _info$ExitCode : -1
197
+ });
198
+ });
199
+ });
200
+ stream.on('error', function (streamErr) {
201
+ reject(new Error("Stream error: ".concat(streamErr.message)));
202
+ });
203
+ if (options.timeout) {
204
+ setTimeout(function () {
205
+ reject(new Error('Command timed out'));
206
+ }, options.timeout);
207
+ }
208
+ });
209
+ });
210
+ });
211
+ }