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

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dex-termux-cli",
3
- "version": "0.3.0-beta.2",
3
+ "version": "0.3.0-beta.3",
4
4
  "type": "module",
5
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": [
package/src/app/main.js CHANGED
@@ -11,10 +11,12 @@ import { runTreeCommand } from '../commands/tree.js';
11
11
  import { runVersionCommand } from '../commands/version.js';
12
12
  import { printHelp, printProjectContextLine } from '../ui/output.js';
13
13
  import { detectProjectContext, formatProjectContext } from '../utils/project-context.js';
14
+ import { resolvePlatformMode } from '../utils/platform.js';
14
15
 
15
16
  export async function main(argv = process.argv.slice(2)) {
16
17
  const parsed = parseArgs(argv);
17
18
  const config = await loadUserConfig();
19
+ const platformMode = resolvePlatformMode(config);
18
20
  const isPromptOnly = parsed.command === 'prompt-context' || parsed.command === 'prompt-project-root' || parsed.command === 'prompt-project-path';
19
21
  const isContextOnly = parsed.command === 'context';
20
22
  const isVersionOnly = parsed.command === 'version';
@@ -27,7 +29,7 @@ export async function main(argv = process.argv.slice(2)) {
27
29
  }
28
30
 
29
31
  if (parsed.help || !argv.length) {
30
- printHelp();
32
+ printHelp(platformMode);
31
33
  return;
32
34
  }
33
35
 
@@ -91,5 +93,5 @@ export async function main(argv = process.argv.slice(2)) {
91
93
  return;
92
94
  }
93
95
 
94
- printHelp();
96
+ printHelp(platformMode);
95
97
  }
@@ -27,7 +27,8 @@ export async function runMenuCommand() {
27
27
  const action = await chooseMenuAction();
28
28
 
29
29
  if (action === 'help') {
30
- printHelp();
30
+ const config = await loadUserConfig();
31
+ printHelp(resolvePlatformMode(config));
31
32
  return;
32
33
  }
33
34
 
@@ -30,6 +30,16 @@ export function getScopeOptions(platformMode) {
30
30
  root: getAndroidStorageRoot(platformMode),
31
31
  description: 'Busca dentro del almacenamiento compartido accesible.',
32
32
  });
33
+ } else {
34
+ const sharedRoot = getAndroidStorageRoot(platformMode);
35
+ if (sharedRoot) {
36
+ options.push({
37
+ key: 'shared',
38
+ label: 'Almacenamiento compartido',
39
+ root: sharedRoot,
40
+ description: 'Busca dentro del almacenamiento compartido montado en Linux.',
41
+ });
42
+ }
33
43
  }
34
44
 
35
45
  return options;
package/src/ui/output.js CHANGED
@@ -1,5 +1,7 @@
1
1
  import path from 'node:path';
2
2
  import { getUserConfigPath } from '../core/config.js';
3
+ import { getScopeOptions } from '../core/scopes.js';
4
+ import { isHostTermux } from '../utils/platform.js';
3
5
 
4
6
  const ANSI = {
5
7
  bold: '\x1b[1m',
@@ -9,7 +11,7 @@ const ANSI = {
9
11
  reset: '\x1b[0m',
10
12
  };
11
13
 
12
- export function printHelp() {
14
+ export function printHelp(platformMode = '') {
13
15
  printBanner();
14
16
  console.log('');
15
17
  console.log(' Explorar, entender y preparar proyectos desde Termux o Linux.');
@@ -37,11 +39,8 @@ export function printHelp() {
37
39
  'dex --prompt-project-path',
38
40
  ]);
39
41
 
40
- printBlock('Scopes', [
41
- 'actual carpeta donde estas parado',
42
- 'home tu HOME del entorno actual',
43
- 'android almacenamiento accesible en modo Termux',
44
- ]);
42
+ const scopeOptions = getScopeOptions(platformMode || (isHostTermux() ? 'termux' : 'linux'));
43
+ printBlock('Scopes', scopeOptions.map((option) => `${option.key.padEnd(19, ' ')}${option.description}`));
45
44
 
46
45
  printBlock('Atajos', [
47
46
  '-h --help ayuda',
@@ -1,12 +1,31 @@
1
+ import fs from 'node:fs';
1
2
  import path from 'node:path';
2
3
 
3
4
  const TERMUX_HOME = '/data/data/com.termux/files/home';
4
5
  const ANDROID_STORAGE = '/sdcard';
6
+ const SHARED_STORAGE_CANDIDATES = [
7
+ '/storage/emulated/0',
8
+ '/sdcard',
9
+ ];
5
10
 
6
11
  export function getHomeDirectory() {
7
12
  return process.env.HOME || TERMUX_HOME;
8
13
  }
9
14
 
15
+ export function getSharedStorageRoot() {
16
+ for (const candidate of SHARED_STORAGE_CANDIDATES) {
17
+ try {
18
+ if (fs.existsSync(candidate)) {
19
+ return candidate;
20
+ }
21
+ } catch {
22
+ // ignore
23
+ }
24
+ }
25
+
26
+ return '';
27
+ }
28
+
10
29
  export function isHostTermux() {
11
30
  const home = getHomeDirectory();
12
31
  return Boolean(process.env.TERMUX_VERSION) || home === TERMUX_HOME || home.startsWith(TERMUX_HOME + path.sep);
@@ -53,11 +72,21 @@ export function canUseAndroidFeatures(platformMode) {
53
72
  }
54
73
 
55
74
  export function getAndroidStorageRoot(platformMode) {
56
- return canUseAndroidFeatures(platformMode) ? ANDROID_STORAGE : '';
75
+ if (canUseAndroidFeatures(platformMode)) {
76
+ return ANDROID_STORAGE;
77
+ }
78
+
79
+ return getSharedStorageRoot();
57
80
  }
58
81
 
59
82
  export function getQuickAccessRoot(platformMode) {
60
- return platformMode === 'termux' ? ANDROID_STORAGE : getHomeDirectory();
83
+ const sharedStorage = getSharedStorageRoot();
84
+
85
+ if (platformMode === 'termux') {
86
+ return sharedStorage || ANDROID_STORAGE;
87
+ }
88
+
89
+ return sharedStorage || getHomeDirectory();
61
90
  }
62
91
 
63
92
  export function getQuickAccessTitle(platformMode) {
@@ -71,13 +100,17 @@ export function getQuickAccessModeDescription(platformMode) {
71
100
  }
72
101
 
73
102
  export function getQuickAccessLabel(platformMode) {
74
- return platformMode === 'termux' ? 'ANDROID STORAGE' : 'LINUX HOME';
103
+ if (platformMode === 'termux') {
104
+ return 'ANDROID STORAGE';
105
+ }
106
+
107
+ return getSharedStorageRoot() ? 'SHARED STORAGE' : 'LINUX HOME';
75
108
  }
76
109
 
77
110
  export function getQuickAccessShortcutSummary(platformMode) {
78
111
  return platformMode === 'termux'
79
112
  ? 'dl, docs, dcim, pics, music, movies, shared'
80
- : 'home, dl, docs, desk, pics, music, vids, tmp, shared';
113
+ : 'shared, home, dl, docs, desk, pics, music, vids, tmp';
81
114
  }
82
115
 
83
116
  export function getQuickAccessAliases(platformMode) {
@@ -94,15 +127,16 @@ export function getQuickAccessAliases(platformMode) {
94
127
  }
95
128
 
96
129
  const home = getHomeDirectory();
130
+ const shared = getSharedStorageRoot() || home;
97
131
  return {
98
132
  home,
99
- shared: home,
100
- dl: path.join(home, 'Downloads'),
101
- docs: path.join(home, 'Documents'),
133
+ shared,
134
+ dl: path.join(shared, 'Download'),
135
+ docs: path.join(shared, 'Documents'),
102
136
  desk: path.join(home, 'Desktop'),
103
- pics: path.join(home, 'Pictures'),
104
- music: path.join(home, 'Music'),
105
- vids: path.join(home, 'Videos'),
137
+ pics: path.join(shared, 'Pictures'),
138
+ music: path.join(shared, 'Music'),
139
+ vids: path.join(shared, 'Movies'),
106
140
  tmp: '/tmp',
107
141
  };
108
142
  }
@@ -24,6 +24,8 @@ const LANGUAGE_META = {
24
24
  };
25
25
 
26
26
  const MAX_PARENT_LOOKUP = 16;
27
+ const MAX_IMPLICIT_SCAN_DEPTH = 3;
28
+ const MAX_IMPLICIT_SCAN_ENTRIES = 160;
27
29
 
28
30
  export async function detectProjectContext(cwd = process.cwd()) {
29
31
  const searchChain = buildSearchChain(cwd);
@@ -136,7 +138,7 @@ async function detectProjectContextAt(targetDir, cwd) {
136
138
  return createContext('ruby', cwd, targetDir, await detectRubyVersion(targetDir), 'implicit');
137
139
  }
138
140
 
139
- const nestedImplicitType = await detectImplicitLanguageFromChildren(targetDir, entries);
141
+ const nestedImplicitType = await detectImplicitLanguageFromTree(targetDir);
140
142
  if (nestedImplicitType) {
141
143
  return createContext(nestedImplicitType, cwd, targetDir, await detectVersionForType(nestedImplicitType, targetDir), 'implicit');
142
144
  }
@@ -144,51 +146,60 @@ async function detectProjectContextAt(targetDir, cwd) {
144
146
  return null;
145
147
  }
146
148
 
147
- async function detectImplicitLanguageFromChildren(targetDir, entries) {
148
- const childDirectories = entries
149
- .filter((entry) => entry.isDirectory())
150
- .slice(0, 8);
149
+ async function detectImplicitLanguageFromTree(rootDir) {
150
+ const queue = [{ dir: rootDir, depth: 0 }];
151
+ let visitedEntries = 0;
151
152
 
152
- for (const entry of childDirectories) {
153
- const nestedEntries = await safeReadDir(path.join(targetDir, entry.name));
153
+ while (queue.length && visitedEntries < MAX_IMPLICIT_SCAN_ENTRIES) {
154
+ const current = queue.shift();
155
+ const nestedEntries = await safeReadDir(current.dir);
154
156
  if (!nestedEntries.length) {
155
157
  continue;
156
158
  }
157
159
 
158
- if (nestedEntries.some((item) => !item.isDirectory() && item.name.endsWith('.py'))) {
159
- return 'python';
160
- }
160
+ for (const item of nestedEntries) {
161
+ visitedEntries += 1;
161
162
 
162
- if (nestedEntries.some((item) => !item.isDirectory() && isNodeScript(item.name))) {
163
- return 'node';
164
- }
163
+ if (!item.isDirectory()) {
164
+ if (item.name.endsWith('.py')) {
165
+ return 'python';
166
+ }
165
167
 
166
- if (nestedEntries.some((item) => !item.isDirectory() && item.name.endsWith('.php'))) {
167
- return 'php';
168
- }
168
+ if (isNodeScript(item.name)) {
169
+ return 'node';
170
+ }
169
171
 
170
- if (nestedEntries.some((item) => !item.isDirectory() && item.name.endsWith('.rb'))) {
171
- return 'ruby';
172
- }
173
- }
172
+ if (item.name.endsWith('.php')) {
173
+ return 'php';
174
+ }
174
175
 
175
- return '';
176
- }
176
+ if (item.name.endsWith('.rb')) {
177
+ return 'ruby';
178
+ }
177
179
 
178
- function pickBestDetection(detections) {
179
- let bestMatch = detections[0];
180
+ continue;
181
+ }
180
182
 
181
- for (let index = 1; index < detections.length; index += 1) {
182
- const current = detections[index];
183
+ if (current.depth + 1 > MAX_IMPLICIT_SCAN_DEPTH) {
184
+ continue;
185
+ }
183
186
 
184
- if (current.type !== bestMatch.type) {
185
- break;
186
- }
187
+ if (shouldSkipImplicitDirectory(item.name)) {
188
+ continue;
189
+ }
187
190
 
188
- bestMatch = current;
191
+ queue.push({
192
+ dir: path.join(current.dir, item.name),
193
+ depth: current.depth + 1,
194
+ });
195
+ }
189
196
  }
190
197
 
191
- return bestMatch;
198
+ return '';
199
+ }
200
+
201
+ function pickBestDetection(detections) {
202
+ return detections[0];
192
203
  }
193
204
 
194
205
  function createContext(type, cwd, projectRoot, version, detectionKind) {
@@ -402,3 +413,17 @@ function firstNonEmptyLine(content) {
402
413
  function isNodeScript(name) {
403
414
  return name.endsWith('.js') || name.endsWith('.mjs') || name.endsWith('.cjs') || name.endsWith('.ts');
404
415
  }
416
+
417
+ function shouldSkipImplicitDirectory(name) {
418
+ return [
419
+ '.git',
420
+ '.venv',
421
+ 'node_modules',
422
+ '__pycache__',
423
+ '.mypy_cache',
424
+ '.pytest_cache',
425
+ '.ruff_cache',
426
+ 'dist',
427
+ 'build',
428
+ ].includes(name);
429
+ }