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 +1 -1
- package/src/app/main.js +4 -2
- package/src/commands/menu.js +2 -1
- package/src/core/scopes.js +10 -0
- package/src/ui/output.js +5 -6
- package/src/utils/platform.js +44 -10
- package/src/utils/project-context.js +56 -31
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dex-termux-cli",
|
|
3
|
-
"version": "0.3.0-beta.
|
|
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
|
}
|
package/src/commands/menu.js
CHANGED
package/src/core/scopes.js
CHANGED
|
@@ -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
|
-
|
|
41
|
-
|
|
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',
|
package/src/utils/platform.js
CHANGED
|
@@ -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
|
-
|
|
75
|
+
if (canUseAndroidFeatures(platformMode)) {
|
|
76
|
+
return ANDROID_STORAGE;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
return getSharedStorageRoot();
|
|
57
80
|
}
|
|
58
81
|
|
|
59
82
|
export function getQuickAccessRoot(platformMode) {
|
|
60
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
100
|
-
dl: path.join(
|
|
101
|
-
docs: path.join(
|
|
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(
|
|
104
|
-
music: path.join(
|
|
105
|
-
vids: path.join(
|
|
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
|
|
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
|
|
148
|
-
const
|
|
149
|
-
|
|
150
|
-
.slice(0, 8);
|
|
149
|
+
async function detectImplicitLanguageFromTree(rootDir) {
|
|
150
|
+
const queue = [{ dir: rootDir, depth: 0 }];
|
|
151
|
+
let visitedEntries = 0;
|
|
151
152
|
|
|
152
|
-
|
|
153
|
-
const
|
|
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
|
-
|
|
159
|
-
|
|
160
|
-
}
|
|
160
|
+
for (const item of nestedEntries) {
|
|
161
|
+
visitedEntries += 1;
|
|
161
162
|
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
163
|
+
if (!item.isDirectory()) {
|
|
164
|
+
if (item.name.endsWith('.py')) {
|
|
165
|
+
return 'python';
|
|
166
|
+
}
|
|
165
167
|
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
168
|
+
if (isNodeScript(item.name)) {
|
|
169
|
+
return 'node';
|
|
170
|
+
}
|
|
169
171
|
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
}
|
|
172
|
+
if (item.name.endsWith('.php')) {
|
|
173
|
+
return 'php';
|
|
174
|
+
}
|
|
174
175
|
|
|
175
|
-
|
|
176
|
-
|
|
176
|
+
if (item.name.endsWith('.rb')) {
|
|
177
|
+
return 'ruby';
|
|
178
|
+
}
|
|
177
179
|
|
|
178
|
-
|
|
179
|
-
|
|
180
|
+
continue;
|
|
181
|
+
}
|
|
180
182
|
|
|
181
|
-
|
|
182
|
-
|
|
183
|
+
if (current.depth + 1 > MAX_IMPLICIT_SCAN_DEPTH) {
|
|
184
|
+
continue;
|
|
185
|
+
}
|
|
183
186
|
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
+
if (shouldSkipImplicitDirectory(item.name)) {
|
|
188
|
+
continue;
|
|
189
|
+
}
|
|
187
190
|
|
|
188
|
-
|
|
191
|
+
queue.push({
|
|
192
|
+
dir: path.join(current.dir, item.name),
|
|
193
|
+
depth: current.depth + 1,
|
|
194
|
+
});
|
|
195
|
+
}
|
|
189
196
|
}
|
|
190
197
|
|
|
191
|
-
return
|
|
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
|
+
}
|