dex-termux-cli 0.2.3 → 0.3.0-beta.1

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.
@@ -5,6 +5,7 @@ import { spawn } from 'node:child_process';
5
5
  import { loadUserConfig } from '../core/config.js';
6
6
  import { detectProjectContext } from '../utils/project-context.js';
7
7
  import { resolveInteractiveShell } from '../utils/shell.js';
8
+ import { isAndroidStoragePath, resolvePlatformMode, shouldRestrictProjectToAndroidStorage } from '../utils/platform.js';
8
9
 
9
10
  export async function runSafeShellCommand() {
10
11
  const config = await loadUserConfig();
@@ -13,20 +14,23 @@ export async function runSafeShellCommand() {
13
14
  throw new Error('La instalacion segura de proyectos esta desactivada. Activa esa funcion antes de abrir el modo seguro.');
14
15
  }
15
16
 
16
- const projectRoot = process.cwd();
17
+ const currentDir = process.cwd();
17
18
  const projectContext = await detectProjectContext();
19
+ const platformMode = resolvePlatformMode(config);
18
20
 
19
21
  if (!projectContext || !supportsSafeMode(projectContext.type)) {
20
- throw new Error('El modo seguro de Dex por ahora solo existe para proyectos Python, Node y PHP.');
22
+ throw new Error('El modo seguro de Dex por ahora solo existe para proyectos Python, Node, PHP, Ruby, Go, Rust y Java.');
21
23
  }
22
24
 
23
- if (!isAndroidStoragePath(projectRoot)) {
24
- throw new Error('El modo seguro de Dex por ahora se usa en proyectos Python, Node o PHP dentro de Android storage.');
25
+ const projectRoot = projectContext.projectRoot || currentDir;
26
+
27
+ if (shouldRestrictProjectToAndroidStorage(platformMode) && !isAndroidStoragePath(projectRoot)) {
28
+ throw new Error('El modo seguro de Dex en Termux por ahora se usa en proyectos Python, Node, PHP, Ruby, Go, Rust o Java dentro de Android storage.');
25
29
  }
26
30
 
27
31
  const projectKey = getProjectKey(projectRoot);
28
32
  const projectState = config.projects && config.projects[projectKey] ? config.projects[projectKey] : null;
29
- const interactiveShell = await resolveInteractiveShell();
33
+ const interactiveShell = await resolveInteractiveShell(platformMode);
30
34
  const reason = projectState && projectState.preferSafeInstall
31
35
  ? 'Este proyecto ya quedo marcado para modo seguro.'
32
36
  : 'Entrando al entorno seguro manual de Dex.';
@@ -46,6 +50,9 @@ export async function runSafeShellCommand() {
46
50
 
47
51
  console.log('Modo Dex : shell segura para ' + path.basename(projectRoot));
48
52
  console.log('Proyecto : ' + projectRoot);
53
+ if (currentDir !== projectRoot) {
54
+ console.log('Desde : ' + currentDir);
55
+ }
49
56
  console.log('Entorno : ' + venvDir);
50
57
  console.log('Python : ' + rescuePython);
51
58
  console.log('Shell : ' + interactiveShell.shellName + ' interactiva');
@@ -61,6 +68,9 @@ export async function runSafeShellCommand() {
61
68
  DEX_CONTEXT: 'safe-shell',
62
69
  DEX_SAFE_PROJECT: projectRoot,
63
70
  DEX_SAFE_PROJECT_KEY: projectKey,
71
+ DEX_SAFE_WORKSPACE: projectRoot,
72
+ DEX_SAFE_LABEL: 'PYTHON SAFE',
73
+ DEX_SAFE_RUNTIME: 'python',
64
74
  });
65
75
  return;
66
76
  }
@@ -74,14 +84,18 @@ export async function runSafeShellCommand() {
74
84
  try {
75
85
  await fs.access(packageJsonPath);
76
86
  await fs.access(nodeModulesDir);
87
+ await ensureSafeWorkspaceMatchesProject(safeProjectDir, projectRoot, projectContext.type);
77
88
  } catch {
78
- throw new Error('No encontre el espacio seguro de este proyecto Node. Corre dex -i primero para prepararlo.');
89
+ throw new Error('No encontre un espacio seguro valido para este proyecto Node. Corre dex -i primero para recrearlo.');
79
90
  }
80
91
 
81
92
  const nextPath = nodeBinDir + ':' + (process.env.PATH || '');
82
93
 
83
94
  console.log('Modo Dex : shell segura para ' + path.basename(projectRoot));
84
95
  console.log('Proyecto : ' + projectRoot);
96
+ if (currentDir !== projectRoot) {
97
+ console.log('Desde : ' + currentDir);
98
+ }
85
99
  console.log('Seguro : ' + safeProjectDir);
86
100
  console.log('Modulos : ' + nodeModulesDir);
87
101
  console.log('Shell : ' + interactiveShell.shellName + ' interactiva');
@@ -97,6 +111,8 @@ export async function runSafeShellCommand() {
97
111
  DEX_SAFE_PROJECT: projectRoot,
98
112
  DEX_SAFE_PROJECT_KEY: projectKey,
99
113
  DEX_SAFE_WORKSPACE: safeProjectDir,
114
+ DEX_SAFE_LABEL: 'NODE SAFE',
115
+ DEX_SAFE_RUNTIME: 'node',
100
116
  });
101
117
  return;
102
118
  }
@@ -111,14 +127,18 @@ export async function runSafeShellCommand() {
111
127
  try {
112
128
  await fs.access(composerJsonPath);
113
129
  await fs.access(vendorDir);
130
+ await ensureSafeWorkspaceMatchesProject(safeProjectDir, projectRoot, projectContext.type);
114
131
  } catch {
115
- throw new Error('No encontre el espacio seguro de este proyecto PHP. Corre dex -i primero para prepararlo.');
132
+ throw new Error('No encontre un espacio seguro valido para este proyecto PHP. Corre dex -i primero para recrearlo.');
116
133
  }
117
134
 
118
135
  const nextPath = vendorBinDir + ':' + (process.env.PATH || '');
119
136
 
120
137
  console.log('Modo Dex : shell segura para ' + path.basename(projectRoot));
121
138
  console.log('Proyecto : ' + projectRoot);
139
+ if (currentDir !== projectRoot) {
140
+ console.log('Desde : ' + currentDir);
141
+ }
122
142
  console.log('Seguro : ' + safeProjectDir);
123
143
  console.log('Vendor : ' + vendorDir);
124
144
  console.log('Shell : ' + interactiveShell.shellName + ' interactiva');
@@ -137,7 +157,154 @@ export async function runSafeShellCommand() {
137
157
  DEX_SAFE_PROJECT: projectRoot,
138
158
  DEX_SAFE_PROJECT_KEY: projectKey,
139
159
  DEX_SAFE_WORKSPACE: safeProjectDir,
160
+ DEX_SAFE_LABEL: 'PHP SAFE',
161
+ DEX_SAFE_RUNTIME: 'php',
162
+ });
163
+ return;
164
+ }
165
+
166
+ if (projectContext.type === 'ruby') {
167
+ const safeProjectDir = getSafeProjectWorkspacePath(projectRoot, projectContext.type);
168
+ const gemfilePath = path.join(safeProjectDir, 'Gemfile');
169
+
170
+ try {
171
+ await fs.access(gemfilePath);
172
+ await ensureSafeWorkspaceMatchesProject(safeProjectDir, projectRoot, projectContext.type);
173
+ } catch {
174
+ throw new Error('No encontre un espacio seguro valido para este proyecto Ruby. Corre dex -i primero para recrearlo.');
175
+ }
176
+
177
+ console.log('Modo Dex : shell segura para ' + path.basename(projectRoot));
178
+ console.log('Proyecto : ' + projectRoot);
179
+ if (currentDir !== projectRoot) {
180
+ console.log('Desde : ' + currentDir);
181
+ }
182
+ console.log('Seguro : ' + safeProjectDir);
183
+ console.log('Shell : ' + interactiveShell.shellName + ' interactiva');
184
+ console.log('Nota : ' + reason);
185
+ console.log('Usa ruby, bundle o bundle exec desde aqui.');
186
+ console.log('Esta shell abre la copia segura del proyecto dentro de HOME.');
187
+ console.log('Escribe exit para volver a tu shell normal.');
188
+ console.log('');
189
+
190
+ await openSafeShell(interactiveShell, safeProjectDir, {
191
+ DEX_CONTEXT: 'safe-shell',
192
+ DEX_SAFE_PROJECT: projectRoot,
193
+ DEX_SAFE_PROJECT_KEY: projectKey,
194
+ DEX_SAFE_WORKSPACE: safeProjectDir,
195
+ DEX_SAFE_LABEL: 'RUBY SAFE',
196
+ DEX_SAFE_RUNTIME: 'ruby',
197
+ });
198
+ return;
199
+ }
200
+
201
+ if (projectContext.type === 'go') {
202
+ const safeProjectDir = getSafeProjectWorkspacePath(projectRoot, projectContext.type);
203
+ const goModPath = path.join(safeProjectDir, 'go.mod');
204
+
205
+ try {
206
+ await fs.access(goModPath);
207
+ await ensureSafeWorkspaceMatchesProject(safeProjectDir, projectRoot, projectContext.type);
208
+ } catch {
209
+ throw new Error('No encontre un espacio seguro valido para este proyecto Go. Corre dex -i primero para recrearlo.');
210
+ }
211
+
212
+ console.log('Modo Dex : shell segura para ' + path.basename(projectRoot));
213
+ console.log('Proyecto : ' + projectRoot);
214
+ if (currentDir !== projectRoot) {
215
+ console.log('Desde : ' + currentDir);
216
+ }
217
+ console.log('Seguro : ' + safeProjectDir);
218
+ console.log('Shell : ' + interactiveShell.shellName + ' interactiva');
219
+ console.log('Nota : ' + reason);
220
+ console.log('Usa go run, go test o go build desde aqui.');
221
+ console.log('Esta shell abre la copia segura del proyecto dentro de HOME.');
222
+ console.log('Escribe exit para volver a tu shell normal.');
223
+ console.log('');
224
+
225
+ await openSafeShell(interactiveShell, safeProjectDir, {
226
+ DEX_CONTEXT: 'safe-shell',
227
+ DEX_SAFE_PROJECT: projectRoot,
228
+ DEX_SAFE_PROJECT_KEY: projectKey,
229
+ DEX_SAFE_WORKSPACE: safeProjectDir,
230
+ DEX_SAFE_LABEL: 'GO SAFE',
231
+ DEX_SAFE_RUNTIME: 'go',
140
232
  });
233
+ return;
234
+ }
235
+
236
+ if (projectContext.type === 'rust') {
237
+ const safeProjectDir = getSafeProjectWorkspacePath(projectRoot, projectContext.type);
238
+ const cargoTomlPath = path.join(safeProjectDir, 'Cargo.toml');
239
+
240
+ try {
241
+ await fs.access(cargoTomlPath);
242
+ await ensureSafeWorkspaceMatchesProject(safeProjectDir, projectRoot, projectContext.type);
243
+ } catch {
244
+ throw new Error('No encontre un espacio seguro valido para este proyecto Rust. Corre dex -i primero para recrearlo.');
245
+ }
246
+
247
+ console.log('Modo Dex : shell segura para ' + path.basename(projectRoot));
248
+ console.log('Proyecto : ' + projectRoot);
249
+ if (currentDir !== projectRoot) {
250
+ console.log('Desde : ' + currentDir);
251
+ }
252
+ console.log('Seguro : ' + safeProjectDir);
253
+ console.log('Shell : ' + interactiveShell.shellName + ' interactiva');
254
+ console.log('Nota : ' + reason);
255
+ console.log('Usa cargo build, cargo test o cargo run desde aqui.');
256
+ console.log('Esta shell abre la copia segura del proyecto dentro de HOME.');
257
+ console.log('Escribe exit para volver a tu shell normal.');
258
+ console.log('');
259
+
260
+ await openSafeShell(interactiveShell, safeProjectDir, {
261
+ DEX_CONTEXT: 'safe-shell',
262
+ DEX_SAFE_PROJECT: projectRoot,
263
+ DEX_SAFE_PROJECT_KEY: projectKey,
264
+ DEX_SAFE_WORKSPACE: safeProjectDir,
265
+ DEX_SAFE_LABEL: 'RUST SAFE',
266
+ DEX_SAFE_RUNTIME: 'rust',
267
+ });
268
+ return;
269
+ }
270
+
271
+ if (projectContext.type === 'java') {
272
+ const safeProjectDir = getSafeProjectWorkspacePath(projectRoot, projectContext.type);
273
+ const pomPath = path.join(safeProjectDir, 'pom.xml');
274
+ const gradlePath = path.join(safeProjectDir, 'build.gradle');
275
+ const gradleKtsPath = path.join(safeProjectDir, 'build.gradle.kts');
276
+
277
+ try {
278
+ await ensureSafeWorkspaceMatchesProject(safeProjectDir, projectRoot, projectContext.type);
279
+ if (!(await hasJavaBuildFiles(pomPath, gradlePath, gradleKtsPath))) {
280
+ throw new Error('missing-java-build-files');
281
+ }
282
+ } catch {
283
+ throw new Error('No encontre un espacio seguro valido para este proyecto Java. Corre dex -i primero para recrearlo.');
284
+ }
285
+
286
+ console.log('Modo Dex : shell segura para ' + path.basename(projectRoot));
287
+ console.log('Proyecto : ' + projectRoot);
288
+ if (currentDir !== projectRoot) {
289
+ console.log('Desde : ' + currentDir);
290
+ }
291
+ console.log('Seguro : ' + safeProjectDir);
292
+ console.log('Shell : ' + interactiveShell.shellName + ' interactiva');
293
+ console.log('Nota : ' + reason);
294
+ console.log('Usa mvn, ./mvnw, gradle o ./gradlew desde aqui segun el proyecto.');
295
+ console.log('Esta shell abre la copia segura del proyecto dentro de HOME.');
296
+ console.log('Escribe exit para volver a tu shell normal.');
297
+ console.log('');
298
+
299
+ await openSafeShell(interactiveShell, safeProjectDir, {
300
+ DEX_CONTEXT: 'safe-shell',
301
+ DEX_SAFE_PROJECT: projectRoot,
302
+ DEX_SAFE_PROJECT_KEY: projectKey,
303
+ DEX_SAFE_WORKSPACE: safeProjectDir,
304
+ DEX_SAFE_LABEL: 'JAVA SAFE',
305
+ DEX_SAFE_RUNTIME: 'java',
306
+ });
307
+ return;
141
308
  }
142
309
  }
143
310
 
@@ -167,12 +334,46 @@ function getComposerHome() {
167
334
  return path.join(home, '.dex', 'composer');
168
335
  }
169
336
 
170
- function isAndroidStoragePath(projectRoot) {
171
- return projectRoot.startsWith('/sdcard') || projectRoot.startsWith('/storage/emulated/0');
337
+ async function hasJavaBuildFiles(pomPath, gradlePath, gradleKtsPath) {
338
+ return Boolean(await pathExists(pomPath) || await pathExists(gradlePath) || await pathExists(gradleKtsPath));
339
+ }
340
+
341
+ async function ensureSafeWorkspaceMatchesProject(safeProjectDir, projectRoot, projectType) {
342
+ const metadataPath = path.join(safeProjectDir, '.dex-workspace.json');
343
+ let metadataRaw = '';
344
+
345
+ try {
346
+ metadataRaw = await fs.readFile(metadataPath, 'utf8');
347
+ } catch {
348
+ return;
349
+ }
350
+
351
+ try {
352
+ const metadata = JSON.parse(metadataRaw);
353
+ if (metadata.projectRoot && metadata.projectRoot !== projectRoot) {
354
+ throw new Error('workspace-root-mismatch');
355
+ }
356
+ if (metadata.projectType && metadata.projectType !== projectType) {
357
+ throw new Error('workspace-type-mismatch');
358
+ }
359
+ } catch (error) {
360
+ if (error && (error.message === 'workspace-root-mismatch' || error.message === 'workspace-type-mismatch')) {
361
+ throw error;
362
+ }
363
+ }
364
+ }
365
+
366
+ async function pathExists(targetPath) {
367
+ try {
368
+ await fs.access(targetPath);
369
+ return true;
370
+ } catch {
371
+ return false;
372
+ }
172
373
  }
173
374
 
174
375
  function supportsSafeMode(projectType) {
175
- return projectType === 'python' || projectType === 'node' || projectType === 'php';
376
+ return projectType === 'python' || projectType === 'node' || projectType === 'php' || projectType === 'ruby' || projectType === 'go' || projectType === 'rust' || projectType === 'java';
176
377
  }
177
378
 
178
379
  function openSafeShell(interactiveShell, cwd, envPatch) {
@@ -1,8 +1,10 @@
1
1
  import fs from 'node:fs/promises';
2
+ import { loadUserConfig } from '../core/config.js';
2
3
  import { chooseSearchScope } from '../ui/prompt.js';
3
4
  import { formatSearchResults, printSection } from '../ui/output.js';
4
5
  import { getScopeOptions, resolveScopeRoot } from '../core/scopes.js';
5
6
  import { searchEntries } from '../utils/fs-search.js';
7
+ import { resolvePlatformMode } from '../utils/platform.js';
6
8
 
7
9
  export async function runSearchCommand({ pattern, scope }) {
8
10
  const cleanPattern = (pattern || '').trim();
@@ -11,7 +13,9 @@ export async function runSearchCommand({ pattern, scope }) {
11
13
  throw new Error('Debes indicar un patron. Ejemplo: dex -b "archivo"');
12
14
  }
13
15
 
14
- const scopeOptions = getScopeOptions();
16
+ const config = await loadUserConfig();
17
+ const platformMode = resolvePlatformMode(config);
18
+ const scopeOptions = getScopeOptions(platformMode);
15
19
  const selectedScope = scope || await chooseSearchScope(scopeOptions);
16
20
  const root = resolveScopeRoot(selectedScope, scopeOptions);
17
21
 
@@ -1,12 +1,16 @@
1
1
  import fs from 'node:fs/promises';
2
2
  import path from 'node:path';
3
+ import { loadUserConfig } from '../core/config.js';
3
4
  import { chooseSearchScope } from '../ui/prompt.js';
4
5
  import { formatTreeReport, printSection } from '../ui/output.js';
5
6
  import { getScopeOptions, resolveScopeRoot } from '../core/scopes.js';
6
7
  import { buildTreeReport } from '../utils/fs-tree.js';
8
+ import { resolvePlatformMode } from '../utils/platform.js';
7
9
 
8
10
  export async function runTreeCommand({ target, scope, depth }) {
9
- const scopeOptions = getScopeOptions();
11
+ const config = await loadUserConfig();
12
+ const platformMode = resolvePlatformMode(config);
13
+ const scopeOptions = getScopeOptions(platformMode);
10
14
  const selectedScope = scope || await chooseSearchScope(scopeOptions);
11
15
  const root = resolveScopeRoot(selectedScope, scopeOptions);
12
16
 
package/src/core/args.js CHANGED
@@ -32,6 +32,16 @@ export function parseArgs(argv) {
32
32
  continue;
33
33
  }
34
34
 
35
+ if (token === '--prompt-project-root') {
36
+ parsed.command = 'prompt-project-root';
37
+ continue;
38
+ }
39
+
40
+ if (token === '--prompt-project-path') {
41
+ parsed.command = 'prompt-project-path';
42
+ continue;
43
+ }
44
+
35
45
  if (token === '-m' || token === '--menu' || token === 'menu') {
36
46
  parsed.command = 'menu';
37
47
  continue;
@@ -1,5 +1,6 @@
1
1
  import fs from 'node:fs/promises';
2
2
  import path from 'node:path';
3
+ import { getHomeDirectory, normalizePlatformMode } from '../utils/platform.js';
3
4
 
4
5
  const DEFAULT_CONFIG = {
5
6
  features: {
@@ -10,12 +11,14 @@ const DEFAULT_CONFIG = {
10
11
  ui: {
11
12
  promptContextPosition: 'right',
12
13
  },
14
+ runtime: {
15
+ platformMode: 'auto',
16
+ },
13
17
  projects: {},
14
18
  };
15
19
 
16
20
  export function getUserConfigPath() {
17
- const home = process.env.HOME || '/data/data/com.termux/files/home';
18
- return path.join(home, '.config', 'dex', 'config.json');
21
+ return path.join(getHomeDirectory(), '.config', 'dex', 'config.json');
19
22
  }
20
23
 
21
24
  export async function loadUserConfig() {
@@ -71,6 +74,33 @@ export async function setPromptContextPosition(position) {
71
74
  return saveUserConfig(nextConfig);
72
75
  }
73
76
 
77
+ export async function setPlatformMode(platformMode) {
78
+ const config = await loadUserConfig();
79
+ const normalized = normalizePlatformMode(platformMode);
80
+ const nextFeatures = {
81
+ ...config.features,
82
+ };
83
+
84
+ if (normalized === 'linux') {
85
+ nextFeatures.androidShortcut = false;
86
+ nextFeatures.projectBadge = true;
87
+ nextFeatures.smartProjectInstall = true;
88
+ }
89
+
90
+ if (normalized === 'termux') {
91
+ nextFeatures.androidShortcut = true;
92
+ }
93
+
94
+ return saveUserConfig({
95
+ ...config,
96
+ features: nextFeatures,
97
+ runtime: {
98
+ ...config.runtime,
99
+ platformMode: normalized,
100
+ },
101
+ });
102
+ }
103
+
74
104
  export async function saveProjectState(projectKey, projectState) {
75
105
  const config = await loadUserConfig();
76
106
  const nextConfig = {
@@ -124,6 +154,11 @@ function mergeConfig(config) {
124
154
  ...(config.ui || {}),
125
155
  promptContextPosition: normalizePromptContextPosition(config.ui?.promptContextPosition),
126
156
  },
157
+ runtime: {
158
+ ...DEFAULT_CONFIG.runtime,
159
+ ...(config.runtime || {}),
160
+ platformMode: normalizePlatformMode(config.runtime?.platformMode),
161
+ },
127
162
  projects: {
128
163
  ...DEFAULT_CONFIG.projects,
129
164
  ...(config.projects || {}),
@@ -139,6 +174,9 @@ function cloneDefaultConfig() {
139
174
  ui: {
140
175
  ...DEFAULT_CONFIG.ui,
141
176
  },
177
+ runtime: {
178
+ ...DEFAULT_CONFIG.runtime,
179
+ },
142
180
  projects: {
143
181
  ...DEFAULT_CONFIG.projects,
144
182
  },
@@ -1,9 +1,14 @@
1
1
  import path from 'node:path';
2
+ import {
3
+ canUseAndroidFeatures,
4
+ getAndroidStorageRoot,
5
+ getHomeDirectory,
6
+ getHomeScopeDescription,
7
+ getHomeScopeLabel,
8
+ } from '../utils/platform.js';
2
9
 
3
- export function getScopeOptions() {
4
- const home = process.env.HOME || '/data/data/com.termux/files/home';
5
-
6
- return [
10
+ export function getScopeOptions(platformMode) {
11
+ const options = [
7
12
  {
8
13
  key: 'actual',
9
14
  label: 'Carpeta actual',
@@ -12,20 +17,25 @@ export function getScopeOptions() {
12
17
  },
13
18
  {
14
19
  key: 'home',
15
- label: 'Home de Termux',
16
- root: home,
17
- description: 'Busca dentro de tu entorno principal de Termux.',
20
+ label: getHomeScopeLabel(platformMode),
21
+ root: getHomeDirectory(),
22
+ description: getHomeScopeDescription(platformMode),
18
23
  },
19
- {
24
+ ];
25
+
26
+ if (canUseAndroidFeatures(platformMode)) {
27
+ options.push({
20
28
  key: 'android',
21
29
  label: 'Almacenamiento Android',
22
- root: '/sdcard',
30
+ root: getAndroidStorageRoot(platformMode),
23
31
  description: 'Busca dentro del almacenamiento compartido accesible.',
24
- },
25
- ];
32
+ });
33
+ }
34
+
35
+ return options;
26
36
  }
27
37
 
28
- export function resolveScopeRoot(scopeKey, options = getScopeOptions()) {
38
+ export function resolveScopeRoot(scopeKey, options = []) {
29
39
  const found = options.find((option) => option.key === scopeKey);
30
40
  return found ? path.resolve(found.root) : '';
31
41
  }
package/src/ui/output.js CHANGED
@@ -12,7 +12,8 @@ const ANSI = {
12
12
  export function printHelp() {
13
13
  printBanner();
14
14
  console.log('');
15
- console.log(' Explorar, entender y preparar proyectos desde Termux y Android storage.');
15
+ console.log(' Explorar, entender y preparar proyectos desde Termux o Linux.');
16
+ console.log(' Android storage aparece cuando el modo de plataforma lo permite.');
16
17
  console.log('');
17
18
 
18
19
  printBlock('Comandos principales', [
@@ -22,22 +23,23 @@ export function printHelp() {
22
23
  'dex -b "archivo" buscar archivos o carpetas',
23
24
  'dex -e ls explicar un comando',
24
25
  'dex -t src arbol guiado de una ruta',
25
- 'dex -i instalar dependencias del proyecto',
26
- 'dex -r abrir modo seguro del proyecto',
26
+ 'dex -i instalar dependencias del proyecto detectado',
27
+ 'dex -r abrir modo seguro del proyecto (Py/Node/PHP/Ruby/Go/Rust/Java)',
27
28
  'dex -a acceso rapido a Android',
28
29
  ]);
29
30
 
30
31
  printBlock('Uso rapido', [
31
32
  'dex -b "*.js" --scope actual',
32
- 'dex -t . --depth 3',
33
+ 'dex -t . --depth 3 --scope actual',
33
34
  'dex -c',
34
35
  'dex --prompt-context',
36
+ 'dex-project-context --root',
35
37
  ]);
36
38
 
37
39
  printBlock('Scopes', [
38
40
  'actual carpeta donde estas parado',
39
- 'home tu HOME de Termux',
40
- 'android almacenamiento accesible',
41
+ 'home tu HOME del entorno actual',
42
+ 'android almacenamiento accesible en modo Termux',
41
43
  ]);
42
44
 
43
45
  printBlock('Atajos', [
package/src/ui/prompt.js CHANGED
@@ -3,14 +3,14 @@ import { moveCursor, cursorTo } from 'node:readline';
3
3
  import { stdin as input, stdout as output } from 'node:process';
4
4
 
5
5
  const ANSI = {
6
- clearDown: '\x1B[0J',
7
- cyan: '\x1B[36m',
8
- dim: '\x1B[2m',
9
- green: '\x1B[32m',
10
- red: '\x1B[31m',
11
- reset: '\x1B[0m',
12
- yellow: '\x1B[33m',
13
- bold: '\x1B[1m',
6
+ clearDown: '',
7
+ cyan: '',
8
+ dim: '',
9
+ green: '',
10
+ red: '',
11
+ reset: '',
12
+ yellow: '',
13
+ bold: '',
14
14
  };
15
15
 
16
16
  export async function chooseSearchScope(options) {
@@ -20,7 +20,7 @@ export async function chooseSearchScope(options) {
20
20
  getValue: (option) => option.key,
21
21
  getLines: (option) => [option.label, option.description, option.root],
22
22
  prompt: 'Scope [1-' + options.length + ', Enter=1]: ',
23
- errorMessage: 'Opcion no valida. Usa un numero o actual/home/android.',
23
+ errorMessage: 'Opcion no valida. Usa un numero o una clave valida.',
24
24
  directMatch: (answer) => options.find((option) => option.key === answer.toLowerCase())?.key,
25
25
  });
26
26
  }
@@ -47,7 +47,7 @@ export async function chooseMenuAction() {
47
47
  { key: 'safe-shell', label: 'Entrar al modo seguro', hint: 'abre el proyecto con el entorno correcto' },
48
48
  { key: 'version', label: 'Ver version de Dex', hint: 'compara local contra la version publicada' },
49
49
  { key: 'context', label: 'Ver contexto del proyecto', hint: 'detecta el lenguaje del proyecto actual' },
50
- { key: 'settings', label: 'Ajustes y funciones', hint: 'activa extras y mueve el badge del prompt' },
50
+ { key: 'settings', label: 'Ajustes y funciones', hint: 'activa extras, plataforma y prompt' },
51
51
  { key: 'help', label: 'Ver ayuda', hint: 'muestra comandos y ejemplos' },
52
52
  { key: 'exit', label: 'Salir', hint: 'cerrar el menu' },
53
53
  ];
@@ -67,11 +67,17 @@ export async function chooseMenuAction() {
67
67
 
68
68
  export async function chooseSettingsAction(config) {
69
69
  const options = [
70
+ {
71
+ key: 'platform-mode',
72
+ label: 'Modo de plataforma',
73
+ status: formatPlatformMode(config.runtime?.platformMode || 'auto'),
74
+ usage: 'elige auto, termux o linux',
75
+ },
70
76
  {
71
77
  key: 'toggle-android-shortcut',
72
78
  label: 'Acceso rapido a Android',
73
79
  status: formatStatus(config.features.androidShortcut),
74
- usage: 'permite dex -a',
80
+ usage: 'permite dex -a en Termux',
75
81
  },
76
82
  {
77
83
  key: 'toggle-project-badge',
@@ -89,7 +95,7 @@ export async function chooseSettingsAction(config) {
89
95
  key: 'toggle-smart-project-install',
90
96
  label: 'Instalacion segura de proyectos',
91
97
  status: formatStatus(config.features.smartProjectInstall),
92
- usage: 'rescata instalaciones y puede instalar runtimes con pkg',
98
+ usage: 'rescata instalaciones y usa modo seguro por lenguaje',
93
99
  },
94
100
  {
95
101
  key: 'back',
@@ -113,7 +119,7 @@ export async function chooseSettingsAction(config) {
113
119
  ];
114
120
  },
115
121
  prompt: 'Ajuste [1-' + options.length + ', Enter=1]: ',
116
- errorMessage: 'Opcion no valida. Usa 1, 2, 3, 4 o 5.',
122
+ errorMessage: 'Opcion no valida. Usa 1, 2, 3, 4, 5 o 6.',
117
123
  directMatch: (answer) => options.find((option) => option.key === answer.toLowerCase())?.key,
118
124
  style: 'card',
119
125
  introLines: ['Activa solo lo que quieras ver todos los dias.'],
@@ -165,6 +171,51 @@ export async function choosePromptContextPosition(currentPosition) {
165
171
  });
166
172
  }
167
173
 
174
+ export async function choosePlatformMode(currentMode) {
175
+ const options = [
176
+ {
177
+ key: 'auto',
178
+ label: 'Auto',
179
+ usage: 'detecta si estas en Termux o Linux segun el entorno real',
180
+ },
181
+ {
182
+ key: 'termux',
183
+ label: 'Modo Termux',
184
+ usage: 'activa comportamiento centrado en Android storage y Termux',
185
+ },
186
+ {
187
+ key: 'linux',
188
+ label: 'Modo Linux',
189
+ usage: 'desactiva Android y asume un entorno Linux puro',
190
+ },
191
+ {
192
+ key: 'back',
193
+ label: 'Volver',
194
+ },
195
+ ];
196
+
197
+ return chooseNumericOption({
198
+ title: 'Modo de plataforma',
199
+ options,
200
+ getValue: (option) => option.key,
201
+ getLines: (option) => {
202
+ if (option.key === 'back') {
203
+ return [option.label];
204
+ }
205
+
206
+ return [
207
+ option.label,
208
+ 'Uso : ' + option.usage,
209
+ ];
210
+ },
211
+ prompt: 'Modo [1-' + options.length + ', Enter=1]: ',
212
+ errorMessage: 'Opcion no valida. Usa 1, 2, 3 o 4.',
213
+ directMatch: (answer) => options.find((option) => option.key === answer.toLowerCase())?.key,
214
+ style: 'card',
215
+ introLines: ['Actual: ' + formatPlatformMode(currentMode)],
216
+ });
217
+ }
218
+
168
219
  export async function chooseFeatureToggle(currentValue, featureName, description) {
169
220
  const options = [
170
221
  {
@@ -371,6 +422,18 @@ function formatPromptPosition(position) {
371
422
  return 'derecha';
372
423
  }
373
424
 
425
+ function formatPlatformMode(mode) {
426
+ if (mode === 'linux') {
427
+ return 'linux';
428
+ }
429
+
430
+ if (mode === 'termux') {
431
+ return 'termux';
432
+ }
433
+
434
+ return 'auto';
435
+ }
436
+
374
437
  function colorize(text, color, weight = '') {
375
438
  if (!output.isTTY) {
376
439
  return text;