@oxis-dev/tessra 2.19.3 → 2.19.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 CHANGED
@@ -4,12 +4,27 @@ Semantic code intelligence engine for AI agents. Tessra indexes your codebase an
4
4
 
5
5
  ## Install
6
6
 
7
- ```bash
7
+ ```sh
8
8
  npm install -g @oxis-dev/tessra
9
9
  ```
10
10
 
11
11
  Requires a valid Tessra license. Get one at [oxis.dev/tessra](https://oxis.dev/tessra).
12
12
 
13
+ On Windows and Linux, Tessra now checks whether the global npm bin directory is available in
14
+ `PATH` during `postinstall`.
15
+
16
+ - Windows: Tessra attempts to repair the user `PATH` automatically if `%APPDATA%\\npm` is missing.
17
+ - Linux: Tessra attempts a safe shell-profile fix when the npm global bin directory lives under
18
+ the user's home directory.
19
+ - If automatic repair is not safe or fails, the installer prints the exact command or path the
20
+ user needs to add manually.
21
+
22
+ After a PATH repair, open a new terminal and run:
23
+
24
+ ```sh
25
+ tessra --version
26
+ ```
27
+
13
28
  ---
14
29
 
15
30
  ## Quick start
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@oxis-dev/tessra",
3
- "version": "2.19.3",
3
+ "version": "2.19.5",
4
4
  "description": "MCP server for AI coding tools and agents. Provides semantic codebase context for Cursor, Claude Code, Codex, Copilot, Antigravity, and CLI workflows without requiring full file uploads.",
5
5
  "license": "UNLICENSED",
6
6
  "author": {
@@ -19,6 +19,9 @@ const VERSION = require('../package.json').version;
19
19
  const BIN_DIR = path.join(__dirname, '..', 'bin');
20
20
  const BIN_PATH_UNIX = path.join(BIN_DIR, 'tessra');
21
21
  const BIN_PATH_WIN = path.join(BIN_DIR, 'tessra.exe');
22
+ const PATH_DELIMITER = process.platform === 'win32' ? ';' : ':';
23
+ const LINUX_PATH_MARKER_START = '# >>> tessra npm bin >>>';
24
+ const LINUX_PATH_MARKER_END = '# <<< tessra npm bin <<<';
22
25
 
23
26
  // ── Detección de plataforma ──────────────────────────────────────────────────
24
27
 
@@ -126,6 +129,258 @@ function extractFromTar(buffer, targetName) {
126
129
  return null;
127
130
  }
128
131
 
132
+ function escapeRegExp(value) {
133
+ return value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
134
+ }
135
+
136
+ function normalizePathForCompare(targetPath) {
137
+ const resolved = path.resolve(targetPath);
138
+ return process.platform === 'win32' ? resolved.toLowerCase() : resolved;
139
+ }
140
+
141
+ function pathContains(targetPath, pathValue) {
142
+ const normalizedTarget = normalizePathForCompare(targetPath);
143
+ return (pathValue || '')
144
+ .split(PATH_DELIMITER)
145
+ .map((entry) => entry.trim())
146
+ .filter(Boolean)
147
+ .some((entry) => normalizePathForCompare(entry) === normalizedTarget);
148
+ }
149
+
150
+ function profileReferencesBinDir(profileContent, binDir) {
151
+ return profileContent.includes(binDir);
152
+ }
153
+
154
+ function inferGlobalPrefix() {
155
+ if (process.env.npm_config_prefix) {
156
+ return process.env.npm_config_prefix;
157
+ }
158
+
159
+ const packageRoot = path.resolve(__dirname, '..');
160
+ const parts = packageRoot.split(path.sep);
161
+ const nodeModulesIndex = parts.lastIndexOf('node_modules');
162
+
163
+ if (nodeModulesIndex === -1) {
164
+ return null;
165
+ }
166
+
167
+ let prefixParts = parts.slice(0, nodeModulesIndex);
168
+ if (prefixParts[prefixParts.length - 1] === 'lib') {
169
+ prefixParts = prefixParts.slice(0, -1);
170
+ }
171
+
172
+ if (prefixParts.length === 0) {
173
+ return null;
174
+ }
175
+
176
+ return prefixParts.join(path.sep);
177
+ }
178
+
179
+ function getGlobalBinDir() {
180
+ const prefix = inferGlobalPrefix();
181
+ if (!prefix) {
182
+ return null;
183
+ }
184
+ return process.platform === 'win32' ? prefix : path.join(prefix, 'bin');
185
+ }
186
+
187
+ function printManualFix(binDir) {
188
+ if (process.platform === 'win32') {
189
+ console.warn(`tessra: agrega esta ruta a tu PATH de usuario: ${binDir}`);
190
+ return;
191
+ }
192
+
193
+ console.warn(`tessra: agrega esta linea a tu shell profile: export PATH="$PATH:${binDir}"`);
194
+ }
195
+
196
+ function repairWindowsUserPath(binDir) {
197
+ const currentUserPath = execFileSync(
198
+ 'powershell.exe',
199
+ ['-NoProfile', '-Command', "[Environment]::GetEnvironmentVariable('Path','User')"],
200
+ { encoding: 'utf8', stdio: ['ignore', 'pipe', 'pipe'] }
201
+ ).trim();
202
+
203
+ if (pathContains(binDir, currentUserPath)) {
204
+ return { changed: false };
205
+ }
206
+
207
+ const nextPath = [currentUserPath, binDir].filter(Boolean).join(';');
208
+ const escapedPath = nextPath.replace(/'/g, "''");
209
+
210
+ execFileSync(
211
+ 'powershell.exe',
212
+ [
213
+ '-NoProfile',
214
+ '-Command',
215
+ `[Environment]::SetEnvironmentVariable('Path', '${escapedPath}', 'User')`,
216
+ ],
217
+ { stdio: ['ignore', 'pipe', 'pipe'] }
218
+ );
219
+
220
+ process.env.PATH = process.env.PATH ? `${process.env.PATH};${binDir}` : binDir;
221
+ return { changed: true };
222
+ }
223
+
224
+ function detectLinuxShellProfile() {
225
+ const home = process.env.HOME;
226
+ if (!home) {
227
+ return null;
228
+ }
229
+
230
+ const shellName = path.basename(process.env.SHELL || '');
231
+ if (shellName === 'bash') {
232
+ return path.join(home, '.bashrc');
233
+ }
234
+ if (shellName === 'zsh') {
235
+ return path.join(home, '.zshrc');
236
+ }
237
+ return path.join(home, '.profile');
238
+ }
239
+
240
+ function detectLinuxShellProfiles() {
241
+ const home = process.env.HOME;
242
+ if (!home) {
243
+ return [];
244
+ }
245
+
246
+ const shellName = path.basename(process.env.SHELL || '');
247
+ const profiles = [];
248
+
249
+ if (shellName === 'bash') {
250
+ profiles.push(path.join(home, '.bashrc'));
251
+ } else if (shellName === 'zsh') {
252
+ profiles.push(path.join(home, '.zshrc'));
253
+ }
254
+
255
+ profiles.push(path.join(home, '.profile'));
256
+
257
+ return [...new Set(profiles)];
258
+ }
259
+
260
+ function upsertLinuxProfilePath(profilePath, binDir) {
261
+ let profileContent = '';
262
+ if (fs.existsSync(profilePath)) {
263
+ profileContent = fs.readFileSync(profilePath, 'utf8');
264
+ }
265
+
266
+ const blockRegex = new RegExp(
267
+ `${escapeRegExp(LINUX_PATH_MARKER_START)}[\\s\\S]*?${escapeRegExp(LINUX_PATH_MARKER_END)}`,
268
+ 'm'
269
+ );
270
+
271
+ if (blockRegex.test(profileContent)) {
272
+ if (profileReferencesBinDir(profileContent, binDir)) {
273
+ return { changed: false, configured: true };
274
+ }
275
+
276
+ const nextContent = profileContent.replace(
277
+ blockRegex,
278
+ `${LINUX_PATH_MARKER_START}\nexport PATH="$PATH:${binDir}"\n${LINUX_PATH_MARKER_END}`
279
+ );
280
+ fs.writeFileSync(profilePath, nextContent);
281
+ return { changed: true, configured: true };
282
+ }
283
+
284
+ if (profileReferencesBinDir(profileContent, binDir)) {
285
+ return { changed: false, configured: true };
286
+ }
287
+
288
+ const prefix = profileContent && !profileContent.endsWith('\n') ? '\n' : '';
289
+ const block =
290
+ `${prefix}${LINUX_PATH_MARKER_START}\n` +
291
+ `export PATH="$PATH:${binDir}"\n` +
292
+ `${LINUX_PATH_MARKER_END}\n`;
293
+ fs.appendFileSync(profilePath, block);
294
+ return { changed: true, configured: true };
295
+ }
296
+
297
+ function repairLinuxShellPath(binDir) {
298
+ const home = process.env.HOME;
299
+ if (!home) {
300
+ return { changed: false, reason: 'missing-home' };
301
+ }
302
+
303
+ const profilePaths = detectLinuxShellProfiles();
304
+ if (profilePaths.length === 0) {
305
+ return { changed: false, reason: 'missing-profile' };
306
+ }
307
+
308
+ const changedProfiles = [];
309
+ const configuredProfiles = [];
310
+
311
+ for (const profilePath of profilePaths) {
312
+ const result = upsertLinuxProfilePath(profilePath, binDir);
313
+ if (result.configured) {
314
+ configuredProfiles.push(profilePath);
315
+ }
316
+ if (result.changed) {
317
+ changedProfiles.push(profilePath);
318
+ }
319
+ }
320
+
321
+ process.env.PATH = process.env.PATH ? `${process.env.PATH}:${binDir}` : binDir;
322
+ return {
323
+ changed: changedProfiles.length > 0,
324
+ configured: configuredProfiles.length > 0,
325
+ profilePath: detectLinuxShellProfile() || profilePaths[0],
326
+ profilePaths: configuredProfiles,
327
+ };
328
+ }
329
+
330
+ function ensureGlobalBinOnPath() {
331
+ const binDir = getGlobalBinDir();
332
+ if (!binDir) {
333
+ console.warn('tessra: no se pudo inferir el directorio global de npm para verificar PATH.');
334
+ return;
335
+ }
336
+
337
+ if (pathContains(binDir, process.env.PATH || '')) {
338
+ console.log(`tessra: comando global disponible en PATH (${binDir}).`);
339
+ return;
340
+ }
341
+
342
+ if (process.platform === 'win32') {
343
+ try {
344
+ const result = repairWindowsUserPath(binDir);
345
+ if (result.changed) {
346
+ console.log('tessra: PATH reparado en Windows.');
347
+ } else {
348
+ console.log('tessra: PATH de usuario ya estaba configurado en Windows.');
349
+ }
350
+ console.log('tessra: abre una PowerShell nueva y ejecuta: tessra --version');
351
+ return;
352
+ } catch (err) {
353
+ console.warn('tessra: no se pudo reparar PATH automaticamente en Windows.');
354
+ console.warn(`tessra: ${err.message}`);
355
+ printManualFix(binDir);
356
+ return;
357
+ }
358
+ }
359
+
360
+ if (process.platform === 'linux') {
361
+ try {
362
+ const result = repairLinuxShellPath(binDir);
363
+ if (result.changed) {
364
+ console.log(`tessra: PATH reparado en Linux via ${result.profilePaths.join(', ')}.`);
365
+ console.log(`tessra: para usarlo en esta terminal ejecuta: source ${result.profilePath}`);
366
+ console.log('tessra: en una terminal nueva ya deberia reconocer: tessra --version');
367
+ } else if (result.configured) {
368
+ console.log(`tessra: PATH ya configurado en Linux via ${result.profilePaths.join(', ')}.`);
369
+ console.log(`tessra: si esta terminal no lo ve aun, ejecuta: source ${result.profilePath}`);
370
+ } else {
371
+ console.warn('tessra: no se aplico auto-fix de PATH en Linux.');
372
+ printManualFix(binDir);
373
+ }
374
+ return;
375
+ } catch (err) {
376
+ console.warn('tessra: no se pudo reparar PATH automaticamente en Linux.');
377
+ console.warn(`tessra: ${err.message}`);
378
+ printManualFix(binDir);
379
+ return;
380
+ }
381
+ }
382
+ }
383
+
129
384
  // ── Main ─────────────────────────────────────────────────────────────────────
130
385
 
131
386
  async function main() {
@@ -167,6 +422,8 @@ async function main() {
167
422
  // --version puede fallar si el binario requiere inicialización — no es fatal
168
423
  }
169
424
 
425
+ ensureGlobalBinOnPath();
426
+
170
427
  console.log(`tessra: binario instalado correctamente en ${dest}`);
171
428
  }
172
429