dex-termux-cli 0.2.3 → 0.3.0-beta.1
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 +58 -61
- package/data/commands/explain.json +63 -0
- package/package.json +5 -2
- package/src/app/main.js +11 -1
- package/src/commands/android-shell.js +49 -7
- package/src/commands/context.js +22 -0
- package/src/commands/explain.js +11 -2
- package/src/commands/install.js +279 -28
- package/src/commands/menu.js +36 -3
- package/src/commands/safe-shell.js +211 -10
- package/src/commands/search.js +5 -1
- package/src/commands/tree.js +5 -1
- package/src/core/args.js +10 -0
- package/src/core/config.js +40 -2
- package/src/core/scopes.js +22 -12
- package/src/ui/output.js +8 -6
- package/src/ui/prompt.js +76 -13
- package/src/utils/platform.js +65 -0
- package/src/utils/project-context.js +116 -32
- package/src/utils/shell.js +33 -7
package/README.md
CHANGED
|
@@ -7,89 +7,86 @@
|
|
|
7
7
|
<h1 align="center">Dex Termux CLI</h1>
|
|
8
8
|
|
|
9
9
|
<p align="center">
|
|
10
|
-
CLI visual para Termux con
|
|
10
|
+
CLI visual para Termux y Linux con busqueda guiada, tree legible, contexto de proyecto, version visible y flujos seguros para Python, Node y PHP.
|
|
11
11
|
</p>
|
|
12
12
|
|
|
13
13
|
<p align="center">
|
|
14
|
-
<img alt="platform" src="https://img.shields.io/badge/platform-Termux-111111?style=flat-square&labelColor=F97316&color=1F2937">
|
|
15
|
-
<img alt="storage" src="https://img.shields.io/badge/storage-Android-111111?style=flat-square&labelColor=0F766E&color=1F2937">
|
|
14
|
+
<img alt="platform" src="https://img.shields.io/badge/platform-Termux%20%7C%20Linux-111111?style=flat-square&labelColor=F97316&color=1F2937">
|
|
16
15
|
<img alt="safe-mode" src="https://img.shields.io/badge/safe%20mode-Python%20%7C%20Node%20%7C%20PHP-111111?style=flat-square&labelColor=2563EB&color=1F2937">
|
|
17
|
-
<img alt="
|
|
16
|
+
<img alt="channel" src="https://img.shields.io/badge/channel-beta-111111?style=flat-square&labelColor=B45309&color=1F2937">
|
|
18
17
|
</p>
|
|
19
18
|
|
|
20
19
|
<p align="center">
|
|
21
20
|
<strong>Explorar.</strong> <strong>Entender.</strong> <strong>Instalar.</strong> <strong>Entrar al modo seguro.</strong>
|
|
22
21
|
</p>
|
|
23
22
|
|
|
24
|
-
> Estado actual: soporte seguro para Python, Node y PHP,
|
|
23
|
+
> Estado actual: beta `0.3.0-beta.1` con soporte seguro para Python, Node y PHP, contexto de proyecto y modo de plataforma `auto | termux | linux`.
|
|
25
24
|
|
|
26
|
-
##
|
|
25
|
+
## Vision general
|
|
27
26
|
|
|
28
|
-
Dex
|
|
27
|
+
Dex mejora tareas comunes de terminal sin tapar el entorno real. Sirve para explorar carpetas, entender comandos, detectar proyectos y preparar instalaciones con una capa mas clara encima de Termux o Linux.
|
|
29
28
|
|
|
30
|
-
##
|
|
29
|
+
## Lo que ya incluye
|
|
31
30
|
|
|
32
|
-
-
|
|
33
|
-
-
|
|
34
|
-
-
|
|
35
|
-
-
|
|
36
|
-
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
| `dex -m` | abre el menú principal |
|
|
43
|
-
| `dex -v` | muestra la versión local y revisa la publicada |
|
|
44
|
-
| `dex -c` | muestra el contexto del proyecto actual |
|
|
45
|
-
| `dex -b` | busca archivos o carpetas |
|
|
46
|
-
| `dex -e` | explica comandos comunes |
|
|
47
|
-
| `dex -t` | muestra árbol guiado |
|
|
48
|
-
| `dex -a` | acceso rápido a Android |
|
|
49
|
-
| `dex -i` | instala dependencias del proyecto actual |
|
|
50
|
-
| `dex -r` | abre el modo seguro del proyecto |
|
|
51
|
-
|
|
52
|
-
## Qué resuelve
|
|
53
|
-
|
|
54
|
-
- salida visual más clara que varios comandos crudos de terminal
|
|
55
|
-
- exploración rápida de carpetas y archivos sin recordar tanta sintaxis
|
|
56
|
-
- detección del lenguaje del proyecto actual con una vista compacta útil para prompt
|
|
57
|
-
- instalación segura cuando Android storage rompe el flujo normal
|
|
58
|
-
- shell segura por proyecto para seguir trabajando con el entorno correcto
|
|
31
|
+
- `dex -m`: menu principal
|
|
32
|
+
- `dex -v`: version local y publicada
|
|
33
|
+
- `dex -c`: contexto del proyecto actual
|
|
34
|
+
- `dex --prompt-context`: badge compacto para prompt
|
|
35
|
+
- `dex -b`: busqueda guiada
|
|
36
|
+
- `dex -e`: explicacion de comandos comunes
|
|
37
|
+
- `dex -t`: tree guiado
|
|
38
|
+
- `dex -a`: shell redisenada para Android storage en modo Termux
|
|
39
|
+
- `dex -i`: instalacion del proyecto actual incluso desde subcarpetas del proyecto
|
|
40
|
+
- `dex -r`: shell segura del proyecto actual incluso desde subcarpetas del proyecto
|
|
59
41
|
|
|
60
42
|
## Contexto del proyecto
|
|
61
43
|
|
|
62
|
-
Dex
|
|
63
|
-
|
|
64
|
-
## Instalación segura por lenguaje
|
|
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.
|
|
65
45
|
|
|
66
|
-
|
|
67
|
-
| --- | --- | --- |
|
|
68
|
-
| Python | `pip`, `requirements.txt`, `pyproject.toml`, `setup.py` o imports inferidos | `venv` seguro |
|
|
69
|
-
| Node | `npm ci` o `npm install` | copia segura del proyecto en `HOME` |
|
|
70
|
-
| PHP | `composer install --no-interaction` | copia segura del proyecto en `HOME` |
|
|
71
|
-
|
|
72
|
-
## Cómo funciona el modo seguro
|
|
46
|
+
Lenguajes detectables actualmente:
|
|
73
47
|
|
|
74
|
-
|
|
75
|
-
|
|
48
|
+
- Python
|
|
49
|
+
- Node
|
|
50
|
+
- PHP
|
|
51
|
+
- Rust
|
|
52
|
+
- Go
|
|
53
|
+
- Java
|
|
54
|
+
- Ruby
|
|
76
55
|
|
|
77
|
-
|
|
78
|
-
Dex puede trabajar con una copia segura del proyecto fuera de Android storage para que `npm` y `node_modules` no choquen con las limitaciones del almacenamiento compartido.
|
|
56
|
+
## Modo de plataforma
|
|
79
57
|
|
|
80
|
-
|
|
81
|
-
Dex puede instalar dependencias con `composer` en una copia segura del proyecto y abrir una shell lista para seguir trabajando desde ahí.
|
|
58
|
+
Dex puede trabajar en tres modos:
|
|
82
59
|
|
|
83
|
-
|
|
60
|
+
- `auto`: detecta si estas en Termux o Linux
|
|
61
|
+
- `termux`: habilita Android storage y restricciones propias de Termux
|
|
62
|
+
- `linux`: desactiva Android y trabaja como entorno Linux puro
|
|
84
63
|
|
|
85
|
-
|
|
86
|
-
- shell segura activa para Python, Node y PHP
|
|
87
|
-
- menú y ayuda con una salida más clara
|
|
88
|
-
- lectura del proyecto centrada en Termux y Android storage
|
|
64
|
+
El modo actual se guarda en `~/.config/dex/config.json`.
|
|
89
65
|
|
|
90
|
-
##
|
|
66
|
+
## Instalacion segura por lenguaje
|
|
91
67
|
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
68
|
+
| Lenguaje | Estrategia | Modo seguro |
|
|
69
|
+
| --- | --- | --- |
|
|
70
|
+
| Python | `pip`, `requirements.txt`, `pyproject.toml`, `setup.py` o imports inferidos | `venv` seguro |
|
|
71
|
+
| Node | `npm ci` o `npm install` | workspace seguro en `HOME` |
|
|
72
|
+
| PHP | `composer install --no-interaction` | workspace seguro en `HOME` |
|
|
73
|
+
| Ruby | `bundle install` | workspace seguro en `HOME` |
|
|
74
|
+
| Go | `go mod download` | workspace seguro en `HOME` |
|
|
75
|
+
| Rust | `cargo fetch` | workspace seguro en `HOME` |
|
|
76
|
+
| Java | `mvn dependency:go-offline`, `./mvnw dependency:go-offline`, `gradle dependencies` o `./gradlew dependencies` | workspace seguro en `HOME` |
|
|
77
|
+
|
|
78
|
+
## Estado real de la beta
|
|
79
|
+
|
|
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`)
|
|
82
|
+
- `dex -i` funciona en proyectos validos y ahora usa el root detectado del proyecto aunque entres desde una subcarpeta
|
|
83
|
+
- `dex -r` funciona para proyectos soportados, hereda mejor el root del proyecto y en Termux exige Android storage
|
|
84
|
+
- el modo seguro real ya cubre Python, Node, PHP, Ruby, Go, Rust y Java
|
|
85
|
+
- `dex -e` ya resuelve su data interna sin depender del directorio actual
|
|
86
|
+
|
|
87
|
+
## Roadmap inmediato
|
|
88
|
+
|
|
89
|
+
- pulir textos y mensajes de error
|
|
90
|
+
- validar modo Linux de punta a punta
|
|
91
|
+
- seguir puliendo mensajes, UX y validacion prolongada en Linux y Java
|
|
92
|
+
- explorar ejecucion guiada de entrypoints
|
|
@@ -70,5 +70,68 @@
|
|
|
70
70
|
"Puede ser lento en rutas muy grandes.",
|
|
71
71
|
"Dex -b esta pensado precisamente para evitarte recordar esta sintaxis."
|
|
72
72
|
]
|
|
73
|
+
},
|
|
74
|
+
"pwd": {
|
|
75
|
+
"summary": "Muestra la ruta actual donde estas parado.",
|
|
76
|
+
"usage": "pwd",
|
|
77
|
+
"risk": "bajo",
|
|
78
|
+
"notes": [
|
|
79
|
+
"Es util para confirmar si Dex te movio al root del proyecto o al workspace seguro.",
|
|
80
|
+
"En Android storage puede mostrar /storage/emulated/0 aunque entres por /sdcard."
|
|
81
|
+
]
|
|
82
|
+
},
|
|
83
|
+
"mkdir": {
|
|
84
|
+
"summary": "Crea carpetas nuevas.",
|
|
85
|
+
"usage": "mkdir carpeta\nmkdir -p ruta/larga",
|
|
86
|
+
"risk": "bajo",
|
|
87
|
+
"notes": [
|
|
88
|
+
"Usa -p para crear rutas completas sin error si parte del camino ya existe.",
|
|
89
|
+
"No borra ni sobrescribe contenido existente."
|
|
90
|
+
]
|
|
91
|
+
},
|
|
92
|
+
"npm": {
|
|
93
|
+
"summary": "Gestiona dependencias y scripts de proyectos Node.",
|
|
94
|
+
"usage": "npm install\nnpm ci\nnpm run dev",
|
|
95
|
+
"risk": "medio",
|
|
96
|
+
"notes": [
|
|
97
|
+
"npm ci es mejor cuando ya existe package-lock.json y quieres una instalacion reproducible.",
|
|
98
|
+
"En Android storage Dex puede rescatar proyectos Node en un workspace seguro dentro de HOME."
|
|
99
|
+
]
|
|
100
|
+
},
|
|
101
|
+
"node": {
|
|
102
|
+
"summary": "Ejecuta codigo JavaScript y proyectos Node.",
|
|
103
|
+
"usage": "node app.js\nnode -v\nnode -e \"console.log(1)\"",
|
|
104
|
+
"risk": "bajo",
|
|
105
|
+
"notes": [
|
|
106
|
+
"Si el proyecto usa dependencias locales, entra primero al root correcto o al workspace seguro.",
|
|
107
|
+
"Dex usa node tambien para correr su propio CLI."
|
|
108
|
+
]
|
|
109
|
+
},
|
|
110
|
+
"python": {
|
|
111
|
+
"summary": "Ejecuta scripts Python e inspecciona el runtime actual.",
|
|
112
|
+
"usage": "python bot.py\npython -V\npython -m pip list",
|
|
113
|
+
"risk": "bajo",
|
|
114
|
+
"notes": [
|
|
115
|
+
"Dentro de safe-shell puede apuntar a un venv preparado por Dex.",
|
|
116
|
+
"python -m pip suele ser mas fiable que usar pip suelto."
|
|
117
|
+
]
|
|
118
|
+
},
|
|
119
|
+
"pip": {
|
|
120
|
+
"summary": "Instala y gestiona paquetes Python.",
|
|
121
|
+
"usage": "pip install paquete\npip install -r requirements.txt",
|
|
122
|
+
"risk": "medio",
|
|
123
|
+
"notes": [
|
|
124
|
+
"En proyectos sensibles conviene usar venv o el modo seguro de Dex.",
|
|
125
|
+
"Si estas en Android storage, Dex puede rescatar la instalacion automaticamente en ciertos casos."
|
|
126
|
+
]
|
|
127
|
+
},
|
|
128
|
+
"composer": {
|
|
129
|
+
"summary": "Instala dependencias y maneja proyectos PHP.",
|
|
130
|
+
"usage": "composer install\ncomposer update\ncomposer dump-autoload",
|
|
131
|
+
"risk": "medio",
|
|
132
|
+
"notes": [
|
|
133
|
+
"En Dex, composer puede ejecutarse dentro de un workspace seguro para evitar problemas de Android storage.",
|
|
134
|
+
"composer update cambia versiones; composer install intenta respetar composer.lock."
|
|
135
|
+
]
|
|
73
136
|
}
|
|
74
137
|
}
|
package/package.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dex-termux-cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0-beta.1",
|
|
4
4
|
"type": "module",
|
|
5
|
-
"description": "
|
|
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.",
|
|
6
6
|
"keywords": [
|
|
7
7
|
"termux",
|
|
8
8
|
"android",
|
|
@@ -38,5 +38,8 @@
|
|
|
38
38
|
},
|
|
39
39
|
"engines": {
|
|
40
40
|
"node": ">=18"
|
|
41
|
+
},
|
|
42
|
+
"dependencies": {
|
|
43
|
+
"tree": "^0.1.3"
|
|
41
44
|
}
|
|
42
45
|
}
|
package/src/app/main.js
CHANGED
|
@@ -15,7 +15,7 @@ import { detectProjectContext, formatProjectContext } from '../utils/project-con
|
|
|
15
15
|
export async function main(argv = process.argv.slice(2)) {
|
|
16
16
|
const parsed = parseArgs(argv);
|
|
17
17
|
const config = await loadUserConfig();
|
|
18
|
-
const isPromptOnly = parsed.command === 'prompt-context';
|
|
18
|
+
const isPromptOnly = parsed.command === 'prompt-context' || parsed.command === 'prompt-project-root' || parsed.command === 'prompt-project-path';
|
|
19
19
|
const isContextOnly = parsed.command === 'context';
|
|
20
20
|
const isVersionOnly = parsed.command === 'version';
|
|
21
21
|
|
|
@@ -46,6 +46,16 @@ export async function main(argv = process.argv.slice(2)) {
|
|
|
46
46
|
return;
|
|
47
47
|
}
|
|
48
48
|
|
|
49
|
+
if (parsed.command === 'prompt-project-root') {
|
|
50
|
+
await runContextCommand({ projectRoot: true });
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
if (parsed.command === 'prompt-project-path') {
|
|
55
|
+
await runContextCommand({ projectPath: true });
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
|
|
49
59
|
if (parsed.command === 'search') {
|
|
50
60
|
await runSearchCommand(parsed);
|
|
51
61
|
return;
|
|
@@ -2,8 +2,12 @@ import fs from 'node:fs/promises';
|
|
|
2
2
|
import { spawn } from 'node:child_process';
|
|
3
3
|
import { getUserConfigPath, loadUserConfig } from '../core/config.js';
|
|
4
4
|
import { resolveInteractiveShell } from '../utils/shell.js';
|
|
5
|
+
import { canUseAndroidFeatures, getAndroidStorageRoot, resolvePlatformMode } from '../utils/platform.js';
|
|
5
6
|
|
|
6
|
-
const
|
|
7
|
+
const ANDROID_BASH_CANDIDATES = [
|
|
8
|
+
'/data/data/com.termux/files/usr/bin/bash',
|
|
9
|
+
'/bin/bash',
|
|
10
|
+
];
|
|
7
11
|
|
|
8
12
|
export async function runAndroidShellCommand() {
|
|
9
13
|
const config = await loadUserConfig();
|
|
@@ -14,26 +18,41 @@ export async function runAndroidShellCommand() {
|
|
|
14
18
|
);
|
|
15
19
|
}
|
|
16
20
|
|
|
21
|
+
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);
|
|
28
|
+
|
|
17
29
|
try {
|
|
18
|
-
await fs.access(
|
|
30
|
+
await fs.access(androidRoot);
|
|
19
31
|
} catch {
|
|
20
|
-
throw new Error(`No se puede acceder a la ruta Android: ${
|
|
32
|
+
throw new Error(`No se puede acceder a la ruta Android: ${androidRoot}`);
|
|
21
33
|
}
|
|
22
34
|
|
|
23
|
-
const interactiveShell = await
|
|
35
|
+
const interactiveShell = await resolveAndroidInteractiveShell(platformMode);
|
|
24
36
|
|
|
25
|
-
console.log(
|
|
37
|
+
console.log('Dex Android');
|
|
38
|
+
console.log('');
|
|
39
|
+
console.log(`Raiz : ${androidRoot}`);
|
|
26
40
|
console.log(`Shell : ${interactiveShell.shellName} interactiva`);
|
|
27
|
-
console.log('
|
|
41
|
+
console.log('Modo : interfaz Android redisenada');
|
|
42
|
+
console.log('Atajos : dl, docs, dcim, pics, music, movies, shared');
|
|
43
|
+
console.log('Salida : escribe exit para volver');
|
|
28
44
|
console.log('');
|
|
29
45
|
|
|
30
46
|
await new Promise((resolve, reject) => {
|
|
31
47
|
const child = spawn(interactiveShell.shellPath, interactiveShell.shellArgs, {
|
|
32
|
-
cwd:
|
|
48
|
+
cwd: androidRoot,
|
|
33
49
|
stdio: 'inherit',
|
|
34
50
|
env: {
|
|
35
51
|
...process.env,
|
|
36
52
|
DEX_CONTEXT: 'android-shell',
|
|
53
|
+
DEX_ANDROID_ROOT: androidRoot,
|
|
54
|
+
DEX_ANDROID_LABEL: 'ANDROID STORAGE',
|
|
55
|
+
DEX_ANDROID_SHELL: interactiveShell.shellName,
|
|
37
56
|
},
|
|
38
57
|
});
|
|
39
58
|
|
|
@@ -53,3 +72,26 @@ export async function runAndroidShellCommand() {
|
|
|
53
72
|
});
|
|
54
73
|
});
|
|
55
74
|
}
|
|
75
|
+
|
|
76
|
+
async function resolveAndroidInteractiveShell(platformMode) {
|
|
77
|
+
const interactiveShell = await resolveInteractiveShell(platformMode);
|
|
78
|
+
|
|
79
|
+
if (interactiveShell.shellName !== 'sh') {
|
|
80
|
+
return interactiveShell;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
for (const shellPath of ANDROID_BASH_CANDIDATES) {
|
|
84
|
+
try {
|
|
85
|
+
await fs.access(shellPath);
|
|
86
|
+
return {
|
|
87
|
+
shellPath,
|
|
88
|
+
shellArgs: ['-i'],
|
|
89
|
+
shellName: 'bash',
|
|
90
|
+
};
|
|
91
|
+
} catch {
|
|
92
|
+
// sigue con el siguiente candidato
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
return interactiveShell;
|
|
97
|
+
}
|
package/src/commands/context.js
CHANGED
|
@@ -2,6 +2,8 @@ import {
|
|
|
2
2
|
detectProjectContext,
|
|
3
3
|
formatProjectContext,
|
|
4
4
|
formatPromptContext,
|
|
5
|
+
formatPromptProjectRoot,
|
|
6
|
+
formatPromptProjectPath,
|
|
5
7
|
formatProjectContextDetails,
|
|
6
8
|
} from '../utils/project-context.js';
|
|
7
9
|
|
|
@@ -9,6 +11,26 @@ export async function runContextCommand(options = {}) {
|
|
|
9
11
|
const context = await detectProjectContext();
|
|
10
12
|
|
|
11
13
|
if (!context) {
|
|
14
|
+
if (!options.prompt && !options.projectRoot && !options.projectPath) {
|
|
15
|
+
console.log('No detecte un proyecto compatible en esta carpeta.');
|
|
16
|
+
console.log('Prueba entrando a un proyecto Python, Node, PHP, Rust, Go, Java o Ruby.');
|
|
17
|
+
}
|
|
18
|
+
return;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
if (options.projectRoot) {
|
|
22
|
+
const rootName = formatPromptProjectRoot(context);
|
|
23
|
+
if (rootName) {
|
|
24
|
+
process.stdout.write(rootName);
|
|
25
|
+
}
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
if (options.projectPath) {
|
|
30
|
+
const projectPath = formatPromptProjectPath(context);
|
|
31
|
+
if (projectPath) {
|
|
32
|
+
process.stdout.write(projectPath);
|
|
33
|
+
}
|
|
12
34
|
return;
|
|
13
35
|
}
|
|
14
36
|
|
package/src/commands/explain.js
CHANGED
|
@@ -1,13 +1,15 @@
|
|
|
1
1
|
import fs from 'node:fs/promises';
|
|
2
2
|
import path from 'node:path';
|
|
3
|
+
import { fileURLToPath } from 'node:url';
|
|
3
4
|
import { printExplainEntry, printSection } from '../ui/output.js';
|
|
4
5
|
import { chooseExplainTopic } from '../ui/prompt.js';
|
|
5
6
|
|
|
6
|
-
const
|
|
7
|
+
const MODULE_DIR = path.dirname(fileURLToPath(import.meta.url));
|
|
8
|
+
const EXPLAIN_DATA_PATH = path.resolve(MODULE_DIR, '..', '..', 'data', 'commands', 'explain.json');
|
|
7
9
|
|
|
8
10
|
export async function runExplainCommand({ topic }) {
|
|
9
11
|
const entries = await loadExplainEntries();
|
|
10
|
-
const selectedTopic = topic ? topic
|
|
12
|
+
const selectedTopic = topic ? normalizeTopic(topic) : await chooseExplainTopic(entries);
|
|
11
13
|
const entry = entries[selectedTopic];
|
|
12
14
|
|
|
13
15
|
if (!entry) {
|
|
@@ -23,3 +25,10 @@ async function loadExplainEntries() {
|
|
|
23
25
|
const raw = await fs.readFile(EXPLAIN_DATA_PATH, 'utf8');
|
|
24
26
|
return JSON.parse(raw);
|
|
25
27
|
}
|
|
28
|
+
|
|
29
|
+
function normalizeTopic(topic) {
|
|
30
|
+
return String(topic)
|
|
31
|
+
.trim()
|
|
32
|
+
.split(/\s+/)[0]
|
|
33
|
+
.toLowerCase();
|
|
34
|
+
}
|