dex-termux-cli 0.1.0

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.
@@ -0,0 +1,120 @@
1
+ import { loadUserConfig, setFeatureEnabled } from '../core/config.js';
2
+ import { runExplainCommand } from './explain.js';
3
+ import { runSafeShellCommand } from './safe-shell.js';
4
+ import { runSearchCommand } from './search.js';
5
+ import { runTreeCommand } from './tree.js';
6
+ import {
7
+ chooseFeatureToggle,
8
+ chooseMenuAction,
9
+ chooseSearchPatternFromMenu,
10
+ chooseSettingsAction,
11
+ chooseTreeTargetFromMenu,
12
+ } from '../ui/prompt.js';
13
+ import { printHelp } from '../ui/output.js';
14
+
15
+ export async function runMenuCommand() {
16
+ while (true) {
17
+ const action = await chooseMenuAction();
18
+
19
+ if (action === 'help') {
20
+ printHelp();
21
+ return;
22
+ }
23
+
24
+ if (action === 'search') {
25
+ const pattern = await chooseSearchPatternFromMenu();
26
+ await runSearchCommand({ pattern, scope: '' });
27
+ return;
28
+ }
29
+
30
+ if (action === 'explain') {
31
+ await runExplainCommand({ topic: '' });
32
+ return;
33
+ }
34
+
35
+ if (action === 'tree') {
36
+ const target = await chooseTreeTargetFromMenu();
37
+ await runTreeCommand({ target, scope: '', depth: 2 });
38
+ return;
39
+ }
40
+
41
+ if (action === 'safe-shell') {
42
+ await runSafeShellCommand();
43
+ return;
44
+ }
45
+
46
+ if (action === 'settings') {
47
+ await runSettingsMenu();
48
+ continue;
49
+ }
50
+
51
+ if (action === 'exit') {
52
+ console.log('Saliendo de Dex.');
53
+ return;
54
+ }
55
+ }
56
+ }
57
+
58
+ async function runSettingsMenu() {
59
+ while (true) {
60
+ const config = await loadUserConfig();
61
+ const action = await chooseSettingsAction(config);
62
+
63
+ if (action === 'back') {
64
+ return;
65
+ }
66
+
67
+ if (action === 'toggle-android-shortcut') {
68
+ const toggle = await chooseFeatureToggle(
69
+ config.features.androidShortcut,
70
+ 'Acceso rapido a Android',
71
+ 'permite usar dex -a o dex --android',
72
+ );
73
+
74
+ if (toggle === 'back') {
75
+ continue;
76
+ }
77
+
78
+ const enabled = toggle === 'enable';
79
+ await setFeatureEnabled('androidShortcut', enabled);
80
+ console.log(`Acceso rapido a Android: ${enabled ? 'activado' : 'desactivado'}.`);
81
+ console.log('');
82
+ continue;
83
+ }
84
+
85
+ if (action === 'toggle-project-badge') {
86
+ const toggle = await chooseFeatureToggle(
87
+ config.features.projectBadge,
88
+ 'Detector de proyecto y color',
89
+ 'muestra el tipo de proyecto actual con color segun lenguaje',
90
+ );
91
+
92
+ if (toggle === 'back') {
93
+ continue;
94
+ }
95
+
96
+ const enabled = toggle === 'enable';
97
+ await setFeatureEnabled('projectBadge', enabled);
98
+ console.log(`Detector de proyecto y color: ${enabled ? 'activado' : 'desactivado'}.`);
99
+ console.log('');
100
+ continue;
101
+ }
102
+
103
+ if (action === 'toggle-smart-project-install') {
104
+ const toggle = await chooseFeatureToggle(
105
+ config.features.smartProjectInstall,
106
+ 'Instalacion segura de proyectos',
107
+ 'instala dependencias y si falta el runtime intenta traerlo con pkg',
108
+ );
109
+
110
+ if (toggle === 'back') {
111
+ continue;
112
+ }
113
+
114
+ const enabled = toggle === 'enable';
115
+ await setFeatureEnabled('smartProjectInstall', enabled);
116
+ console.log(`Instalacion segura de proyectos: ${enabled ? 'activado' : 'desactivado'}.`);
117
+ console.log('');
118
+ }
119
+ }
120
+ }
@@ -0,0 +1,200 @@
1
+ import fs from 'node:fs/promises';
2
+ import path from 'node:path';
3
+ import { createHash } from 'node:crypto';
4
+ import { spawn } from 'node:child_process';
5
+ import { loadUserConfig } from '../core/config.js';
6
+ import { detectProjectContext } from '../utils/project-context.js';
7
+
8
+ export async function runSafeShellCommand() {
9
+ const config = await loadUserConfig();
10
+
11
+ if (!config.features.smartProjectInstall) {
12
+ throw new Error('La instalacion segura de proyectos esta desactivada. Activa esa funcion antes de abrir el modo seguro.');
13
+ }
14
+
15
+ const projectRoot = process.cwd();
16
+ const projectContext = await detectProjectContext();
17
+
18
+ if (!projectContext || !supportsSafeMode(projectContext.type)) {
19
+ throw new Error('El modo seguro de Dex por ahora solo existe para proyectos Python, Node y PHP.');
20
+ }
21
+
22
+ if (!isAndroidStoragePath(projectRoot)) {
23
+ throw new Error('El modo seguro de Dex por ahora se usa en proyectos Python, Node o PHP dentro de Android storage.');
24
+ }
25
+
26
+ const projectKey = getProjectKey(projectRoot);
27
+ const projectState = config.projects && config.projects[projectKey] ? config.projects[projectKey] : null;
28
+ const shell = process.env.SHELL || '/data/data/com.termux/files/usr/bin/sh';
29
+ const reason = projectState && projectState.preferSafeInstall
30
+ ? 'Este proyecto ya quedo marcado para modo seguro.'
31
+ : 'Entrando al entorno seguro manual de Dex.';
32
+
33
+ if (projectContext.type === 'python') {
34
+ const venvDir = getRescueVenvPath(projectRoot);
35
+ const rescuePython = path.join(venvDir, 'bin', 'python');
36
+
37
+ try {
38
+ await fs.access(rescuePython);
39
+ } catch {
40
+ throw new Error('No encontre el entorno seguro de este proyecto. Corre dex -i primero para prepararlo.');
41
+ }
42
+
43
+ const venvBin = path.join(venvDir, 'bin');
44
+ const nextPath = venvBin + ':' + (process.env.PATH || '');
45
+
46
+ console.log('Modo Dex : shell segura para ' + path.basename(projectRoot));
47
+ console.log('Proyecto : ' + projectRoot);
48
+ console.log('Entorno : ' + venvDir);
49
+ console.log('Python : ' + rescuePython);
50
+ console.log('Nota : ' + reason);
51
+ console.log('Usa python, pip o python bot.py desde aqui.');
52
+ console.log('Si sales de esta shell, el modo seguro deja de estar activo.');
53
+ console.log('Escribe exit para volver a tu shell normal.');
54
+ console.log('');
55
+
56
+ await openSafeShell(shell, projectRoot, {
57
+ PATH: nextPath,
58
+ VIRTUAL_ENV: venvDir,
59
+ DEX_CONTEXT: 'safe-shell',
60
+ DEX_SAFE_PROJECT: projectRoot,
61
+ DEX_SAFE_PROJECT_KEY: projectKey,
62
+ });
63
+ return;
64
+ }
65
+
66
+ if (projectContext.type === 'node') {
67
+ const safeProjectDir = getSafeProjectWorkspacePath(projectRoot, projectContext.type);
68
+ const packageJsonPath = path.join(safeProjectDir, 'package.json');
69
+ const nodeModulesDir = path.join(safeProjectDir, 'node_modules');
70
+ const nodeBinDir = path.join(nodeModulesDir, '.bin');
71
+
72
+ try {
73
+ await fs.access(packageJsonPath);
74
+ await fs.access(nodeModulesDir);
75
+ } catch {
76
+ throw new Error('No encontre el espacio seguro de este proyecto Node. Corre dex -i primero para prepararlo.');
77
+ }
78
+
79
+ const nextPath = nodeBinDir + ':' + (process.env.PATH || '');
80
+
81
+ console.log('Modo Dex : shell segura para ' + path.basename(projectRoot));
82
+ console.log('Proyecto : ' + projectRoot);
83
+ console.log('Seguro : ' + safeProjectDir);
84
+ console.log('Modulos : ' + nodeModulesDir);
85
+ console.log('Nota : ' + reason);
86
+ console.log('Usa node, npm o npm run desde aqui.');
87
+ console.log('Esta shell abre la copia segura del proyecto dentro de HOME.');
88
+ console.log('Escribe exit para volver a tu shell normal.');
89
+ console.log('');
90
+
91
+ await openSafeShell(shell, safeProjectDir, {
92
+ PATH: nextPath,
93
+ DEX_CONTEXT: 'safe-shell',
94
+ DEX_SAFE_PROJECT: projectRoot,
95
+ DEX_SAFE_PROJECT_KEY: projectKey,
96
+ DEX_SAFE_WORKSPACE: safeProjectDir,
97
+ });
98
+ return;
99
+ }
100
+
101
+ if (projectContext.type === 'php') {
102
+ const safeProjectDir = getSafeProjectWorkspacePath(projectRoot, projectContext.type);
103
+ const composerJsonPath = path.join(safeProjectDir, 'composer.json');
104
+ const vendorDir = path.join(safeProjectDir, 'vendor');
105
+ const vendorBinDir = path.join(vendorDir, 'bin');
106
+ const composerHome = getComposerHome();
107
+
108
+ try {
109
+ await fs.access(composerJsonPath);
110
+ await fs.access(vendorDir);
111
+ } catch {
112
+ throw new Error('No encontre el espacio seguro de este proyecto PHP. Corre dex -i primero para prepararlo.');
113
+ }
114
+
115
+ const nextPath = vendorBinDir + ':' + (process.env.PATH || '');
116
+
117
+ console.log('Modo Dex : shell segura para ' + path.basename(projectRoot));
118
+ console.log('Proyecto : ' + projectRoot);
119
+ console.log('Seguro : ' + safeProjectDir);
120
+ console.log('Vendor : ' + vendorDir);
121
+ console.log('Nota : ' + reason);
122
+ console.log('Usa php, composer o binarios de vendor/bin desde aqui.');
123
+ console.log('Esta shell abre la copia segura del proyecto dentro de HOME.');
124
+ console.log('Escribe exit para volver a tu shell normal.');
125
+ console.log('');
126
+
127
+ await fs.mkdir(composerHome, { recursive: true });
128
+
129
+ await openSafeShell(shell, safeProjectDir, {
130
+ PATH: nextPath,
131
+ COMPOSER_HOME: composerHome,
132
+ DEX_CONTEXT: 'safe-shell',
133
+ DEX_SAFE_PROJECT: projectRoot,
134
+ DEX_SAFE_PROJECT_KEY: projectKey,
135
+ DEX_SAFE_WORKSPACE: safeProjectDir,
136
+ });
137
+ }
138
+ }
139
+
140
+ function getProjectKey(projectRoot) {
141
+ return createHash('sha1').update(projectRoot).digest('hex');
142
+ }
143
+
144
+ function getRescueVenvPath(projectRoot) {
145
+ const home = process.env.HOME || '/data/data/com.termux/files/home';
146
+ const digest = createHash('sha1').update(projectRoot).digest('hex').slice(0, 8);
147
+ const baseName = path.basename(projectRoot).toLowerCase().replace(/[^a-z0-9._-]+/g, '-');
148
+ const safeName = baseName || 'project';
149
+ return path.join(home, '.dex', 'venvs', safeName + '-' + digest);
150
+ }
151
+
152
+ function getSafeProjectWorkspacePath(projectRoot, projectType) {
153
+ const home = process.env.HOME || '/data/data/com.termux/files/home';
154
+ const digest = createHash('sha1').update(projectRoot).digest('hex').slice(0, 8);
155
+ const baseName = path.basename(projectRoot).toLowerCase().replace(/[^a-z0-9._-]+/g, '-');
156
+ const safeName = baseName || projectType || 'project';
157
+ const safeKind = projectType || 'project';
158
+ return path.join(home, '.dex', 'projects', safeKind, safeName + '-' + digest);
159
+ }
160
+
161
+ function getComposerHome() {
162
+ const home = process.env.HOME || '/data/data/com.termux/files/home';
163
+ return path.join(home, '.dex', 'composer');
164
+ }
165
+
166
+ function isAndroidStoragePath(projectRoot) {
167
+ return projectRoot.startsWith('/sdcard') || projectRoot.startsWith('/storage/emulated/0');
168
+ }
169
+
170
+ function supportsSafeMode(projectType) {
171
+ return projectType === 'python' || projectType === 'node' || projectType === 'php';
172
+ }
173
+
174
+ function openSafeShell(shell, cwd, envPatch) {
175
+ return new Promise((resolve, reject) => {
176
+ const child = spawn(shell, ['-i'], {
177
+ cwd,
178
+ stdio: 'inherit',
179
+ env: {
180
+ ...process.env,
181
+ ...envPatch,
182
+ },
183
+ });
184
+
185
+ child.on('error', reject);
186
+ child.on('exit', (code, signal) => {
187
+ if (signal) {
188
+ reject(new Error('La shell segura termino por senal: ' + signal));
189
+ return;
190
+ }
191
+
192
+ if (code && code !== 0) {
193
+ reject(new Error('La shell segura termino con codigo ' + code));
194
+ return;
195
+ }
196
+
197
+ resolve();
198
+ });
199
+ });
200
+ }
@@ -0,0 +1,41 @@
1
+ import fs from 'node:fs/promises';
2
+ import { chooseSearchScope } from '../ui/prompt.js';
3
+ import { formatSearchResults, printSection } from '../ui/output.js';
4
+ import { getScopeOptions, resolveScopeRoot } from '../core/scopes.js';
5
+ import { searchEntries } from '../utils/fs-search.js';
6
+
7
+ export async function runSearchCommand({ pattern, scope }) {
8
+ const cleanPattern = (pattern || '').trim();
9
+
10
+ if (!cleanPattern) {
11
+ throw new Error('Debes indicar un patron. Ejemplo: dex -b "archivo"');
12
+ }
13
+
14
+ const scopeOptions = getScopeOptions();
15
+ const selectedScope = scope || await chooseSearchScope(scopeOptions);
16
+ const root = resolveScopeRoot(selectedScope, scopeOptions);
17
+
18
+ if (!root) {
19
+ throw new Error(`Alcance no valido: ${selectedScope}`);
20
+ }
21
+
22
+ try {
23
+ await fs.access(root);
24
+ } catch {
25
+ throw new Error(`No se puede acceder a la ruta: ${root}`);
26
+ }
27
+
28
+ printSection('Busqueda');
29
+ console.log(`Patron : ${cleanPattern}`);
30
+ console.log(`Alcance: ${selectedScope}`);
31
+ console.log(`Ruta : ${root}`);
32
+ console.log('');
33
+
34
+ const results = await searchEntries({
35
+ root,
36
+ pattern: cleanPattern,
37
+ limit: 80,
38
+ });
39
+
40
+ console.log(formatSearchResults(results, root));
41
+ }
@@ -0,0 +1,61 @@
1
+ import fs from 'node:fs/promises';
2
+ import path from 'node:path';
3
+ import { chooseSearchScope } from '../ui/prompt.js';
4
+ import { formatTreeReport, printSection } from '../ui/output.js';
5
+ import { getScopeOptions, resolveScopeRoot } from '../core/scopes.js';
6
+ import { buildTreeReport } from '../utils/fs-tree.js';
7
+
8
+ export async function runTreeCommand({ target, scope, depth }) {
9
+ const scopeOptions = getScopeOptions();
10
+ const selectedScope = scope || await chooseSearchScope(scopeOptions);
11
+ const root = resolveScopeRoot(selectedScope, scopeOptions);
12
+
13
+ if (!root) {
14
+ throw new Error(`Alcance no valido: ${selectedScope}`);
15
+ }
16
+
17
+ const cleanTarget = (target || '.').trim() || '.';
18
+ const resolvedTarget = path.resolve(root, cleanTarget);
19
+ const relativeToRoot = path.relative(root, resolvedTarget);
20
+
21
+ if (relativeToRoot.startsWith('..') || path.isAbsolute(relativeToRoot)) {
22
+ throw new Error('La ruta pedida se sale del scope elegido.');
23
+ }
24
+
25
+ let stats;
26
+ try {
27
+ await fs.access(resolvedTarget);
28
+ stats = await fs.stat(resolvedTarget);
29
+ } catch {
30
+ throw new Error(`No se puede acceder a la ruta: ${resolvedTarget}`);
31
+ }
32
+
33
+ printSection('Tree');
34
+ console.log(`Scope : ${selectedScope}`);
35
+ console.log(`Base : ${root}`);
36
+ console.log(`Objetivo: ${cleanTarget}`);
37
+ console.log('');
38
+
39
+ if (!stats.isDirectory()) {
40
+ console.log(formatTreeReport({
41
+ label: cleanTarget,
42
+ root: resolvedTarget,
43
+ depth,
44
+ directories: 0,
45
+ files: 1,
46
+ truncatedByDepth: false,
47
+ truncatedByLimit: false,
48
+ lines: [path.basename(resolvedTarget)],
49
+ }));
50
+ return;
51
+ }
52
+
53
+ const report = await buildTreeReport({
54
+ root: resolvedTarget,
55
+ label: cleanTarget,
56
+ depth,
57
+ limit: 120,
58
+ });
59
+
60
+ console.log(formatTreeReport(report));
61
+ }
@@ -0,0 +1,114 @@
1
+ export function parseArgs(argv) {
2
+ const parsed = {
3
+ help: false,
4
+ command: '',
5
+ pattern: '',
6
+ topic: '',
7
+ target: '',
8
+ scope: '',
9
+ depth: 2,
10
+ };
11
+
12
+ for (let index = 0; index < argv.length; index += 1) {
13
+ const token = argv[index];
14
+
15
+ if (token === '-h' || token === '--help' || token === 'help') {
16
+ parsed.help = true;
17
+ continue;
18
+ }
19
+
20
+ if (token === '-m' || token === '--menu' || token === 'menu') {
21
+ parsed.command = 'menu';
22
+ continue;
23
+ }
24
+
25
+ if (token === '-b' || token === '--buscar') {
26
+ parsed.command = 'search';
27
+ const next = argv[index + 1];
28
+ if (next && !next.startsWith('-')) {
29
+ parsed.pattern = next;
30
+ index += 1;
31
+ }
32
+ continue;
33
+ }
34
+
35
+ if (token === '-e' || token === '--explicar' || token === 'explicar') {
36
+ parsed.command = 'explain';
37
+ const next = argv[index + 1];
38
+ if (next && !next.startsWith('-')) {
39
+ parsed.topic = next;
40
+ index += 1;
41
+ }
42
+ continue;
43
+ }
44
+
45
+ if (token === '-t' || token === '--tree' || token === 'tree') {
46
+ parsed.command = 'tree';
47
+ const next = argv[index + 1];
48
+ if (next && !next.startsWith('-')) {
49
+ parsed.target = next;
50
+ index += 1;
51
+ }
52
+ continue;
53
+ }
54
+
55
+ if (token === '-a' || token === '--android' || token === 'android') {
56
+ parsed.command = 'android';
57
+ continue;
58
+ }
59
+
60
+ if (token === '-i' || token === '--instalar' || token === '--install' || token === 'install') {
61
+ parsed.command = 'install';
62
+ continue;
63
+ }
64
+
65
+ if (token === '-r' || token === '--seguro' || token === '--safe-shell' || token === 'seguro') {
66
+ parsed.command = 'safe-shell';
67
+ continue;
68
+ }
69
+
70
+ if (token === '-s' || token === '--scope') {
71
+ const next = argv[index + 1];
72
+ if (next) {
73
+ parsed.scope = next.toLowerCase();
74
+ index += 1;
75
+ }
76
+ continue;
77
+ }
78
+
79
+ if (token === '-d' || token === '--depth') {
80
+ const next = argv[index + 1];
81
+ if (next) {
82
+ parsed.depth = normalizeDepth(next);
83
+ index += 1;
84
+ }
85
+ continue;
86
+ }
87
+
88
+ if (!parsed.pattern && parsed.command === 'search') {
89
+ parsed.pattern = token;
90
+ continue;
91
+ }
92
+
93
+ if (!parsed.topic && parsed.command === 'explain') {
94
+ parsed.topic = token;
95
+ continue;
96
+ }
97
+
98
+ if (!parsed.target && parsed.command === 'tree') {
99
+ parsed.target = token;
100
+ }
101
+ }
102
+
103
+ return parsed;
104
+ }
105
+
106
+ function normalizeDepth(value) {
107
+ const numeric = Number.parseInt(value, 10);
108
+
109
+ if (!Number.isInteger(numeric) || numeric < 1) {
110
+ return 2;
111
+ }
112
+
113
+ return numeric;
114
+ }
@@ -0,0 +1,122 @@
1
+ import fs from 'node:fs/promises';
2
+ import path from 'node:path';
3
+
4
+ const DEFAULT_CONFIG = {
5
+ features: {
6
+ androidShortcut: false,
7
+ projectBadge: false,
8
+ smartProjectInstall: false,
9
+ },
10
+ projects: {},
11
+ };
12
+
13
+ export function getUserConfigPath() {
14
+ const home = process.env.HOME || '/data/data/com.termux/files/home';
15
+ return path.join(home, '.config', 'dex', 'config.json');
16
+ }
17
+
18
+ export async function loadUserConfig() {
19
+ const configPath = getUserConfigPath();
20
+
21
+ try {
22
+ const raw = await fs.readFile(configPath, 'utf8');
23
+ const parsed = JSON.parse(raw);
24
+ return mergeConfig(parsed);
25
+ } catch (error) {
26
+ if (error && error.code !== 'ENOENT') {
27
+ throw new Error(`No pude leer la configuracion de Dex: ${error.message}`);
28
+ }
29
+
30
+ await ensureUserConfigExists(configPath);
31
+ return cloneDefaultConfig();
32
+ }
33
+ }
34
+
35
+ export async function saveUserConfig(config) {
36
+ const configPath = getUserConfigPath();
37
+ const merged = mergeConfig(config);
38
+
39
+ await fs.mkdir(path.dirname(configPath), { recursive: true });
40
+ await fs.writeFile(configPath, `${JSON.stringify(merged, null, 2)}\n`, 'utf8');
41
+
42
+ return merged;
43
+ }
44
+
45
+ export async function setFeatureEnabled(featureKey, enabled) {
46
+ const config = await loadUserConfig();
47
+ const nextConfig = {
48
+ ...config,
49
+ features: {
50
+ ...config.features,
51
+ [featureKey]: enabled,
52
+ },
53
+ };
54
+
55
+ return saveUserConfig(nextConfig);
56
+ }
57
+
58
+ export async function saveProjectState(projectKey, projectState) {
59
+ const config = await loadUserConfig();
60
+ const nextConfig = {
61
+ ...config,
62
+ projects: {
63
+ ...(config.projects || {}),
64
+ [projectKey]: {
65
+ ...((config.projects || {})[projectKey] || {}),
66
+ ...projectState,
67
+ },
68
+ },
69
+ };
70
+
71
+ return saveUserConfig(nextConfig);
72
+ }
73
+
74
+ export async function clearProjectState(projectKey) {
75
+ const config = await loadUserConfig();
76
+ const nextProjects = { ...(config.projects || {}) };
77
+ delete nextProjects[projectKey];
78
+
79
+ return saveUserConfig({
80
+ ...config,
81
+ projects: nextProjects,
82
+ });
83
+ }
84
+
85
+ async function ensureUserConfigExists(configPath) {
86
+ await fs.mkdir(path.dirname(configPath), { recursive: true });
87
+ await fs.writeFile(configPath, `${JSON.stringify(DEFAULT_CONFIG, null, 2)}\n`, { flag: 'wx' }).catch((error) => {
88
+ if (!error || error.code !== 'EEXIST') {
89
+ throw error;
90
+ }
91
+ });
92
+ }
93
+
94
+ function mergeConfig(config) {
95
+ const features = {
96
+ ...DEFAULT_CONFIG.features,
97
+ ...(config.features || {}),
98
+ };
99
+
100
+ if (typeof config?.features?.smartPythonInstall === 'boolean' && typeof config?.features?.smartProjectInstall !== 'boolean') {
101
+ features.smartProjectInstall = config.features.smartPythonInstall;
102
+ }
103
+
104
+ return {
105
+ features,
106
+ projects: {
107
+ ...DEFAULT_CONFIG.projects,
108
+ ...(config.projects || {}),
109
+ },
110
+ };
111
+ }
112
+
113
+ function cloneDefaultConfig() {
114
+ return {
115
+ features: {
116
+ ...DEFAULT_CONFIG.features,
117
+ },
118
+ projects: {
119
+ ...DEFAULT_CONFIG.projects,
120
+ },
121
+ };
122
+ }
@@ -0,0 +1,31 @@
1
+ import path from 'node:path';
2
+
3
+ export function getScopeOptions() {
4
+ const home = process.env.HOME || '/data/data/com.termux/files/home';
5
+
6
+ return [
7
+ {
8
+ key: 'actual',
9
+ label: 'Carpeta actual',
10
+ root: process.cwd(),
11
+ description: 'Busca solo dentro de donde estas parado ahora.',
12
+ },
13
+ {
14
+ key: 'home',
15
+ label: 'Home de Termux',
16
+ root: home,
17
+ description: 'Busca dentro de tu entorno principal de Termux.',
18
+ },
19
+ {
20
+ key: 'android',
21
+ label: 'Almacenamiento Android',
22
+ root: '/sdcard',
23
+ description: 'Busca dentro del almacenamiento compartido accesible.',
24
+ },
25
+ ];
26
+ }
27
+
28
+ export function resolveScopeRoot(scopeKey, options = getScopeOptions()) {
29
+ const found = options.find((option) => option.key === scopeKey);
30
+ return found ? path.resolve(found.root) : '';
31
+ }