@gramatr/client 0.6.1 → 0.6.2

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/bin/install.ts CHANGED
@@ -20,6 +20,11 @@ import { createInterface } from 'readline';
20
20
  import { buildClaudeHooksFile } from '../core/install.ts';
21
21
  import { VERSION } from '../core/version.ts';
22
22
  import { resolveAuthToken } from '../core/auth.ts';
23
+ import {
24
+ sanitizeLegacyEnvBlock,
25
+ detectShellRcLegacyEnv,
26
+ formatShellRcLegacyWarning,
27
+ } from '../core/migration.ts';
23
28
 
24
29
  // ── Constants ──
25
30
 
@@ -513,8 +518,8 @@ function updateClaudeSettings(url: string, token: string): void {
513
518
 
514
519
  const settings = readJson(CLAUDE_SETTINGS);
515
520
 
516
- // Env vars
517
- settings.env = settings.env || {};
521
+ // Env vars — sanitize legacy GMTR_* keys before writing (v0.6.2+ fix)
522
+ settings.env = sanitizeLegacyEnvBlock(settings.env);
518
523
  settings.env.GRAMATR_DIR = CLIENT_DIR;
519
524
  settings.env.GRAMATR_URL = url;
520
525
  settings.env.PATH = `${HOME}/.gramatr/bin:/usr/local/bin:/usr/bin:/bin`;
@@ -584,7 +589,8 @@ function registerMcpServer(url: string, token: string): void {
584
589
 
585
590
  // Register in ~/.claude.json
586
591
  mergeJson(CLAUDE_JSON, (data) => {
587
- data.env = data.env || {};
592
+ // Sanitize legacy GMTR_* keys before writing (v0.6.2+ fix)
593
+ data.env = sanitizeLegacyEnvBlock(data.env);
588
594
  if (token) data.env.GRAMATR_TOKEN = token;
589
595
  delete data.env.AIOS_MCP_TOKEN;
590
596
 
@@ -737,6 +743,10 @@ async function main(): Promise<void> {
737
743
  log(' 2. Already authenticated (token found in ~/.gramatr.json)');
738
744
  }
739
745
  log('');
746
+
747
+ // v0.6.2+: detect legacy GMTR_* exports in user shell rc files and warn
748
+ const rcHits = detectShellRcLegacyEnv(HOME);
749
+ if (rcHits.length > 0) log(formatShellRcLegacyWarning(rcHits));
740
750
  } else {
741
751
  log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
742
752
  log(' Install completed with warnings');
package/codex/install.ts CHANGED
@@ -9,6 +9,10 @@ import {
9
9
  mergeHooksFile,
10
10
  upsertManagedBlock,
11
11
  } from './lib/codex-install-utils.ts';
12
+ import {
13
+ detectShellRcLegacyEnv,
14
+ formatShellRcLegacyWarning,
15
+ } from '../core/migration.ts';
12
16
 
13
17
  const START_MARKER = '<!-- GMTR-CODEX-START -->';
14
18
  const END_MARKER = '<!-- GMTR-CODEX-END -->';
@@ -107,6 +111,10 @@ export function main(): void {
107
111
  log('');
108
112
  log('Codex installer complete.');
109
113
  log('Restart Codex or start a new session to load the updated hook configuration.');
114
+
115
+ // v0.6.2+: detect legacy GMTR_* exports in shell rc and warn
116
+ const rcHits = detectShellRcLegacyEnv(home);
117
+ if (rcHits.length > 0) log(formatShellRcLegacyWarning(rcHits));
110
118
  }
111
119
 
112
120
  // Run directly when executed as a script
package/core/migration.ts CHANGED
@@ -308,6 +308,33 @@ function stripLegacyEntriesFromHookEvent(value: unknown): unknown {
308
308
  .filter(Boolean);
309
309
  }
310
310
 
311
+ /**
312
+ * Env-var key prefixes that were used by earlier gramatr versions and are
313
+ * now either renamed or removed. Any key matching one of these prefixes that
314
+ * survives in a user's config file across an upgrade is a legacy leak and
315
+ * must be scrubbed before we write the new env block.
316
+ *
317
+ * Issue: v0.6.0 renamed GMTR_* → GRAMATR_* but the installer's env merge
318
+ * only SET new keys without removing old ones. Users upgrading from v0.5.x
319
+ * kept the stale GMTR_DIR, GMTR_URL, GMTR_TOKEN in their config files
320
+ * forever. v0.6.2 fixes this by sanitizing on every install.
321
+ */
322
+ export const LEGACY_ENV_KEY_PREFIXES: ReadonlyArray<string> = ['GMTR_'];
323
+
324
+ /**
325
+ * Strip any env-var key matching a legacy prefix. Returns a new object;
326
+ * input is not mutated. Safe on null/undefined (returns {}).
327
+ */
328
+ export function sanitizeLegacyEnvBlock(env: unknown): JsonObject {
329
+ if (!isRecord(env)) return {};
330
+ const next: JsonObject = {};
331
+ for (const [key, value] of Object.entries(env)) {
332
+ if (LEGACY_ENV_KEY_PREFIXES.some((prefix) => key.startsWith(prefix))) continue;
333
+ next[key] = value;
334
+ }
335
+ return next;
336
+ }
337
+
311
338
  export function sanitizeClaudeSettings(
312
339
  settings: JsonObject,
313
340
  clientDir: string,
@@ -326,14 +353,103 @@ export function sanitizeClaudeSettings(
326
353
  }
327
354
 
328
355
  next.hooks = hooks;
356
+ // Sanitize legacy env keys (v0.6.2+)
357
+ next.env = sanitizeLegacyEnvBlock(next.env);
329
358
  return next;
330
359
  }
331
360
 
332
361
  export function sanitizeClaudeJson(claudeJson: JsonObject): JsonObject {
333
- if (!isRecord(claudeJson.mcpServers)) return claudeJson;
334
- const mcpServers = { ...claudeJson.mcpServers };
335
- delete mcpServers.aios;
336
- return { ...claudeJson, mcpServers };
362
+ const next = { ...claudeJson };
363
+ if (isRecord(claudeJson.mcpServers)) {
364
+ const mcpServers = { ...claudeJson.mcpServers };
365
+ delete mcpServers.aios;
366
+ next.mcpServers = mcpServers;
367
+ }
368
+ // Sanitize legacy env keys (v0.6.2+)
369
+ next.env = sanitizeLegacyEnvBlock(claudeJson.env);
370
+ return next;
371
+ }
372
+
373
+ // ── Shell rc legacy env detection (v0.6.2+) ──────────────────────────────
374
+ //
375
+ // Users who added `export GMTR_TOKEN=...` lines to their shell rc files
376
+ // before v0.6.0 will keep those vars live across every new shell until they
377
+ // manually edit the files. Our installer cannot mutate user dotfiles
378
+ // without permission, but it CAN detect and warn.
379
+
380
+ export interface ShellRcLegacyHit {
381
+ file: string;
382
+ lineNumber: number;
383
+ line: string;
384
+ variableName: string;
385
+ }
386
+
387
+ const SHELL_RC_CANDIDATES: ReadonlyArray<string> = [
388
+ '.bashrc',
389
+ '.bash_profile',
390
+ '.zshrc',
391
+ '.zprofile',
392
+ '.profile',
393
+ ];
394
+
395
+ const LEGACY_EXPORT_RE = /^\s*export\s+(GMTR_[A-Z_]+)=/;
396
+
397
+ /**
398
+ * Scan shell rc files for `export GMTR_*=...` lines. Returns a list of hits,
399
+ * each with file path, line number, raw line, and the variable name. Never
400
+ * mutates any file — read-only detection.
401
+ */
402
+ export function detectShellRcLegacyEnv(homeDir: string): ShellRcLegacyHit[] {
403
+ const out: ShellRcLegacyHit[] = [];
404
+ for (const basename of SHELL_RC_CANDIDATES) {
405
+ const path = `${homeDir}/${basename}`;
406
+ if (!existsSync(path)) continue;
407
+ let content: string;
408
+ try {
409
+ content = readFileSync(path, 'utf8');
410
+ } catch {
411
+ continue;
412
+ }
413
+ const lines = content.split(/\r?\n/);
414
+ for (let i = 0; i < lines.length; i++) {
415
+ const match = lines[i].match(LEGACY_EXPORT_RE);
416
+ if (match) {
417
+ out.push({
418
+ file: path,
419
+ lineNumber: i + 1,
420
+ line: lines[i],
421
+ variableName: match[1],
422
+ });
423
+ }
424
+ }
425
+ }
426
+ return out;
427
+ }
428
+
429
+ /**
430
+ * Format shell-rc legacy hits as a human-readable block of warning text.
431
+ * Returns an empty string if there are no hits. Writes no files.
432
+ */
433
+ export function formatShellRcLegacyWarning(hits: ShellRcLegacyHit[]): string {
434
+ if (hits.length === 0) return '';
435
+ const lines: string[] = [];
436
+ lines.push('');
437
+ lines.push('⚠ Legacy GMTR_* exports detected in your shell rc files:');
438
+ lines.push('');
439
+ for (const hit of hits) {
440
+ lines.push(` ${hit.file}:${hit.lineNumber}`);
441
+ lines.push(` ${hit.line.trim()}`);
442
+ }
443
+ const uniqueVars = Array.from(new Set(hits.map((h) => h.variableName)));
444
+ lines.push('');
445
+ lines.push(' These vars are no longer read by gramatr (since v0.6.0) but');
446
+ lines.push(' remain in your shell environment on every login. To clean up:');
447
+ lines.push('');
448
+ lines.push(' 1. Edit the files above and delete the matching export lines');
449
+ lines.push(` 2. Run in each open terminal: unset ${uniqueVars.join(' ')}`);
450
+ lines.push(' 3. Or just start a fresh shell session');
451
+ lines.push('');
452
+ return lines.join('\n');
337
453
  }
338
454
 
339
455
  export function findStaleArtifacts(
package/gemini/install.ts CHANGED
@@ -17,6 +17,10 @@ import {
17
17
  getGramatrExtensionDir,
18
18
  readStoredApiKey,
19
19
  } from './lib/gemini-install-utils.ts';
20
+ import {
21
+ detectShellRcLegacyEnv,
22
+ formatShellRcLegacyWarning,
23
+ } from '../core/migration.ts';
20
24
 
21
25
  function log(message: string): void {
22
26
  process.stdout.write(`${message}\n`);
@@ -272,6 +276,10 @@ export async function main(): Promise<void> {
272
276
  log('');
273
277
  log(' Restart Gemini CLI to load the extension.');
274
278
  log('');
279
+
280
+ // v0.6.2+: detect legacy GMTR_* exports in shell rc and warn
281
+ const rcHits = detectShellRcLegacyEnv(home);
282
+ if (rcHits.length > 0) log(formatShellRcLegacyWarning(rcHits));
275
283
  }
276
284
 
277
285
  // Run directly when executed as a script
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gramatr/client",
3
- "version": "0.6.1",
3
+ "version": "0.6.2",
4
4
  "publishConfig": {
5
5
  "access": "public"
6
6
  },