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

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.
package/README.md CHANGED
@@ -35,13 +35,13 @@ Dex mejora tareas comunes de terminal sin tapar el entorno real. Sirve para expl
35
35
  - `dex -b`: busqueda guiada
36
36
  - `dex -e`: explicacion de comandos comunes
37
37
  - `dex -t`: tree guiado
38
- - `dex -a`: shell redisenada para Android storage en modo Termux
38
+ - `dex -a`: shell de acceso rapido redisenada para Android storage en Termux o para HOME en Linux
39
39
  - `dex -i`: instalacion del proyecto actual incluso desde subcarpetas del proyecto
40
40
  - `dex -r`: shell segura del proyecto actual incluso desde subcarpetas del proyecto
41
41
 
42
42
  ## Contexto del proyecto
43
43
 
44
- Dex detecta el lenguaje segun los archivos del directorio actual. El badge compacto del prompt usa formato limpio como `PYTHON`, `NODE` o `PHP`. La version detectada del runtime se ve en `dex -c`, no dentro del badge.
44
+ Dex detecta el lenguaje segun los archivos del directorio actual y tambien desde estructuras simples como `src/main.py` o `app/main.js`. El badge compacto del prompt usa formato limpio como `PYTHON`, `NODE` o `PHP`. La version detectada del runtime se ve en `dex -c`, no dentro del badge.
45
45
 
46
46
  Lenguajes detectables actualmente:
47
47
 
@@ -78,7 +78,7 @@ El modo actual se guarda en `~/.config/dex/config.json`.
78
78
  ## Estado real de la beta
79
79
 
80
80
  - `dex -m`, `-a`, `-b`, `-t`, `-v`, `-c` y `--prompt-context` funcionan
81
- - `dex -a` ahora abre una interfaz Android dedicada con splash, prompt propio y atajos (`shared`, `dl`, `docs`, `dcim`, `pics`, `music`, `movies`)
81
+ - `dex -a` ahora abre una shell de acceso rapido con prompt propio y atajos. En Termux apunta a Android storage y en Linux apunta a HOME
82
82
  - `dex -i` funciona en proyectos validos y ahora usa el root detectado del proyecto aunque entres desde una subcarpeta
83
83
  - `dex -r` funciona para proyectos soportados, hereda mejor el root del proyecto y en Termux exige Android storage
84
84
  - el modo seguro real ya cubre Python, Node, PHP, Ruby, Go, Rust y Java
package/package.json CHANGED
@@ -1,15 +1,20 @@
1
1
  {
2
2
  "name": "dex-termux-cli",
3
- "version": "0.3.0-beta.1",
3
+ "version": "0.3.0-beta.2",
4
4
  "type": "module",
5
- "description": "Visual CLI for Termux and Linux with guided search, readable tree views, project context, version checks, and safe flows for Python, Node, and PHP.",
5
+ "description": "Visual CLI for Termux and Linux with guided search, readable tree views, project context, version checks, Android mode, and safe flows for Python, Node, PHP, Ruby, Go, Rust, and Java.",
6
6
  "keywords": [
7
7
  "termux",
8
+ "linux",
8
9
  "android",
9
10
  "cli",
10
11
  "python",
11
12
  "node",
12
13
  "php",
14
+ "ruby",
15
+ "go",
16
+ "rust",
17
+ "java",
13
18
  "safe-shell"
14
19
  ],
15
20
  "license": "MIT",
@@ -18,7 +23,8 @@
18
23
  },
19
24
  "scripts": {
20
25
  "start": "node ./bin/dex",
21
- "help": "node ./bin/dex --help"
26
+ "help": "node ./bin/dex --help",
27
+ "test": "node --test ./tests/**/*.test.js"
22
28
  },
23
29
  "author": "farllirs",
24
30
  "files": [
@@ -38,8 +44,5 @@
38
44
  },
39
45
  "engines": {
40
46
  "node": ">=18"
41
- },
42
- "dependencies": {
43
- "tree": "^0.1.3"
44
47
  }
45
48
  }
@@ -1,8 +1,18 @@
1
1
  import fs from 'node:fs/promises';
2
+ import os from 'node:os';
3
+ import path from 'node:path';
2
4
  import { spawn } from 'node:child_process';
3
5
  import { getUserConfigPath, loadUserConfig } from '../core/config.js';
4
6
  import { resolveInteractiveShell } from '../utils/shell.js';
5
- import { canUseAndroidFeatures, getAndroidStorageRoot, resolvePlatformMode } from '../utils/platform.js';
7
+ import {
8
+ getQuickAccessAliases,
9
+ getQuickAccessLabel,
10
+ getQuickAccessModeDescription,
11
+ getQuickAccessRoot,
12
+ getQuickAccessShortcutSummary,
13
+ getQuickAccessTitle,
14
+ resolvePlatformMode,
15
+ } from '../utils/platform.js';
6
16
 
7
17
  const ANDROID_BASH_CANDIDATES = [
8
18
  '/data/data/com.termux/files/usr/bin/bash',
@@ -14,63 +24,64 @@ export async function runAndroidShellCommand() {
14
24
 
15
25
  if (!config.features.androidShortcut) {
16
26
  throw new Error(
17
- `La opcion Android esta desactivada. Activa features.androidShortcut en ${getUserConfigPath()}`,
27
+ `El acceso rapido esta desactivado. Activa features.androidShortcut en ${getUserConfigPath()}`,
18
28
  );
19
29
  }
20
30
 
21
31
  const platformMode = resolvePlatformMode(config);
22
-
23
- if (!canUseAndroidFeatures(platformMode)) {
24
- throw new Error('dex -a solo esta disponible en modo Termux/Android. Cambia el modo Linux desde ajustes si quieres evitar esta opcion.');
25
- }
26
-
27
- const androidRoot = getAndroidStorageRoot(platformMode);
32
+ const quickAccessRoot = getQuickAccessRoot(platformMode);
28
33
 
29
34
  try {
30
- await fs.access(androidRoot);
35
+ await fs.access(quickAccessRoot);
31
36
  } catch {
32
- throw new Error(`No se puede acceder a la ruta Android: ${androidRoot}`);
37
+ throw new Error(`No se puede acceder a la ruta base de acceso rapido: ${quickAccessRoot}`);
33
38
  }
34
39
 
35
40
  const interactiveShell = await resolveAndroidInteractiveShell(platformMode);
41
+ const shellSession = await createQuickAccessShellSession(platformMode, interactiveShell);
36
42
 
37
- console.log('Dex Android');
43
+ console.log(getQuickAccessTitle(platformMode));
38
44
  console.log('');
39
- console.log(`Raiz : ${androidRoot}`);
45
+ console.log(`Raiz : ${quickAccessRoot}`);
40
46
  console.log(`Shell : ${interactiveShell.shellName} interactiva`);
41
- console.log('Modo : interfaz Android redisenada');
42
- console.log('Atajos : dl, docs, dcim, pics, music, movies, shared');
47
+ console.log(`Modo : ${getQuickAccessModeDescription(platformMode)}`);
48
+ console.log(`Atajos : ${getQuickAccessShortcutSummary(platformMode)}`);
43
49
  console.log('Salida : escribe exit para volver');
44
50
  console.log('');
45
51
 
46
- await new Promise((resolve, reject) => {
47
- const child = spawn(interactiveShell.shellPath, interactiveShell.shellArgs, {
48
- cwd: androidRoot,
49
- stdio: 'inherit',
50
- env: {
51
- ...process.env,
52
- DEX_CONTEXT: 'android-shell',
53
- DEX_ANDROID_ROOT: androidRoot,
54
- DEX_ANDROID_LABEL: 'ANDROID STORAGE',
55
- DEX_ANDROID_SHELL: interactiveShell.shellName,
56
- },
57
- });
58
-
59
- child.on('error', reject);
60
- child.on('exit', (code, signal) => {
61
- if (signal) {
62
- reject(new Error(`La shell Android termino por senal: ${signal}`));
63
- return;
64
- }
65
-
66
- if (code && code !== 0) {
67
- reject(new Error(`La shell Android termino con codigo ${code}`));
68
- return;
69
- }
70
-
71
- resolve();
52
+ try {
53
+ await new Promise((resolve, reject) => {
54
+ const child = spawn(shellSession.shellPath, shellSession.shellArgs, {
55
+ cwd: quickAccessRoot,
56
+ stdio: 'inherit',
57
+ env: {
58
+ ...process.env,
59
+ ...shellSession.env,
60
+ DEX_CONTEXT: platformMode === 'termux' ? 'android-shell' : 'linux-shell',
61
+ DEX_ANDROID_ROOT: quickAccessRoot,
62
+ DEX_ANDROID_LABEL: getQuickAccessLabel(platformMode),
63
+ DEX_ANDROID_SHELL: interactiveShell.shellName,
64
+ },
65
+ });
66
+
67
+ child.on('error', reject);
68
+ child.on('exit', (code, signal) => {
69
+ if (signal) {
70
+ reject(new Error(`La shell de acceso rapido termino por senal: ${signal}`));
71
+ return;
72
+ }
73
+
74
+ if (code && code !== 0) {
75
+ reject(new Error(`La shell de acceso rapido termino con codigo ${code}`));
76
+ return;
77
+ }
78
+
79
+ resolve();
80
+ });
72
81
  });
73
- });
82
+ } finally {
83
+ await shellSession.cleanup();
84
+ }
74
85
  }
75
86
 
76
87
  async function resolveAndroidInteractiveShell(platformMode) {
@@ -95,3 +106,143 @@ async function resolveAndroidInteractiveShell(platformMode) {
95
106
 
96
107
  return interactiveShell;
97
108
  }
109
+
110
+ async function createQuickAccessShellSession(platformMode, interactiveShell) {
111
+ const tmpBase = await fs.mkdtemp(path.join(os.tmpdir(), 'dex-shell-'));
112
+ const aliases = getQuickAccessAliases(platformMode);
113
+ const root = getQuickAccessRoot(platformMode);
114
+ const label = getQuickAccessLabel(platformMode);
115
+ const rcContent = buildShellRc({
116
+ aliases,
117
+ root,
118
+ label,
119
+ shellName: interactiveShell.shellName,
120
+ platformMode,
121
+ });
122
+
123
+ if (interactiveShell.shellName === 'bash') {
124
+ const rcPath = path.join(tmpBase, 'dex-bashrc');
125
+ await fs.writeFile(rcPath, rcContent, 'utf8');
126
+ return {
127
+ shellPath: interactiveShell.shellPath,
128
+ shellArgs: ['--rcfile', rcPath, '-i'],
129
+ env: {},
130
+ cleanup: () => fs.rm(tmpBase, { recursive: true, force: true }),
131
+ };
132
+ }
133
+
134
+ if (interactiveShell.shellName === 'zsh') {
135
+ const zdotdir = path.join(tmpBase, 'zsh');
136
+ await fs.mkdir(zdotdir, { recursive: true });
137
+ await fs.writeFile(path.join(zdotdir, '.zshrc'), rcContent, 'utf8');
138
+ return {
139
+ shellPath: interactiveShell.shellPath,
140
+ shellArgs: ['-i'],
141
+ env: { ZDOTDIR: zdotdir },
142
+ cleanup: () => fs.rm(tmpBase, { recursive: true, force: true }),
143
+ };
144
+ }
145
+
146
+ const rcPath = path.join(tmpBase, 'dex-shrc');
147
+ await fs.writeFile(rcPath, rcContent, 'utf8');
148
+ return {
149
+ shellPath: interactiveShell.shellPath,
150
+ shellArgs: ['-i'],
151
+ env: { ENV: rcPath },
152
+ cleanup: () => fs.rm(tmpBase, { recursive: true, force: true }),
153
+ };
154
+ }
155
+
156
+ function buildShellRc({ aliases, root, label, shellName, platformMode }) {
157
+ if (platformMode === 'termux') {
158
+ return buildTermuxShellRc(shellName);
159
+ }
160
+
161
+ return buildLinuxShellRc({ aliases, root, label, shellName });
162
+ }
163
+
164
+ function buildTermuxShellRc(shellName) {
165
+ const lines = [
166
+ '# dex quick access shell',
167
+ 'export PATH="$HOME/bin:$PATH"',
168
+ ];
169
+
170
+ if (shellName === 'zsh') {
171
+ lines.push('[[ -f "$HOME/.zshrc" ]] && source "$HOME/.zshrc"');
172
+ lines.push('printf "Dex: acceso rapido cargado en %s\\n" "$PWD"');
173
+ return lines.join('\n') + '\n';
174
+ }
175
+
176
+ if (shellName === 'bash') {
177
+ lines.push('[[ -f "$HOME/.bashrc" ]] && source "$HOME/.bashrc"');
178
+ lines.push('printf "Dex: acceso rapido cargado en %s\\n" "$PWD"');
179
+ return lines.join('\n') + '\n';
180
+ }
181
+
182
+ lines.push('printf "Dex: acceso rapido cargado en %s\\n" "$PWD"');
183
+ return lines.join('\n') + '\n';
184
+ }
185
+
186
+ function buildLinuxShellRc({ aliases, root, label, shellName }) {
187
+ const aliasLines = Object.entries(aliases).map(([name, target]) => `alias ${name}='cd "${target}"'`);
188
+ const commonLines = [
189
+ '# dex quick access shell',
190
+ 'export PATH="$HOME/bin:$PATH"',
191
+ `export DEX_QUICK_ROOT="${root}"`,
192
+ `export DEX_QUICK_LABEL="${label}"`,
193
+ ...aliasLines,
194
+ ];
195
+
196
+ if (shellName === 'zsh') {
197
+ return [
198
+ ...commonLines,
199
+ '[[ -f "$HOME/.zshrc" ]] && source "$HOME/.zshrc"',
200
+ '_dex_linux_project_badge_zsh() {',
201
+ ' local dex_bin context',
202
+ ' dex_bin="$(command -v dex 2>/dev/null)"',
203
+ ' [[ -z "$dex_bin" ]] && return',
204
+ ' context="$($dex_bin --prompt-context 2>/dev/null)"',
205
+ ' [[ -z "$context" ]] && return',
206
+ ' print -n "%F{81}[$context]%f"',
207
+ '}',
208
+ '_dex_linux_prompt_zsh() {',
209
+ ' local badge',
210
+ ' badge="$(_dex_linux_project_badge_zsh)"',
211
+ " PROMPT=$'%F{45}Dex@linux%f %F{117}%~%f\\n%F{45}>%f '",
212
+ ' RPROMPT="$badge"',
213
+ '}',
214
+ 'autoload -Uz add-zsh-hook 2>/dev/null || true',
215
+ 'add-zsh-hook precmd _dex_linux_prompt_zsh 2>/dev/null || true',
216
+ '_dex_linux_prompt_zsh',
217
+ 'printf "Dex: acceso rapido cargado en %s\\n" "$PWD"',
218
+ ].join('\n') + '\n';
219
+ }
220
+
221
+ if (shellName === 'bash') {
222
+ return [
223
+ ...commonLines,
224
+ '_dex_linux_project_badge_bash() {',
225
+ ' local dex_bin context',
226
+ ' dex_bin="$(command -v dex 2>/dev/null)"',
227
+ ' [[ -z "$dex_bin" ]] && return',
228
+ ' context="$($dex_bin --prompt-context 2>/dev/null)"',
229
+ ' [[ -z "$context" ]] && return',
230
+ ' printf "\\[\\e[38;5;81m\\][%s]\\[\\e[0m\\]" "$context"',
231
+ '}',
232
+ '_dex_linux_prompt_bash() {',
233
+ ' local badge',
234
+ ' badge="$(_dex_linux_project_badge_bash)"',
235
+ ' PS1="\\[\\e[38;5;45m\\]Dex@linux\\[\\e[0m\\] \\[\\e[38;5;117m\\]\\w\\[\\e[0m\\] ${badge}\\n\\[\\e[38;5;45m\\]>\\[\\e[0m\\] "',
236
+ '}',
237
+ 'PROMPT_COMMAND=_dex_linux_prompt_bash',
238
+ '_dex_linux_prompt_bash',
239
+ 'printf "Dex: acceso rapido cargado en %s\\n" "$PWD"',
240
+ ].join('\n') + '\n';
241
+ }
242
+
243
+ return [
244
+ ...commonLines,
245
+ `export PS1='[dex ${label}] \\w \\$ '`,
246
+ 'printf "Dex: acceso rapido cargado en %s\\n" "$PWD"',
247
+ ].join('\n') + '\n';
248
+ }
@@ -101,8 +101,8 @@ async function runSettingsMenu() {
101
101
  if (action === 'toggle-android-shortcut') {
102
102
  const toggle = await chooseFeatureToggle(
103
103
  config.features.androidShortcut,
104
- 'Acceso rapido a Android',
105
- 'permite usar dex -a',
104
+ 'Acceso rapido',
105
+ 'permite usar dex -a en Termux o Linux',
106
106
  );
107
107
 
108
108
  if (toggle === 'back') {
@@ -111,7 +111,7 @@ async function runSettingsMenu() {
111
111
 
112
112
  const enabled = toggle === 'enable';
113
113
  await setFeatureEnabled('androidShortcut', enabled);
114
- console.log(`Acceso rapido a Android: ${enabled ? 'activado' : 'desactivado'}.`);
114
+ console.log(`Acceso rapido: ${enabled ? 'activado' : 'desactivado'}.`);
115
115
  console.log('');
116
116
  continue;
117
117
  }
@@ -69,14 +69,14 @@ async function readLatestPublishedVersion() {
69
69
  }
70
70
  }
71
71
 
72
- function compareVersions(left, right) {
73
- const leftParts = normalizeVersion(left);
74
- const rightParts = normalizeVersion(right);
75
- const maxLength = Math.max(leftParts.length, rightParts.length);
72
+ export function compareVersions(left, right) {
73
+ const leftVersion = parseSemver(left);
74
+ const rightVersion = parseSemver(right);
75
+ const maxLength = Math.max(leftVersion.core.length, rightVersion.core.length);
76
76
 
77
77
  for (let index = 0; index < maxLength; index += 1) {
78
- const leftValue = leftParts[index] || 0;
79
- const rightValue = rightParts[index] || 0;
78
+ const leftValue = leftVersion.core[index] || 0;
79
+ const rightValue = rightVersion.core[index] || 0;
80
80
 
81
81
  if (leftValue > rightValue) {
82
82
  return 1;
@@ -87,12 +87,85 @@ function compareVersions(left, right) {
87
87
  }
88
88
  }
89
89
 
90
+ return comparePrerelease(leftVersion.prerelease, rightVersion.prerelease);
91
+ }
92
+
93
+ function parseSemver(version) {
94
+ const normalized = String(version).trim();
95
+ const [mainPart = '', prereleasePart = ''] = normalized.split('-', 2);
96
+
97
+ return {
98
+ core: mainPart
99
+ .split('.')
100
+ .map((part) => Number.parseInt(part, 10))
101
+ .filter((part) => Number.isInteger(part)),
102
+ prerelease: prereleasePart
103
+ ? prereleasePart
104
+ .split('.')
105
+ .filter(Boolean)
106
+ .map((part) => (/^\d+$/.test(part) ? Number.parseInt(part, 10) : part.toLowerCase()))
107
+ : [],
108
+ };
109
+ }
110
+
111
+ function comparePrerelease(left, right) {
112
+ const leftHasPrerelease = left.length > 0;
113
+ const rightHasPrerelease = right.length > 0;
114
+
115
+ if (!leftHasPrerelease && !rightHasPrerelease) {
116
+ return 0;
117
+ }
118
+
119
+ if (!leftHasPrerelease) {
120
+ return 1;
121
+ }
122
+
123
+ if (!rightHasPrerelease) {
124
+ return -1;
125
+ }
126
+
127
+ const maxLength = Math.max(left.length, right.length);
128
+
129
+ for (let index = 0; index < maxLength; index += 1) {
130
+ const leftPart = left[index];
131
+ const rightPart = right[index];
132
+
133
+ if (leftPart === undefined) {
134
+ return -1;
135
+ }
136
+
137
+ if (rightPart === undefined) {
138
+ return 1;
139
+ }
140
+
141
+ const comparison = comparePrereleasePart(leftPart, rightPart);
142
+ if (comparison !== 0) {
143
+ return comparison;
144
+ }
145
+ }
146
+
90
147
  return 0;
91
148
  }
92
149
 
93
- function normalizeVersion(version) {
94
- return String(version)
95
- .split('.')
96
- .map((part) => Number.parseInt(part.replace(/[^0-9].*$/, ''), 10))
97
- .filter((part) => Number.isInteger(part));
150
+ function comparePrereleasePart(left, right) {
151
+ const leftIsNumber = typeof left === 'number';
152
+ const rightIsNumber = typeof right === 'number';
153
+
154
+ if (leftIsNumber && rightIsNumber) {
155
+ return left === right ? 0 : left > right ? 1 : -1;
156
+ }
157
+
158
+ if (leftIsNumber) {
159
+ return -1;
160
+ }
161
+
162
+ if (rightIsNumber) {
163
+ return 1;
164
+ }
165
+
166
+ if (left === right) {
167
+ return 0;
168
+ }
169
+
170
+ return left > right ? 1 : -1;
98
171
  }
package/src/core/args.js CHANGED
@@ -47,7 +47,7 @@ export function parseArgs(argv) {
47
47
  continue;
48
48
  }
49
49
 
50
- if (token === '-b' || token === '--buscar') {
50
+ if (token === '-b' || token === '--buscar' || token === '--search' || token === 'search') {
51
51
  parsed.command = 'search';
52
52
  const next = argv[index + 1];
53
53
  if (next && !next.startsWith('-')) {
@@ -57,7 +57,7 @@ export function parseArgs(argv) {
57
57
  continue;
58
58
  }
59
59
 
60
- if (token === '-e' || token === '--explicar' || token === 'explicar') {
60
+ if (token === '-e' || token === '--explicar' || token === '--explain' || token === 'explicar' || token === 'explain') {
61
61
  parsed.command = 'explain';
62
62
  const next = argv[index + 1];
63
63
  if (next && !next.startsWith('-')) {
@@ -87,7 +87,7 @@ export function parseArgs(argv) {
87
87
  continue;
88
88
  }
89
89
 
90
- if (token === '-r' || token === '--seguro' || token === '--safe-shell' || token === 'seguro') {
90
+ if (token === '-r' || token === '--seguro' || token === '--safe-shell' || token === 'seguro' || token === 'safe-shell') {
91
91
  parsed.command = 'safe-shell';
92
92
  continue;
93
93
  }
@@ -4,8 +4,8 @@ import { getHomeDirectory, normalizePlatformMode } from '../utils/platform.js';
4
4
 
5
5
  const DEFAULT_CONFIG = {
6
6
  features: {
7
- androidShortcut: false,
8
- projectBadge: false,
7
+ androidShortcut: true,
8
+ projectBadge: true,
9
9
  smartProjectInstall: false,
10
10
  },
11
11
  ui: {
@@ -82,13 +82,14 @@ export async function setPlatformMode(platformMode) {
82
82
  };
83
83
 
84
84
  if (normalized === 'linux') {
85
- nextFeatures.androidShortcut = false;
85
+ nextFeatures.androidShortcut = true;
86
86
  nextFeatures.projectBadge = true;
87
87
  nextFeatures.smartProjectInstall = true;
88
88
  }
89
89
 
90
90
  if (normalized === 'termux') {
91
91
  nextFeatures.androidShortcut = true;
92
+ nextFeatures.projectBadge = true;
92
93
  }
93
94
 
94
95
  return saveUserConfig({
package/src/ui/output.js CHANGED
@@ -25,7 +25,7 @@ export function printHelp() {
25
25
  'dex -t src arbol guiado de una ruta',
26
26
  'dex -i instalar dependencias del proyecto detectado',
27
27
  'dex -r abrir modo seguro del proyecto (Py/Node/PHP/Ruby/Go/Rust/Java)',
28
- 'dex -a acceso rapido a Android',
28
+ 'dex -a acceso rapido a Android o Linux',
29
29
  ]);
30
30
 
31
31
  printBlock('Uso rapido', [
@@ -33,7 +33,8 @@ export function printHelp() {
33
33
  'dex -t . --depth 3 --scope actual',
34
34
  'dex -c',
35
35
  'dex --prompt-context',
36
- 'dex-project-context --root',
36
+ 'dex --prompt-project-root',
37
+ 'dex --prompt-project-path',
37
38
  ]);
38
39
 
39
40
  printBlock('Scopes', [
package/src/ui/prompt.js CHANGED
@@ -75,9 +75,9 @@ export async function chooseSettingsAction(config) {
75
75
  },
76
76
  {
77
77
  key: 'toggle-android-shortcut',
78
- label: 'Acceso rapido a Android',
78
+ label: 'Acceso rapido',
79
79
  status: formatStatus(config.features.androidShortcut),
80
- usage: 'permite dex -a en Termux',
80
+ usage: 'permite dex -a en Termux o Linux',
81
81
  },
82
82
  {
83
83
  key: 'toggle-project-badge',
@@ -56,6 +56,57 @@ export function getAndroidStorageRoot(platformMode) {
56
56
  return canUseAndroidFeatures(platformMode) ? ANDROID_STORAGE : '';
57
57
  }
58
58
 
59
+ export function getQuickAccessRoot(platformMode) {
60
+ return platformMode === 'termux' ? ANDROID_STORAGE : getHomeDirectory();
61
+ }
62
+
63
+ export function getQuickAccessTitle(platformMode) {
64
+ return platformMode === 'termux' ? 'Dex Android' : 'Dex Linux';
65
+ }
66
+
67
+ export function getQuickAccessModeDescription(platformMode) {
68
+ return platformMode === 'termux'
69
+ ? 'interfaz Android redisenada'
70
+ : 'interfaz Linux redisenada';
71
+ }
72
+
73
+ export function getQuickAccessLabel(platformMode) {
74
+ return platformMode === 'termux' ? 'ANDROID STORAGE' : 'LINUX HOME';
75
+ }
76
+
77
+ export function getQuickAccessShortcutSummary(platformMode) {
78
+ return platformMode === 'termux'
79
+ ? 'dl, docs, dcim, pics, music, movies, shared'
80
+ : 'home, dl, docs, desk, pics, music, vids, tmp, shared';
81
+ }
82
+
83
+ export function getQuickAccessAliases(platformMode) {
84
+ if (platformMode === 'termux') {
85
+ return {
86
+ shared: '/sdcard',
87
+ dl: '/sdcard/Download',
88
+ docs: '/sdcard/Documents',
89
+ dcim: '/sdcard/DCIM',
90
+ pics: '/sdcard/Pictures',
91
+ music: '/sdcard/Music',
92
+ movies: '/sdcard/Movies',
93
+ };
94
+ }
95
+
96
+ const home = getHomeDirectory();
97
+ return {
98
+ home,
99
+ shared: home,
100
+ dl: path.join(home, 'Downloads'),
101
+ docs: path.join(home, 'Documents'),
102
+ desk: path.join(home, 'Desktop'),
103
+ pics: path.join(home, 'Pictures'),
104
+ music: path.join(home, 'Music'),
105
+ vids: path.join(home, 'Videos'),
106
+ tmp: '/tmp',
107
+ };
108
+ }
109
+
59
110
  export function shouldRestrictProjectToAndroidStorage(platformMode) {
60
111
  return platformMode === 'termux';
61
112
  }
@@ -63,7 +63,7 @@ export function formatPromptContext(context) {
63
63
  }
64
64
 
65
65
  export function formatPromptProjectRoot(context) {
66
- return path.basename(context.projectRoot || context.cwd) || context.cwd;
66
+ return context.projectRoot || context.cwd;
67
67
  }
68
68
 
69
69
  export function formatPromptProjectPath(context) {
@@ -136,9 +136,45 @@ async function detectProjectContextAt(targetDir, cwd) {
136
136
  return createContext('ruby', cwd, targetDir, await detectRubyVersion(targetDir), 'implicit');
137
137
  }
138
138
 
139
+ const nestedImplicitType = await detectImplicitLanguageFromChildren(targetDir, entries);
140
+ if (nestedImplicitType) {
141
+ return createContext(nestedImplicitType, cwd, targetDir, await detectVersionForType(nestedImplicitType, targetDir), 'implicit');
142
+ }
143
+
139
144
  return null;
140
145
  }
141
146
 
147
+ async function detectImplicitLanguageFromChildren(targetDir, entries) {
148
+ const childDirectories = entries
149
+ .filter((entry) => entry.isDirectory())
150
+ .slice(0, 8);
151
+
152
+ for (const entry of childDirectories) {
153
+ const nestedEntries = await safeReadDir(path.join(targetDir, entry.name));
154
+ if (!nestedEntries.length) {
155
+ continue;
156
+ }
157
+
158
+ if (nestedEntries.some((item) => !item.isDirectory() && item.name.endsWith('.py'))) {
159
+ return 'python';
160
+ }
161
+
162
+ if (nestedEntries.some((item) => !item.isDirectory() && isNodeScript(item.name))) {
163
+ return 'node';
164
+ }
165
+
166
+ if (nestedEntries.some((item) => !item.isDirectory() && item.name.endsWith('.php'))) {
167
+ return 'php';
168
+ }
169
+
170
+ if (nestedEntries.some((item) => !item.isDirectory() && item.name.endsWith('.rb'))) {
171
+ return 'ruby';
172
+ }
173
+ }
174
+
175
+ return '';
176
+ }
177
+
142
178
  function pickBestDetection(detections) {
143
179
  let bestMatch = detections[0];
144
180
 
@@ -279,6 +315,38 @@ async function detectJavaVersion(cwd) {
279
315
  );
280
316
  }
281
317
 
318
+ async function detectVersionForType(type, cwd) {
319
+ if (type === 'python') {
320
+ return detectPythonVersion(cwd);
321
+ }
322
+
323
+ if (type === 'node') {
324
+ return detectNodeVersion(cwd);
325
+ }
326
+
327
+ if (type === 'php') {
328
+ return detectPhpVersion(cwd);
329
+ }
330
+
331
+ if (type === 'ruby') {
332
+ return detectRubyVersion(cwd);
333
+ }
334
+
335
+ if (type === 'go') {
336
+ return detectGoVersion(cwd);
337
+ }
338
+
339
+ if (type === 'rust') {
340
+ return detectRustVersion(cwd);
341
+ }
342
+
343
+ if (type === 'java') {
344
+ return detectJavaVersion(cwd);
345
+ }
346
+
347
+ return null;
348
+ }
349
+
282
350
  async function safeReadDir(directoryPath) {
283
351
  try {
284
352
  return await fs.readdir(directoryPath, { withFileTypes: true });