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.
@@ -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
+ }
@@ -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
- export async function runTreeCommand({ target, scope, depth }) {
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(report));
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;
@@ -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 arbol guiado de una ruta',
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 . --depth 3 --scope actual',
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('Prof. : ' + report.depth);
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) {
@@ -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 lines = [formatDirectoryLabel(`${path.basename(root) || root}/`)];
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
- ? formatDirectoryLabel(`${entry.name}/`)
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;