dex-termux-cli 0.3.0-beta.3 → 0.3.0-beta.5
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 +9 -3
- package/bin/dex-project-context +12 -0
- package/package.json +8 -2
- package/src/app/main.js +14 -1
- package/src/commands/android-shell.js +165 -54
- package/src/commands/safe-shell.js +209 -29
- package/src/commands/sessions-daemon.js +307 -0
- package/src/commands/sessions.js +347 -0
- package/src/commands/tree.js +30 -3
- package/src/core/args.js +38 -0
- package/src/core/config.js +37 -0
- package/src/ui/output.js +15 -6
- package/src/utils/fs-tree.js +34 -2
- package/src/utils/path-icons.js +90 -0
- package/src/utils/platform.js +4 -1
- package/src/utils/project-context.js +4 -18
- package/src/utils/prompt-theme.js +223 -0
|
@@ -0,0 +1,347 @@
|
|
|
1
|
+
import fs from 'node:fs/promises';
|
|
2
|
+
import net from 'node:net';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import { spawn } from 'node:child_process';
|
|
5
|
+
import { getHomeDirectory } from '../utils/platform.js';
|
|
6
|
+
|
|
7
|
+
const SESSION_REGISTRY_FILE = 'sessions.json';
|
|
8
|
+
const SESSION_SOCKET_FILE = 'sessions.sock';
|
|
9
|
+
const ESCAPE_BYTE = '\x1d';
|
|
10
|
+
|
|
11
|
+
export async function runSessionsCommand(parsed = {}) {
|
|
12
|
+
const action = normalizeSessionAction(parsed.topic);
|
|
13
|
+
|
|
14
|
+
if (action === 'unknown') {
|
|
15
|
+
printSessionsUsage();
|
|
16
|
+
return;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const client = await connectSessionsClient();
|
|
20
|
+
|
|
21
|
+
if (action === 'list') {
|
|
22
|
+
const response = await sendRequest(client, { action: 'list' });
|
|
23
|
+
printSessionsList(response.sessions || []);
|
|
24
|
+
client.end();
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
if (action === 'new') {
|
|
29
|
+
const response = await sendRequest(client, {
|
|
30
|
+
action: 'new',
|
|
31
|
+
name: parsed.target || '',
|
|
32
|
+
cwd: process.cwd(),
|
|
33
|
+
cols: getTerminalCols(),
|
|
34
|
+
rows: getTerminalRows(),
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
if (!response.session?.id) {
|
|
38
|
+
throw new Error('No pude crear la sesion PTY.');
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
await attachToSession(client, response.session);
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
if (action === 'attach') {
|
|
46
|
+
if (!parsed.target) {
|
|
47
|
+
throw new Error('Debes indicar el id o nombre de la sesion. Usa: dex sessions attach <id|nombre>');
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const response = await sendRequest(client, {
|
|
51
|
+
action: 'attach',
|
|
52
|
+
selector: parsed.target,
|
|
53
|
+
cols: getTerminalCols(),
|
|
54
|
+
rows: getTerminalRows(),
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
if (!response.session?.id) {
|
|
58
|
+
throw new Error(`No pude adjuntar la sesion: ${parsed.target}`);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
await attachToSession(client, response.session);
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
if (action === 'kill') {
|
|
66
|
+
if (!parsed.target) {
|
|
67
|
+
throw new Error('Debes indicar el id o nombre de la sesion. Usa: dex sessions kill <id|nombre>');
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const response = await sendRequest(client, {
|
|
71
|
+
action: 'kill',
|
|
72
|
+
selector: parsed.target,
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
console.log(`Sesion cerrada: ${response.session?.name || parsed.target}`);
|
|
76
|
+
client.end();
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
async function connectSessionsClient() {
|
|
81
|
+
await ensureSessionsDaemon();
|
|
82
|
+
const socketPath = getSessionSocketPath();
|
|
83
|
+
|
|
84
|
+
return await new Promise((resolve, reject) => {
|
|
85
|
+
const client = net.createConnection(socketPath);
|
|
86
|
+
client.setEncoding('utf8');
|
|
87
|
+
client.once('connect', () => resolve(client));
|
|
88
|
+
client.once('error', reject);
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
async function ensureSessionsDaemon() {
|
|
93
|
+
if (await canPingSessionsDaemon()) {
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
await fs.mkdir(path.dirname(getSessionSocketPath()), { recursive: true });
|
|
98
|
+
|
|
99
|
+
const child = spawn(process.execPath, ['./bin/dex', '--sessions-daemon'], {
|
|
100
|
+
cwd: process.cwd(),
|
|
101
|
+
detached: true,
|
|
102
|
+
stdio: 'ignore',
|
|
103
|
+
env: process.env,
|
|
104
|
+
});
|
|
105
|
+
child.unref();
|
|
106
|
+
|
|
107
|
+
for (let attempt = 0; attempt < 30; attempt += 1) {
|
|
108
|
+
await delay(100);
|
|
109
|
+
if (await canPingSessionsDaemon()) {
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
throw new Error('No pude iniciar el daemon de sesiones Dex.');
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
async function canPingSessionsDaemon() {
|
|
118
|
+
const socketPath = getSessionSocketPath();
|
|
119
|
+
|
|
120
|
+
try {
|
|
121
|
+
await fs.access(socketPath);
|
|
122
|
+
} catch {
|
|
123
|
+
return false;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
try {
|
|
127
|
+
const client = await new Promise((resolve, reject) => {
|
|
128
|
+
const socket = net.createConnection(socketPath);
|
|
129
|
+
socket.setEncoding('utf8');
|
|
130
|
+
socket.once('connect', () => resolve(socket));
|
|
131
|
+
socket.once('error', reject);
|
|
132
|
+
});
|
|
133
|
+
const response = await sendRequest(client, { action: 'ping' });
|
|
134
|
+
client.end();
|
|
135
|
+
return response.ok === true;
|
|
136
|
+
} catch {
|
|
137
|
+
return false;
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
async function sendRequest(client, message) {
|
|
142
|
+
return await new Promise((resolve, reject) => {
|
|
143
|
+
let buffer = '';
|
|
144
|
+
|
|
145
|
+
const onData = (chunk) => {
|
|
146
|
+
buffer += chunk;
|
|
147
|
+
const lines = buffer.split('\n');
|
|
148
|
+
buffer = lines.pop() || '';
|
|
149
|
+
|
|
150
|
+
for (const line of lines) {
|
|
151
|
+
if (!line.trim()) {
|
|
152
|
+
continue;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
const payload = JSON.parse(line);
|
|
156
|
+
cleanup();
|
|
157
|
+
|
|
158
|
+
if (payload.error) {
|
|
159
|
+
reject(new Error(payload.error));
|
|
160
|
+
return;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
resolve(payload);
|
|
164
|
+
return;
|
|
165
|
+
}
|
|
166
|
+
};
|
|
167
|
+
|
|
168
|
+
const onError = (error) => {
|
|
169
|
+
cleanup();
|
|
170
|
+
reject(error);
|
|
171
|
+
};
|
|
172
|
+
|
|
173
|
+
const cleanup = () => {
|
|
174
|
+
client.off('data', onData);
|
|
175
|
+
client.off('error', onError);
|
|
176
|
+
};
|
|
177
|
+
|
|
178
|
+
client.on('data', onData);
|
|
179
|
+
client.on('error', onError);
|
|
180
|
+
client.write(JSON.stringify(message) + '\n');
|
|
181
|
+
});
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
async function attachToSession(client, session) {
|
|
185
|
+
if (!process.stdin.isTTY || !process.stdout.isTTY) {
|
|
186
|
+
client.end();
|
|
187
|
+
throw new Error('El attach de sesiones necesita una terminal interactiva.');
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
process.stdout.write(`\n[Dex attach] ${session.name} Ctrl+] para volver a Dex\n\n`);
|
|
191
|
+
|
|
192
|
+
return await new Promise((resolve) => {
|
|
193
|
+
let buffer = '';
|
|
194
|
+
|
|
195
|
+
const cleanup = () => {
|
|
196
|
+
client.off('data', onSocketData);
|
|
197
|
+
client.off('close', onSocketClose);
|
|
198
|
+
client.off('error', onSocketError);
|
|
199
|
+
process.stdin.off('data', onInput);
|
|
200
|
+
if (typeof process.stdin.setRawMode === 'function') {
|
|
201
|
+
process.stdin.setRawMode(false);
|
|
202
|
+
}
|
|
203
|
+
process.stdin.pause();
|
|
204
|
+
client.end();
|
|
205
|
+
};
|
|
206
|
+
|
|
207
|
+
const finish = (message = '') => {
|
|
208
|
+
cleanup();
|
|
209
|
+
if (message) {
|
|
210
|
+
process.stdout.write(message);
|
|
211
|
+
}
|
|
212
|
+
resolve();
|
|
213
|
+
};
|
|
214
|
+
|
|
215
|
+
const onSocketData = (chunk) => {
|
|
216
|
+
buffer += chunk;
|
|
217
|
+
const lines = buffer.split('\n');
|
|
218
|
+
buffer = lines.pop() || '';
|
|
219
|
+
|
|
220
|
+
for (const line of lines) {
|
|
221
|
+
if (!line.trim()) {
|
|
222
|
+
continue;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
const payload = JSON.parse(line);
|
|
226
|
+
|
|
227
|
+
if (payload.type === 'data') {
|
|
228
|
+
process.stdout.write(Buffer.from(payload.data, 'base64'));
|
|
229
|
+
continue;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
if (payload.type === 'exit') {
|
|
233
|
+
finish('\n[Dex attach] sesion finalizada\n');
|
|
234
|
+
return;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
if (payload.type === 'detached') {
|
|
238
|
+
finish('\n[Dex attach] sesion desacoplada\n');
|
|
239
|
+
return;
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
};
|
|
243
|
+
|
|
244
|
+
const onSocketClose = () => {
|
|
245
|
+
finish('\n[Dex attach] conexion cerrada\n');
|
|
246
|
+
};
|
|
247
|
+
|
|
248
|
+
const onSocketError = (error) => {
|
|
249
|
+
finish(`\n[Dex attach] error: ${error.message}\n`);
|
|
250
|
+
};
|
|
251
|
+
|
|
252
|
+
const onInput = (chunk) => {
|
|
253
|
+
const raw = Buffer.from(chunk);
|
|
254
|
+
|
|
255
|
+
if (raw.toString('utf8') === ESCAPE_BYTE) {
|
|
256
|
+
client.write(JSON.stringify({ action: 'detach', sessionId: session.id }) + '\n');
|
|
257
|
+
return;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
client.write(JSON.stringify({ action: 'input', sessionId: session.id, data: raw.toString('base64') }) + '\n');
|
|
261
|
+
};
|
|
262
|
+
|
|
263
|
+
client.on('data', onSocketData);
|
|
264
|
+
client.on('close', onSocketClose);
|
|
265
|
+
client.on('error', onSocketError);
|
|
266
|
+
|
|
267
|
+
if (typeof process.stdin.setRawMode === 'function') {
|
|
268
|
+
process.stdin.setRawMode(true);
|
|
269
|
+
}
|
|
270
|
+
process.stdin.resume();
|
|
271
|
+
process.stdin.on('data', onInput);
|
|
272
|
+
});
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
function printSessionsList(sessions) {
|
|
276
|
+
console.log('Dex Sessions');
|
|
277
|
+
console.log('');
|
|
278
|
+
|
|
279
|
+
if (!sessions.length) {
|
|
280
|
+
console.log('No hay sesiones registradas.');
|
|
281
|
+
console.log('');
|
|
282
|
+
printSessionsUsage();
|
|
283
|
+
return;
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
for (const session of sessions) {
|
|
287
|
+
console.log(`${session.id} ${session.name}`);
|
|
288
|
+
console.log(`Estado : ${session.status}`);
|
|
289
|
+
console.log(`Shell : ${session.shell}`);
|
|
290
|
+
console.log(`Ruta : ${session.cwd}`);
|
|
291
|
+
console.log(`PID : ${session.pid}`);
|
|
292
|
+
console.log('');
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
function normalizeSessionAction(value) {
|
|
297
|
+
const normalized = String(value || '').trim().toLowerCase();
|
|
298
|
+
if (!normalized) {
|
|
299
|
+
return 'list';
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
if (['new', 'create'].includes(normalized)) {
|
|
303
|
+
return 'new';
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
if (['attach', 'open', 'use'].includes(normalized)) {
|
|
307
|
+
return 'attach';
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
if (['kill', 'close', 'rm', 'remove'].includes(normalized)) {
|
|
311
|
+
return 'kill';
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
if (['list', 'ls'].includes(normalized)) {
|
|
315
|
+
return 'list';
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
return 'unknown';
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
function printSessionsUsage() {
|
|
322
|
+
console.log('Uso:');
|
|
323
|
+
console.log(' dex sessions');
|
|
324
|
+
console.log(' dex sessions new <nombre>');
|
|
325
|
+
console.log(' dex sessions attach <id|nombre>');
|
|
326
|
+
console.log(' dex sessions kill <id|nombre>');
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
function getSessionSocketPath() {
|
|
330
|
+
return path.join(getHomeDirectory(), '.config', 'dex', SESSION_SOCKET_FILE);
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
export function getSessionRegistryPath() {
|
|
334
|
+
return path.join(getHomeDirectory(), '.config', 'dex', SESSION_REGISTRY_FILE);
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
function getTerminalCols() {
|
|
338
|
+
return Number.parseInt(process.env.COLUMNS || '80', 10) || 80;
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
function getTerminalRows() {
|
|
342
|
+
return Number.parseInt(process.env.LINES || '24', 10) || 24;
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
function delay(ms) {
|
|
346
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
347
|
+
}
|
package/src/commands/tree.js
CHANGED
|
@@ -7,12 +7,19 @@ import { getScopeOptions, resolveScopeRoot } from '../core/scopes.js';
|
|
|
7
7
|
import { buildTreeReport } from '../utils/fs-tree.js';
|
|
8
8
|
import { resolvePlatformMode } from '../utils/platform.js';
|
|
9
9
|
|
|
10
|
-
|
|
10
|
+
const TREE_DEPTH_PRESETS = {
|
|
11
|
+
mini: 2,
|
|
12
|
+
med: 4,
|
|
13
|
+
max: Number.POSITIVE_INFINITY,
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
export async function runTreeCommand({ target, scope, depth, hasCustomDepth, treeViewPreset }) {
|
|
11
17
|
const config = await loadUserConfig();
|
|
12
18
|
const platformMode = resolvePlatformMode(config);
|
|
13
19
|
const scopeOptions = getScopeOptions(platformMode);
|
|
14
20
|
const selectedScope = scope || await chooseSearchScope(scopeOptions);
|
|
15
21
|
const root = resolveScopeRoot(selectedScope, scopeOptions);
|
|
22
|
+
const resolvedTreeView = resolveTreeView({ depth, hasCustomDepth, treeViewPreset, config });
|
|
16
23
|
|
|
17
24
|
if (!root) {
|
|
18
25
|
throw new Error(`Alcance no valido: ${selectedScope}`);
|
|
@@ -57,9 +64,29 @@ export async function runTreeCommand({ target, scope, depth }) {
|
|
|
57
64
|
const report = await buildTreeReport({
|
|
58
65
|
root: resolvedTarget,
|
|
59
66
|
label: cleanTarget,
|
|
60
|
-
depth,
|
|
67
|
+
depth: resolvedTreeView.depth,
|
|
61
68
|
limit: 120,
|
|
62
69
|
});
|
|
63
70
|
|
|
64
|
-
console.log(formatTreeReport(
|
|
71
|
+
console.log(formatTreeReport({
|
|
72
|
+
...report,
|
|
73
|
+
depthLabel: resolvedTreeView.label,
|
|
74
|
+
}));
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export function resolveTreeView({ depth, hasCustomDepth, treeViewPreset, config }) {
|
|
78
|
+
if (hasCustomDepth) {
|
|
79
|
+
return {
|
|
80
|
+
depth,
|
|
81
|
+
label: `manual (${depth})`,
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
const preset = treeViewPreset || config.ui?.treeViewPreset || 'max';
|
|
86
|
+
const normalizedPreset = TREE_DEPTH_PRESETS[preset] ? preset : 'max';
|
|
87
|
+
|
|
88
|
+
return {
|
|
89
|
+
depth: TREE_DEPTH_PRESETS[normalizedPreset],
|
|
90
|
+
label: normalizedPreset,
|
|
91
|
+
};
|
|
65
92
|
}
|
package/src/core/args.js
CHANGED
|
@@ -7,6 +7,8 @@ export function parseArgs(argv) {
|
|
|
7
7
|
target: '',
|
|
8
8
|
scope: '',
|
|
9
9
|
depth: 2,
|
|
10
|
+
hasCustomDepth: false,
|
|
11
|
+
treeViewPreset: '',
|
|
10
12
|
};
|
|
11
13
|
|
|
12
14
|
for (let index = 0; index < argv.length; index += 1) {
|
|
@@ -47,6 +49,26 @@ export function parseArgs(argv) {
|
|
|
47
49
|
continue;
|
|
48
50
|
}
|
|
49
51
|
|
|
52
|
+
if (token === 'sessions' || token === 'session' || token === '--sessions') {
|
|
53
|
+
parsed.command = 'sessions';
|
|
54
|
+
const next = argv[index + 1];
|
|
55
|
+
if (next && !next.startsWith('-')) {
|
|
56
|
+
parsed.topic = next;
|
|
57
|
+
index += 1;
|
|
58
|
+
}
|
|
59
|
+
const target = argv[index + 1];
|
|
60
|
+
if (target && !target.startsWith('-')) {
|
|
61
|
+
parsed.target = target;
|
|
62
|
+
index += 1;
|
|
63
|
+
}
|
|
64
|
+
continue;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
if (token === '--sessions-daemon') {
|
|
68
|
+
parsed.command = 'sessions-daemon';
|
|
69
|
+
continue;
|
|
70
|
+
}
|
|
71
|
+
|
|
50
72
|
if (token === '-b' || token === '--buscar' || token === '--search' || token === 'search') {
|
|
51
73
|
parsed.command = 'search';
|
|
52
74
|
const next = argv[index + 1];
|
|
@@ -105,11 +127,27 @@ export function parseArgs(argv) {
|
|
|
105
127
|
const next = argv[index + 1];
|
|
106
128
|
if (next) {
|
|
107
129
|
parsed.depth = normalizeDepth(next);
|
|
130
|
+
parsed.hasCustomDepth = true;
|
|
108
131
|
index += 1;
|
|
109
132
|
}
|
|
110
133
|
continue;
|
|
111
134
|
}
|
|
112
135
|
|
|
136
|
+
if (token === '--mini') {
|
|
137
|
+
parsed.treeViewPreset = 'mini';
|
|
138
|
+
continue;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
if (token === '--med' || token === '--medio') {
|
|
142
|
+
parsed.treeViewPreset = 'med';
|
|
143
|
+
continue;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
if (token === '--max' || token === '--maximo') {
|
|
147
|
+
parsed.treeViewPreset = 'max';
|
|
148
|
+
continue;
|
|
149
|
+
}
|
|
150
|
+
|
|
113
151
|
if (!parsed.pattern && parsed.command === 'search') {
|
|
114
152
|
parsed.pattern = token;
|
|
115
153
|
continue;
|
package/src/core/config.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import fs from 'node:fs/promises';
|
|
2
2
|
import path from 'node:path';
|
|
3
3
|
import { getHomeDirectory, normalizePlatformMode } from '../utils/platform.js';
|
|
4
|
+
import { getDefaultLinuxSetupTheme, normalizeLinuxSetupTheme } from '../utils/prompt-theme.js';
|
|
4
5
|
|
|
5
6
|
const DEFAULT_CONFIG = {
|
|
6
7
|
features: {
|
|
@@ -10,6 +11,8 @@ const DEFAULT_CONFIG = {
|
|
|
10
11
|
},
|
|
11
12
|
ui: {
|
|
12
13
|
promptContextPosition: 'right',
|
|
14
|
+
treeViewPreset: 'max',
|
|
15
|
+
linuxSetup: getDefaultLinuxSetupTheme(),
|
|
13
16
|
},
|
|
14
17
|
runtime: {
|
|
15
18
|
platformMode: 'auto',
|
|
@@ -74,6 +77,19 @@ export async function setPromptContextPosition(position) {
|
|
|
74
77
|
return saveUserConfig(nextConfig);
|
|
75
78
|
}
|
|
76
79
|
|
|
80
|
+
export async function setTreeViewPreset(preset) {
|
|
81
|
+
const config = await loadUserConfig();
|
|
82
|
+
const nextConfig = {
|
|
83
|
+
...config,
|
|
84
|
+
ui: {
|
|
85
|
+
...config.ui,
|
|
86
|
+
treeViewPreset: normalizeTreeViewPreset(preset),
|
|
87
|
+
},
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
return saveUserConfig(nextConfig);
|
|
91
|
+
}
|
|
92
|
+
|
|
77
93
|
export async function setPlatformMode(platformMode) {
|
|
78
94
|
const config = await loadUserConfig();
|
|
79
95
|
const normalized = normalizePlatformMode(platformMode);
|
|
@@ -154,6 +170,8 @@ function mergeConfig(config) {
|
|
|
154
170
|
...DEFAULT_CONFIG.ui,
|
|
155
171
|
...(config.ui || {}),
|
|
156
172
|
promptContextPosition: normalizePromptContextPosition(config.ui?.promptContextPosition),
|
|
173
|
+
treeViewPreset: normalizeTreeViewPreset(config.ui?.treeViewPreset),
|
|
174
|
+
linuxSetup: normalizeLinuxSetupTheme(config.ui?.linuxSetup || {}),
|
|
157
175
|
},
|
|
158
176
|
runtime: {
|
|
159
177
|
...DEFAULT_CONFIG.runtime,
|
|
@@ -174,6 +192,9 @@ function cloneDefaultConfig() {
|
|
|
174
192
|
},
|
|
175
193
|
ui: {
|
|
176
194
|
...DEFAULT_CONFIG.ui,
|
|
195
|
+
linuxSetup: {
|
|
196
|
+
...DEFAULT_CONFIG.ui.linuxSetup,
|
|
197
|
+
},
|
|
177
198
|
},
|
|
178
199
|
runtime: {
|
|
179
200
|
...DEFAULT_CONFIG.runtime,
|
|
@@ -187,3 +208,19 @@ function cloneDefaultConfig() {
|
|
|
187
208
|
function normalizePromptContextPosition(position) {
|
|
188
209
|
return ['right', 'inline', 'off'].includes(position) ? position : DEFAULT_CONFIG.ui.promptContextPosition;
|
|
189
210
|
}
|
|
211
|
+
|
|
212
|
+
export function normalizeTreeViewPreset(preset) {
|
|
213
|
+
if (preset === 'mini') {
|
|
214
|
+
return 'mini';
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
if (preset === 'med' || preset === 'medio') {
|
|
218
|
+
return 'med';
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
if (preset === 'max' || preset === 'maximo') {
|
|
222
|
+
return 'max';
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
return DEFAULT_CONFIG.ui.treeViewPreset;
|
|
226
|
+
}
|
package/src/ui/output.js
CHANGED
|
@@ -20,19 +20,24 @@ export function printHelp(platformMode = '') {
|
|
|
20
20
|
|
|
21
21
|
printBlock('Comandos principales', [
|
|
22
22
|
'dex -m menu visual',
|
|
23
|
+
'dex sessions lista o gestiona sesiones PTY',
|
|
23
24
|
'dex -v version local y publicada',
|
|
24
25
|
'dex -c contexto del proyecto actual',
|
|
25
26
|
'dex -b "archivo" buscar archivos o carpetas',
|
|
26
27
|
'dex -e ls explicar un comando',
|
|
27
|
-
'dex -t src
|
|
28
|
+
'dex -t src --mini arbol guiado de una ruta',
|
|
28
29
|
'dex -i instalar dependencias del proyecto detectado',
|
|
29
30
|
'dex -r abrir modo seguro del proyecto (Py/Node/PHP/Ruby/Go/Rust/Java)',
|
|
30
31
|
'dex -a acceso rapido a Android o Linux',
|
|
31
32
|
]);
|
|
32
33
|
|
|
33
34
|
printBlock('Uso rapido', [
|
|
35
|
+
'dex sessions',
|
|
36
|
+
'dex sessions new dev',
|
|
37
|
+
'dex sessions attach dev',
|
|
34
38
|
'dex -b "*.js" --scope actual',
|
|
35
|
-
'dex -t . --
|
|
39
|
+
'dex -t . --med --scope actual',
|
|
40
|
+
'dex -t proyectos --max --scope actual',
|
|
36
41
|
'dex -c',
|
|
37
42
|
'dex --prompt-context',
|
|
38
43
|
'dex --prompt-project-root',
|
|
@@ -47,6 +52,7 @@ export function printHelp(platformMode = '') {
|
|
|
47
52
|
'-v --version version',
|
|
48
53
|
'-c --context contexto',
|
|
49
54
|
'-m --menu menu',
|
|
55
|
+
'sessions multiplexor PTY de Dex',
|
|
50
56
|
'-b --buscar buscar',
|
|
51
57
|
'-e --explicar explicar',
|
|
52
58
|
'-t --tree arbol',
|
|
@@ -54,7 +60,10 @@ export function printHelp(platformMode = '') {
|
|
|
54
60
|
'-r --seguro shell segura',
|
|
55
61
|
'-a --android Android',
|
|
56
62
|
'-s --scope scope',
|
|
57
|
-
'-d --depth profundidad',
|
|
63
|
+
'-d --depth profundidad manual',
|
|
64
|
+
'--mini vista corta',
|
|
65
|
+
'--med --medio vista media',
|
|
66
|
+
'--max --maximo vista completa',
|
|
58
67
|
]);
|
|
59
68
|
|
|
60
69
|
console.log(dim('Configuracion: ' + getUserConfigPath()));
|
|
@@ -105,7 +114,7 @@ export function formatTreeReport(report) {
|
|
|
105
114
|
|
|
106
115
|
lines.push('Punto : ' + report.label);
|
|
107
116
|
lines.push('Ruta : ' + report.root);
|
|
108
|
-
lines.push('
|
|
117
|
+
lines.push('Vista : ' + (report.depthLabel || report.depth));
|
|
109
118
|
lines.push('Modo : vista guiada de carpetas y archivos');
|
|
110
119
|
lines.push('');
|
|
111
120
|
lines.push('Mapa:');
|
|
@@ -113,8 +122,8 @@ export function formatTreeReport(report) {
|
|
|
113
122
|
lines.push('');
|
|
114
123
|
lines.push('Resumen: ' + report.directories + ' carpetas, ' + report.files + ' archivos visibles.');
|
|
115
124
|
|
|
116
|
-
if (report.truncatedByDepth) {
|
|
117
|
-
lines.push('Nota : hay mas niveles abajo. Prueba con --depth ' + (report.depth + 1) + '.');
|
|
125
|
+
if (report.truncatedByDepth && Number.isFinite(report.depth)) {
|
|
126
|
+
lines.push('Nota : hay mas niveles abajo. Prueba con --med, --max o --depth ' + (report.depth + 1) + '.');
|
|
118
127
|
}
|
|
119
128
|
|
|
120
129
|
if (report.truncatedByLimit) {
|
package/src/utils/fs-tree.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import fs from 'node:fs/promises';
|
|
2
2
|
import path from 'node:path';
|
|
3
|
+
import { detectProjectContext } from './project-context.js';
|
|
3
4
|
|
|
4
5
|
const COLORS = {
|
|
5
6
|
reset: '\x1b[0m',
|
|
@@ -8,7 +9,8 @@ const COLORS = {
|
|
|
8
9
|
};
|
|
9
10
|
|
|
10
11
|
export async function buildTreeReport({ root, label, depth = 2, limit = 120 }) {
|
|
11
|
-
const
|
|
12
|
+
const detectionCache = new Map();
|
|
13
|
+
const lines = [await formatProjectDirectoryLabel(root, `${path.basename(root) || root}/`, detectionCache)];
|
|
12
14
|
const counters = {
|
|
13
15
|
directories: 0,
|
|
14
16
|
files: 0,
|
|
@@ -59,7 +61,7 @@ export async function buildTreeReport({ root, label, depth = 2, limit = 120 }) {
|
|
|
59
61
|
const childPrefix = `${prefix}${isLast ? ' ' : '│ '}`;
|
|
60
62
|
const fullPath = path.join(currentPath, entry.name);
|
|
61
63
|
const label = entry.isDirectory()
|
|
62
|
-
?
|
|
64
|
+
? await formatProjectDirectoryLabel(fullPath, `${entry.name}/`, detectionCache)
|
|
63
65
|
: entry.name;
|
|
64
66
|
|
|
65
67
|
lines.push(`${prefix}${branch}${label}`);
|
|
@@ -89,6 +91,36 @@ export async function buildTreeReport({ root, label, depth = 2, limit = 120 }) {
|
|
|
89
91
|
}
|
|
90
92
|
}
|
|
91
93
|
|
|
94
|
+
async function formatProjectDirectoryLabel(directoryPath, directoryLabel, detectionCache) {
|
|
95
|
+
const decoratedLabel = await decorateDirectoryLabel(directoryPath, directoryLabel, detectionCache);
|
|
96
|
+
return formatDirectoryLabel(decoratedLabel);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
async function decorateDirectoryLabel(directoryPath, directoryLabel, detectionCache) {
|
|
100
|
+
const cached = detectionCache.get(directoryPath);
|
|
101
|
+
if (cached !== undefined) {
|
|
102
|
+
return cached ? `${directoryLabel} ${cached}` : directoryLabel;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
let suffix = '';
|
|
106
|
+
|
|
107
|
+
try {
|
|
108
|
+
const context = await detectProjectContext(directoryPath);
|
|
109
|
+
if (
|
|
110
|
+
context &&
|
|
111
|
+
context.detectionKind !== 'nested-implicit' &&
|
|
112
|
+
path.resolve(context.projectRoot || '') === path.resolve(directoryPath)
|
|
113
|
+
) {
|
|
114
|
+
suffix = `[${context.label}]`;
|
|
115
|
+
}
|
|
116
|
+
} catch {
|
|
117
|
+
suffix = '';
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
detectionCache.set(directoryPath, suffix);
|
|
121
|
+
return suffix ? `${directoryLabel} ${suffix}` : directoryLabel;
|
|
122
|
+
}
|
|
123
|
+
|
|
92
124
|
function formatDirectoryLabel(value) {
|
|
93
125
|
if (!process.stdout.isTTY) {
|
|
94
126
|
return value;
|