dex-termux-cli 0.2.3 → 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.
@@ -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
 
@@ -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
@@ -32,12 +32,22 @@ 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;
38
48
  }
39
49
 
40
- if (token === '-b' || token === '--buscar') {
50
+ if (token === '-b' || token === '--buscar' || token === '--search' || token === 'search') {
41
51
  parsed.command = 'search';
42
52
  const next = argv[index + 1];
43
53
  if (next && !next.startsWith('-')) {
@@ -47,7 +57,7 @@ export function parseArgs(argv) {
47
57
  continue;
48
58
  }
49
59
 
50
- if (token === '-e' || token === '--explicar' || token === 'explicar') {
60
+ if (token === '-e' || token === '--explicar' || token === '--explain' || token === 'explicar' || token === 'explain') {
51
61
  parsed.command = 'explain';
52
62
  const next = argv[index + 1];
53
63
  if (next && !next.startsWith('-')) {
@@ -77,7 +87,7 @@ export function parseArgs(argv) {
77
87
  continue;
78
88
  }
79
89
 
80
- if (token === '-r' || token === '--seguro' || token === '--safe-shell' || token === 'seguro') {
90
+ if (token === '-r' || token === '--seguro' || token === '--safe-shell' || token === 'seguro' || token === 'safe-shell') {
81
91
  parsed.command = 'safe-shell';
82
92
  continue;
83
93
  }
@@ -1,21 +1,24 @@
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: {
6
- androidShortcut: false,
7
- projectBadge: false,
7
+ androidShortcut: true,
8
+ projectBadge: true,
8
9
  smartProjectInstall: false,
9
10
  },
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,34 @@ 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 = true;
86
+ nextFeatures.projectBadge = true;
87
+ nextFeatures.smartProjectInstall = true;
88
+ }
89
+
90
+ if (normalized === 'termux') {
91
+ nextFeatures.androidShortcut = true;
92
+ nextFeatures.projectBadge = true;
93
+ }
94
+
95
+ return saveUserConfig({
96
+ ...config,
97
+ features: nextFeatures,
98
+ runtime: {
99
+ ...config.runtime,
100
+ platformMode: normalized,
101
+ },
102
+ });
103
+ }
104
+
74
105
  export async function saveProjectState(projectKey, projectState) {
75
106
  const config = await loadUserConfig();
76
107
  const nextConfig = {
@@ -124,6 +155,11 @@ function mergeConfig(config) {
124
155
  ...(config.ui || {}),
125
156
  promptContextPosition: normalizePromptContextPosition(config.ui?.promptContextPosition),
126
157
  },
158
+ runtime: {
159
+ ...DEFAULT_CONFIG.runtime,
160
+ ...(config.runtime || {}),
161
+ platformMode: normalizePlatformMode(config.runtime?.platformMode),
162
+ },
127
163
  projects: {
128
164
  ...DEFAULT_CONFIG.projects,
129
165
  ...(config.projects || {}),
@@ -139,6 +175,9 @@ function cloneDefaultConfig() {
139
175
  ui: {
140
176
  ...DEFAULT_CONFIG.ui,
141
177
  },
178
+ runtime: {
179
+ ...DEFAULT_CONFIG.runtime,
180
+ },
142
181
  projects: {
143
182
  ...DEFAULT_CONFIG.projects,
144
183
  },
@@ -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,24 @@ 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',
27
- 'dex -a acceso rapido a Android',
26
+ 'dex -i instalar dependencias del proyecto detectado',
27
+ 'dex -r abrir modo seguro del proyecto (Py/Node/PHP/Ruby/Go/Rust/Java)',
28
+ 'dex -a acceso rapido a Android o Linux',
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 --prompt-project-root',
37
+ 'dex --prompt-project-path',
35
38
  ]);
36
39
 
37
40
  printBlock('Scopes', [
38
41
  'actual carpeta donde estas parado',
39
- 'home tu HOME de Termux',
40
- 'android almacenamiento accesible',
42
+ 'home tu HOME del entorno actual',
43
+ 'android almacenamiento accesible en modo Termux',
41
44
  ]);
42
45
 
43
46
  printBlock('Atajos', [