@gramatr/client 0.6.0 → 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/add-api-key.ts +4 -4
- package/bin/clean-legacy-install.ts +1 -1
- package/bin/clear-creds.ts +8 -8
- package/bin/gmtr-login.ts +8 -8
- package/bin/gramatr.ts +4 -4
- package/bin/install.ts +22 -12
- package/bin/lib/config.ts +4 -4
- package/bin/logout.ts +5 -5
- package/bin/uninstall.ts +14 -14
- package/codex/install.ts +9 -1
- package/core/auth.ts +6 -6
- package/core/migration.ts +120 -4
- package/core/version.ts +2 -2
- package/gemini/install.ts +16 -8
- package/gemini/lib/gemini-install-utils.ts +2 -2
- package/hooks/StopOrchestrator.hook.ts +1 -1
- package/hooks/lib/gmtr-hook-utils.ts +9 -9
- package/hooks/lib/identity.ts +2 -2
- package/hooks/lib/paths.ts +1 -1
- package/hooks/session-start.hook.ts +1 -1
- package/package.json +1 -1
package/bin/add-api-key.ts
CHANGED
|
@@ -8,9 +8,9 @@
|
|
|
8
8
|
* 3. Env-sourced: gramatr add-api-key --from-env GRAMATR_API_KEY
|
|
9
9
|
*
|
|
10
10
|
* The key is validated against the gramatr server before being written
|
|
11
|
-
* to ~/.
|
|
11
|
+
* to ~/.gramatr.json. Use --force to skip server validation when offline.
|
|
12
12
|
*
|
|
13
|
-
* This command is the ONLY way to put an API key into ~/.
|
|
13
|
+
* This command is the ONLY way to put an API key into ~/.gramatr.json.
|
|
14
14
|
* Installers never prompt for API keys — see packages/client/core/auth.ts.
|
|
15
15
|
*/
|
|
16
16
|
|
|
@@ -20,7 +20,7 @@ import { join } from "path";
|
|
|
20
20
|
import { createInterface } from "readline";
|
|
21
21
|
|
|
22
22
|
function gmtrJsonPath(): string {
|
|
23
|
-
return join(process.env.HOME || process.env.USERPROFILE || homedir(), ".
|
|
23
|
+
return join(process.env.HOME || process.env.USERPROFILE || homedir(), ".gramatr.json");
|
|
24
24
|
}
|
|
25
25
|
const SERVER_BASE = (process.env.GRAMATR_URL || "https://api.gramatr.com").replace(/\/mcp\/?$/, "");
|
|
26
26
|
|
|
@@ -59,7 +59,7 @@ function parseArgs(argv: string[]): {
|
|
|
59
59
|
}
|
|
60
60
|
|
|
61
61
|
function showHelp(): void {
|
|
62
|
-
log(`gramatr add-api-key — Add a gramatr API key to ~/.
|
|
62
|
+
log(`gramatr add-api-key — Add a gramatr API key to ~/.gramatr.json
|
|
63
63
|
|
|
64
64
|
Usage:
|
|
65
65
|
gramatr add-api-key Interactive prompt for the key
|
|
@@ -15,7 +15,7 @@ function main(): void {
|
|
|
15
15
|
|
|
16
16
|
const apply = process.argv.includes('--apply');
|
|
17
17
|
const includeOptionalUx = process.env.GRAMATR_ENABLE_OPTIONAL_CLAUDE_UX === '1';
|
|
18
|
-
const clientDir = process.env.GRAMATR_DIR || join(home, '
|
|
18
|
+
const clientDir = process.env.GRAMATR_DIR || join(home, '.gramatr');
|
|
19
19
|
runLegacyMigration({
|
|
20
20
|
homeDir: home,
|
|
21
21
|
clientDir,
|
package/bin/clear-creds.ts
CHANGED
|
@@ -2,13 +2,13 @@
|
|
|
2
2
|
/**
|
|
3
3
|
* gramatr clear-creds — Nuke ALL stored gramatr credentials.
|
|
4
4
|
*
|
|
5
|
-
* Stronger than `gramatr logout` (which only removes ~/.
|
|
5
|
+
* Stronger than `gramatr logout` (which only removes ~/.gramatr.json).
|
|
6
6
|
* This sweeps every credential location in the resolveAuthToken chain
|
|
7
7
|
* (core/auth.ts), so the next install/login is forced through OAuth.
|
|
8
8
|
*
|
|
9
9
|
* Locations cleared:
|
|
10
|
-
* 1. ~/.
|
|
11
|
-
* 2.
|
|
10
|
+
* 1. ~/.gramatr.json — deleted entirely
|
|
11
|
+
* 2. ~/.gramatr/settings.json `auth.api_key` — stripped, file preserved
|
|
12
12
|
*
|
|
13
13
|
* Env vars (GRAMATR_API_KEY, GRAMATR_TOKEN) cannot be cleared from a child
|
|
14
14
|
* process — they live in the parent shell. We detect them and warn.
|
|
@@ -24,11 +24,11 @@ function getHome(): string {
|
|
|
24
24
|
}
|
|
25
25
|
|
|
26
26
|
function gmtrJsonPath(): string {
|
|
27
|
-
return join(getHome(), ".
|
|
27
|
+
return join(getHome(), ".gramatr.json");
|
|
28
28
|
}
|
|
29
29
|
|
|
30
30
|
function legacySettingsPath(): string {
|
|
31
|
-
const gmtrDir = process.env.GRAMATR_DIR || join(getHome(), "
|
|
31
|
+
const gmtrDir = process.env.GRAMATR_DIR || join(getHome(), ".gramatr");
|
|
32
32
|
return join(gmtrDir, "settings.json");
|
|
33
33
|
}
|
|
34
34
|
|
|
@@ -40,7 +40,7 @@ function showHelp(): void {
|
|
|
40
40
|
log(`gramatr clear-creds — Remove every stored gramatr credential
|
|
41
41
|
|
|
42
42
|
Usage:
|
|
43
|
-
gramatr clear-creds Sweep ~/.
|
|
43
|
+
gramatr clear-creds Sweep ~/.gramatr.json + auth.api_key from gmtr-client/settings.json
|
|
44
44
|
|
|
45
45
|
After running, the next install or login will be forced through OAuth.
|
|
46
46
|
|
|
@@ -61,14 +61,14 @@ export function clearAll(): ClearResult {
|
|
|
61
61
|
envVarsSet: [],
|
|
62
62
|
};
|
|
63
63
|
|
|
64
|
-
// 1. ~/.
|
|
64
|
+
// 1. ~/.gramatr.json — delete entirely
|
|
65
65
|
const gj = gmtrJsonPath();
|
|
66
66
|
if (existsSync(gj)) {
|
|
67
67
|
unlinkSync(gj);
|
|
68
68
|
result.removedGmtrJson = true;
|
|
69
69
|
}
|
|
70
70
|
|
|
71
|
-
// 2.
|
|
71
|
+
// 2. ~/.gramatr/settings.json — strip auth.api_key, preserve rest
|
|
72
72
|
const ls = legacySettingsPath();
|
|
73
73
|
if (existsSync(ls)) {
|
|
74
74
|
try {
|
package/bin/gmtr-login.ts
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* gmtr-login — Authenticate with the gramatr server
|
|
4
4
|
*
|
|
5
5
|
* Opens the grāmatr dashboard login flow, captures a Firebase ID token
|
|
6
|
-
* on localhost, and stores it in ~/.
|
|
6
|
+
* on localhost, and stores it in ~/.gramatr.json.
|
|
7
7
|
*
|
|
8
8
|
* Usage:
|
|
9
9
|
* bun gmtr-login.ts # Interactive browser login via Firebase dashboard
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
* bun gmtr-login.ts --status # Check current auth status
|
|
12
12
|
* bun gmtr-login.ts --logout # Remove stored credentials
|
|
13
13
|
*
|
|
14
|
-
* Token is stored in ~/.
|
|
14
|
+
* Token is stored in ~/.gramatr.json under the "token" key.
|
|
15
15
|
* The GMTRPromptEnricher hook reads this on every prompt.
|
|
16
16
|
*/
|
|
17
17
|
|
|
@@ -23,7 +23,7 @@ import { createServer, type IncomingMessage, type ServerResponse } from 'http';
|
|
|
23
23
|
// ── Config ──
|
|
24
24
|
|
|
25
25
|
const HOME = process.env.HOME || process.env.USERPROFILE || '';
|
|
26
|
-
const CONFIG_PATH = join(HOME, '.
|
|
26
|
+
const CONFIG_PATH = join(HOME, '.gramatr.json');
|
|
27
27
|
const DEFAULT_SERVER = process.env.GRAMATR_URL || 'https://api.gramatr.com/mcp';
|
|
28
28
|
// Strip /mcp suffix to get base URL
|
|
29
29
|
const SERVER_BASE = DEFAULT_SERVER.replace(/\/mcp\/?$/, '');
|
|
@@ -317,7 +317,7 @@ async function logout(): Promise<void> {
|
|
|
317
317
|
delete config.token_expires;
|
|
318
318
|
delete config.authenticated_at;
|
|
319
319
|
writeConfig(config);
|
|
320
|
-
console.log('\n ✓ Logged out. Token removed from ~/.
|
|
320
|
+
console.log('\n ✓ Logged out. Token removed from ~/.gramatr.json\n');
|
|
321
321
|
}
|
|
322
322
|
|
|
323
323
|
async function loginWithToken(token: string): Promise<void> {
|
|
@@ -330,7 +330,7 @@ async function loginWithToken(token: string): Promise<void> {
|
|
|
330
330
|
config.token_type = token.startsWith('aios_sk_') || token.startsWith('gramatr_sk_') ? 'api_key' : 'oauth';
|
|
331
331
|
config.authenticated_at = new Date().toISOString();
|
|
332
332
|
writeConfig(config);
|
|
333
|
-
console.log(' ✓ Token valid. Saved to ~/.
|
|
333
|
+
console.log(' ✓ Token valid. Saved to ~/.gramatr.json');
|
|
334
334
|
console.log(' gramatr intelligence is now active.\n');
|
|
335
335
|
} else {
|
|
336
336
|
console.log(` ✗ Token rejected: ${result.error}`);
|
|
@@ -396,7 +396,7 @@ async function loginBrowser(): Promise<void> {
|
|
|
396
396
|
|
|
397
397
|
console.log('');
|
|
398
398
|
console.log(' ✓ Authenticated successfully');
|
|
399
|
-
console.log(' Token saved to ~/.
|
|
399
|
+
console.log(' Token saved to ~/.gramatr.json');
|
|
400
400
|
console.log(' gramatr intelligence is now active.\n');
|
|
401
401
|
return;
|
|
402
402
|
} catch (e: any) {
|
|
@@ -503,7 +503,7 @@ async function loginBrowser(): Promise<void> {
|
|
|
503
503
|
|
|
504
504
|
console.log('');
|
|
505
505
|
console.log(' ✓ Authenticated successfully');
|
|
506
|
-
console.log(' Token saved to ~/.
|
|
506
|
+
console.log(' Token saved to ~/.gramatr.json');
|
|
507
507
|
console.log(' gramatr intelligence is now active.\n');
|
|
508
508
|
} catch (e: any) {
|
|
509
509
|
console.log(`\n ✗ Authentication failed: ${e.message}\n`);
|
|
@@ -570,7 +570,7 @@ export async function main(): Promise<void> {
|
|
|
570
570
|
gmtr-login --logout Remove stored credentials
|
|
571
571
|
gmtr-login --help Show this help
|
|
572
572
|
|
|
573
|
-
Token storage: ~/.
|
|
573
|
+
Token storage: ~/.gramatr.json
|
|
574
574
|
Server: ${SERVER_BASE}
|
|
575
575
|
Dashboard: ${DASHBOARD_BASE}
|
|
576
576
|
`);
|
package/bin/gramatr.ts
CHANGED
|
@@ -205,7 +205,7 @@ function installTarget(targetId: IntegrationTargetId): void {
|
|
|
205
205
|
|
|
206
206
|
function migrate(apply: boolean, deep: boolean = false): void {
|
|
207
207
|
const homeDir = homedir();
|
|
208
|
-
const clientDir = process.env.GRAMATR_DIR || join(homeDir, '
|
|
208
|
+
const clientDir = process.env.GRAMATR_DIR || join(homeDir, '.gramatr');
|
|
209
209
|
runLegacyMigration({
|
|
210
210
|
homeDir,
|
|
211
211
|
clientDir,
|
|
@@ -220,7 +220,7 @@ function migrate(apply: boolean, deep: boolean = false): void {
|
|
|
220
220
|
}
|
|
221
221
|
|
|
222
222
|
function doctor(): void {
|
|
223
|
-
const gmtrDir = process.env.GRAMATR_DIR || join(homedir(), '
|
|
223
|
+
const gmtrDir = process.env.GRAMATR_DIR || join(homedir(), '.gramatr');
|
|
224
224
|
const stale = findStaleArtifacts(homedir(), gmtrDir, existsSync);
|
|
225
225
|
for (const line of formatDoctorLines(detectTargets(), gmtrDir, existsSync(gmtrDir), stale)) log(line);
|
|
226
226
|
renderRemoteGuidance();
|
|
@@ -228,7 +228,7 @@ function doctor(): void {
|
|
|
228
228
|
|
|
229
229
|
function upgrade(): void {
|
|
230
230
|
const homeDir = homedir();
|
|
231
|
-
const clientDir = process.env.GRAMATR_DIR || join(homeDir, '
|
|
231
|
+
const clientDir = process.env.GRAMATR_DIR || join(homeDir, '.gramatr');
|
|
232
232
|
const stale = findStaleArtifacts(homeDir, clientDir, existsSync);
|
|
233
233
|
if (stale.length > 0) {
|
|
234
234
|
log('Cleaning stale legacy artifacts before upgrade...');
|
|
@@ -344,7 +344,7 @@ function main(): void {
|
|
|
344
344
|
log(' install [target] Install gramatr (claude-code, codex, gemini-cli, all)');
|
|
345
345
|
log(' login Authenticate with the gramatr server (OAuth)');
|
|
346
346
|
log(' add-api-key Add an API key explicitly (interactive / piped / --from-env)');
|
|
347
|
-
log(' logout Clear session token (~/.
|
|
347
|
+
log(' logout Clear session token (~/.gramatr.json)');
|
|
348
348
|
log(' clear-creds Nuke ALL stored credentials, force OAuth on next login');
|
|
349
349
|
log(' detect Show detected CLI platforms');
|
|
350
350
|
log(' doctor Check installation health');
|
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
|
|
|
@@ -27,8 +32,8 @@ const HOME = process.env.HOME || process.env.USERPROFILE || '';
|
|
|
27
32
|
const CLAUDE_DIR = join(HOME, '.claude');
|
|
28
33
|
const CLAUDE_SETTINGS = join(CLAUDE_DIR, 'settings.json');
|
|
29
34
|
const CLAUDE_JSON = join(HOME, '.claude.json');
|
|
30
|
-
const CLIENT_DIR = join(HOME, '
|
|
31
|
-
const GMTR_JSON = join(HOME, '.
|
|
35
|
+
const CLIENT_DIR = join(HOME, '.gramatr');
|
|
36
|
+
const GMTR_JSON = join(HOME, '.gramatr.json');
|
|
32
37
|
const SCRIPT_DIR = dirname(dirname(resolve(import.meta.filename || __filename)));
|
|
33
38
|
const DEFAULT_URL = 'https://api.gramatr.com/mcp';
|
|
34
39
|
|
|
@@ -398,13 +403,13 @@ async function handleAuth(legacyToken: string): Promise<{ url: string; token: st
|
|
|
398
403
|
const url = await prompt('gramatr server URL', DEFAULT_URL) || DEFAULT_URL;
|
|
399
404
|
|
|
400
405
|
// Legacy aios token migration: if we cherry-picked a token from a prior
|
|
401
|
-
// aios install and there is no current ~/.
|
|
406
|
+
// aios install and there is no current ~/.gramatr.json token, seed it so the
|
|
402
407
|
// shared resolver picks it up.
|
|
403
408
|
if (legacyToken && !existsSync(GMTR_JSON)) {
|
|
404
409
|
try {
|
|
405
410
|
writeFileSync(GMTR_JSON, `${JSON.stringify({ token: legacyToken }, null, 2)}\n`, 'utf8');
|
|
406
411
|
try { chmodSync(GMTR_JSON, 0o600); } catch { /* ok */ }
|
|
407
|
-
log('OK Seeded ~/.
|
|
412
|
+
log('OK Seeded ~/.gramatr.json from legacy aios installation token');
|
|
408
413
|
} catch { /* ignore */ }
|
|
409
414
|
}
|
|
410
415
|
|
|
@@ -513,11 +518,11 @@ 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
|
-
settings.env.PATH = `${HOME}/.
|
|
525
|
+
settings.env.PATH = `${HOME}/.gramatr/bin:/usr/local/bin:/usr/bin:/bin`;
|
|
521
526
|
if (token) settings.env.AIOS_MCP_TOKEN = token;
|
|
522
527
|
|
|
523
528
|
// Identity defaults (don't overwrite)
|
|
@@ -564,7 +569,7 @@ function registerMcpServer(url: string, token: string): void {
|
|
|
564
569
|
const backup = `${CLAUDE_JSON}.backup-${timestamp()}`;
|
|
565
570
|
cpSync(CLAUDE_JSON, backup);
|
|
566
571
|
|
|
567
|
-
// Store token in ~/.
|
|
572
|
+
// Store token in ~/.gramatr.json
|
|
568
573
|
if (token) {
|
|
569
574
|
mergeJson(GMTR_JSON, (data) => ({
|
|
570
575
|
...data,
|
|
@@ -572,10 +577,10 @@ function registerMcpServer(url: string, token: string): void {
|
|
|
572
577
|
token_updated_at: new Date().toISOString(),
|
|
573
578
|
}));
|
|
574
579
|
try { chmodSync(GMTR_JSON, 0o600); } catch { /* ok */ }
|
|
575
|
-
log('OK Token stored in ~/.
|
|
580
|
+
log('OK Token stored in ~/.gramatr.json (canonical source)');
|
|
576
581
|
}
|
|
577
582
|
|
|
578
|
-
// Write tool paths to ~/.
|
|
583
|
+
// Write tool paths to ~/.gramatr.json
|
|
579
584
|
mergeJson(GMTR_JSON, (data) => ({
|
|
580
585
|
...data,
|
|
581
586
|
jq_binary: which('jq') || '',
|
|
@@ -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
|
-
|
|
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
|
|
|
@@ -734,9 +740,13 @@ async function main(): Promise<void> {
|
|
|
734
740
|
if (!token) {
|
|
735
741
|
log(' 2. Run /gmtr-login in Claude Code to authenticate');
|
|
736
742
|
} else {
|
|
737
|
-
log(' 2. Already authenticated (token found in ~/.
|
|
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/bin/lib/config.ts
CHANGED
|
@@ -2,14 +2,14 @@
|
|
|
2
2
|
* gramatr client config resolver (statusline refactor, #495).
|
|
3
3
|
*
|
|
4
4
|
* Single-purpose helper that returns the server URL + auth token with
|
|
5
|
-
* NO reads of ~/.claude* or
|
|
5
|
+
* NO reads of ~/.claude* or ~/.gramatr/settings.json. This is the
|
|
6
6
|
* statusline-specific config path; the richer installer auth lives in
|
|
7
7
|
* core/auth.ts for a reason — this file must stay small and side-effect-free
|
|
8
8
|
* so it can be loaded in a 2s shim without touching disk more than once.
|
|
9
9
|
*
|
|
10
10
|
* Resolution:
|
|
11
|
-
* URL — GRAMATR_URL → ~/.
|
|
12
|
-
* Token —
|
|
11
|
+
* URL — GRAMATR_URL → ~/.gramatr.json.url → https://api.gramatr.com
|
|
12
|
+
* Token — GRAMATR_API_KEY → ~/.gramatr.json.token → null
|
|
13
13
|
*/
|
|
14
14
|
import { existsSync, readFileSync } from 'fs';
|
|
15
15
|
import { homedir } from 'os';
|
|
@@ -27,7 +27,7 @@ function getHome(): string {
|
|
|
27
27
|
}
|
|
28
28
|
|
|
29
29
|
function readGmtrJson(): Record<string, any> | null {
|
|
30
|
-
const path = join(getHome(), '.
|
|
30
|
+
const path = join(getHome(), '.gramatr.json');
|
|
31
31
|
if (!existsSync(path)) return null;
|
|
32
32
|
try {
|
|
33
33
|
return JSON.parse(readFileSync(path, 'utf8'));
|
package/bin/logout.ts
CHANGED
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
/**
|
|
3
3
|
* gramatr logout — Clear stored gramatr credentials (issue #484).
|
|
4
4
|
*
|
|
5
|
-
* Removes ~/.
|
|
6
|
-
* ~/.
|
|
5
|
+
* Removes ~/.gramatr.json. With --keep-backup, renames it to
|
|
6
|
+
* ~/.gramatr.json.bak.<timestamp> instead of deleting.
|
|
7
7
|
*
|
|
8
8
|
* Not-logged-in is not an error: exits 0 with a clean message.
|
|
9
9
|
*/
|
|
@@ -13,7 +13,7 @@ import { homedir } from "os";
|
|
|
13
13
|
import { join } from "path";
|
|
14
14
|
|
|
15
15
|
function gmtrJsonPath(): string {
|
|
16
|
-
return join(process.env.HOME || process.env.USERPROFILE || homedir(), ".
|
|
16
|
+
return join(process.env.HOME || process.env.USERPROFILE || homedir(), ".gramatr.json");
|
|
17
17
|
}
|
|
18
18
|
|
|
19
19
|
function log(msg: string = ""): void {
|
|
@@ -34,8 +34,8 @@ function showHelp(): void {
|
|
|
34
34
|
log(`gramatr logout — Clear stored gramatr credentials
|
|
35
35
|
|
|
36
36
|
Usage:
|
|
37
|
-
gramatr logout Delete ~/.
|
|
38
|
-
gramatr logout --keep-backup Rename to ~/.
|
|
37
|
+
gramatr logout Delete ~/.gramatr.json
|
|
38
|
+
gramatr logout --keep-backup Rename to ~/.gramatr.json.bak.<timestamp> instead`);
|
|
39
39
|
}
|
|
40
40
|
|
|
41
41
|
export function main(argv: string[] = process.argv.slice(2)): number {
|
package/bin/uninstall.ts
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* Usage:
|
|
6
6
|
* bun uninstall.ts # Interactive — confirms each step
|
|
7
7
|
* bun uninstall.ts --yes # Non-interactive — uninstall everything
|
|
8
|
-
* bun uninstall.ts --keep-auth # Keep ~/.
|
|
8
|
+
* bun uninstall.ts --keep-auth # Keep ~/.gramatr.json (preserve login)
|
|
9
9
|
* npx tsx uninstall.ts # Without bun
|
|
10
10
|
*/
|
|
11
11
|
|
|
@@ -97,8 +97,8 @@ async function main(): Promise<void> {
|
|
|
97
97
|
log(' ===================');
|
|
98
98
|
log('');
|
|
99
99
|
|
|
100
|
-
const gmtrClient = join(HOME, '
|
|
101
|
-
const gmtrJson = join(HOME, '.
|
|
100
|
+
const gmtrClient = join(HOME, '.gramatr');
|
|
101
|
+
const gmtrJson = join(HOME, '.gramatr.json');
|
|
102
102
|
const gmtrDotDir = join(HOME, '.gmtr');
|
|
103
103
|
const claudeSettings = join(HOME, '.claude', 'settings.json');
|
|
104
104
|
const claudeJson = join(HOME, '.claude.json');
|
|
@@ -111,9 +111,9 @@ async function main(): Promise<void> {
|
|
|
111
111
|
|
|
112
112
|
// Detect what's installed
|
|
113
113
|
const installed: string[] = [];
|
|
114
|
-
if (existsSync(gmtrClient)) installed.push(
|
|
114
|
+
if (existsSync(gmtrClient)) installed.push(`~/.gramatr (${dirSize(gmtrClient)})`);
|
|
115
115
|
if (existsSync(gmtrDotDir)) installed.push(`~/.gmtr/ (${dirSize(gmtrDotDir)})`);
|
|
116
|
-
if (existsSync(gmtrJson)) installed.push('~/.
|
|
116
|
+
if (existsSync(gmtrJson)) installed.push('~/.gramatr.json');
|
|
117
117
|
if (existsSync(claudeSettings)) installed.push('~/.claude/settings.json (hooks)');
|
|
118
118
|
if (existsSync(claudeJson)) installed.push('~/.claude.json (MCP server)');
|
|
119
119
|
if (existsSync(codexHooks)) installed.push('~/.codex/hooks.json');
|
|
@@ -136,10 +136,10 @@ async function main(): Promise<void> {
|
|
|
136
136
|
|
|
137
137
|
log('');
|
|
138
138
|
|
|
139
|
-
// 1. Remove
|
|
139
|
+
// 1. Remove ~/.gramatr (hooks, bin, skills, agents, tools)
|
|
140
140
|
if (existsSync(gmtrClient)) {
|
|
141
141
|
rmSync(gmtrClient, { recursive: true, force: true });
|
|
142
|
-
log(' OK Removed
|
|
142
|
+
log(' OK Removed ~/.gramatr');
|
|
143
143
|
}
|
|
144
144
|
|
|
145
145
|
// 2. Remove ~/.gmtr/ (bun binary, PATH additions)
|
|
@@ -148,13 +148,13 @@ async function main(): Promise<void> {
|
|
|
148
148
|
log(' OK Removed ~/.gmtr/');
|
|
149
149
|
}
|
|
150
150
|
|
|
151
|
-
// 3. Remove ~/.
|
|
151
|
+
// 3. Remove ~/.gramatr.json (auth token) — unless --keep-auth
|
|
152
152
|
if (existsSync(gmtrJson)) {
|
|
153
153
|
if (KEEP_AUTH) {
|
|
154
|
-
log(' -- Kept ~/.
|
|
154
|
+
log(' -- Kept ~/.gramatr.json (--keep-auth)');
|
|
155
155
|
} else {
|
|
156
156
|
rmSync(gmtrJson);
|
|
157
|
-
log(' OK Removed ~/.
|
|
157
|
+
log(' OK Removed ~/.gramatr.json');
|
|
158
158
|
}
|
|
159
159
|
}
|
|
160
160
|
|
|
@@ -174,14 +174,14 @@ async function main(): Promise<void> {
|
|
|
174
174
|
for (const [event, hooks] of Object.entries(settings.hooks)) {
|
|
175
175
|
if (Array.isArray(hooks)) {
|
|
176
176
|
settings.hooks[event] = (hooks as any[]).filter(
|
|
177
|
-
(h: any) => !h.command?.includes('
|
|
177
|
+
(h: any) => !h.command?.includes('.gramatr') && !h.command?.includes('gramatr')
|
|
178
178
|
);
|
|
179
179
|
if (settings.hooks[event].length === 0) delete settings.hooks[event];
|
|
180
180
|
}
|
|
181
181
|
}
|
|
182
182
|
if (Object.keys(settings.hooks).length === 0) delete settings.hooks;
|
|
183
183
|
}
|
|
184
|
-
if (settings.statusLine?.command?.includes('
|
|
184
|
+
if (settings.statusLine?.command?.includes('.gramatr')) {
|
|
185
185
|
delete settings.statusLine;
|
|
186
186
|
}
|
|
187
187
|
writeFileSync(claudeSettings, JSON.stringify(settings, null, 2) + '\n');
|
|
@@ -242,7 +242,7 @@ async function main(): Promise<void> {
|
|
|
242
242
|
for (const [event, eventHooks] of Object.entries(hooks.hooks)) {
|
|
243
243
|
if (Array.isArray(eventHooks)) {
|
|
244
244
|
hooks.hooks[event] = (eventHooks as any[]).filter(
|
|
245
|
-
(h: any) => !h.command?.includes('
|
|
245
|
+
(h: any) => !h.command?.includes('.gramatr') && !h.command?.includes('gramatr')
|
|
246
246
|
);
|
|
247
247
|
if (hooks.hooks[event].length === 0) delete hooks.hooks[event];
|
|
248
248
|
}
|
|
@@ -278,7 +278,7 @@ async function main(): Promise<void> {
|
|
|
278
278
|
log(' Uninstall complete.');
|
|
279
279
|
log(' Restart Claude Code / Codex / Gemini CLI to apply changes.');
|
|
280
280
|
if (KEEP_AUTH) {
|
|
281
|
-
log(' Auth token preserved in ~/.
|
|
281
|
+
log(' Auth token preserved in ~/.gramatr.json (use --token with next install).');
|
|
282
282
|
}
|
|
283
283
|
log('');
|
|
284
284
|
}
|
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 -->';
|
|
@@ -47,7 +51,7 @@ export function main(): void {
|
|
|
47
51
|
throw new Error('HOME is not set');
|
|
48
52
|
}
|
|
49
53
|
|
|
50
|
-
const gmtrDir = process.env.GRAMATR_DIR || join(home, '
|
|
54
|
+
const gmtrDir = process.env.GRAMATR_DIR || join(home, '.gramatr');
|
|
51
55
|
const codexHome = join(home, '.codex');
|
|
52
56
|
const hooksPath = join(codexHome, 'hooks.json');
|
|
53
57
|
const configPath = join(codexHome, 'config.toml');
|
|
@@ -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/auth.ts
CHANGED
|
@@ -8,8 +8,8 @@
|
|
|
8
8
|
* Resolution chain (first non-empty wins):
|
|
9
9
|
* 1. GRAMATR_API_KEY env var
|
|
10
10
|
* 2. GRAMATR_TOKEN env var (legacy)
|
|
11
|
-
* 3. ~/.
|
|
12
|
-
* 4.
|
|
11
|
+
* 3. ~/.gramatr.json `token` field
|
|
12
|
+
* 4. ~/.gramatr/settings.json `auth.api_key` (legacy, skips placeholder)
|
|
13
13
|
* 5. If interactive + TTY: spawn gmtr-login.ts (OAuth)
|
|
14
14
|
* 6. Otherwise: throw clean actionable error
|
|
15
15
|
*
|
|
@@ -44,11 +44,11 @@ function getHome(): string {
|
|
|
44
44
|
}
|
|
45
45
|
|
|
46
46
|
function gmtrJsonPath(): string {
|
|
47
|
-
return join(getHome(), ".
|
|
47
|
+
return join(getHome(), ".gramatr.json");
|
|
48
48
|
}
|
|
49
49
|
|
|
50
50
|
function legacySettingsPath(): string {
|
|
51
|
-
const gmtrDir = process.env.GRAMATR_DIR || join(getHome(), "
|
|
51
|
+
const gmtrDir = process.env.GRAMATR_DIR || join(getHome(), ".gramatr");
|
|
52
52
|
return join(gmtrDir, "settings.json");
|
|
53
53
|
}
|
|
54
54
|
|
|
@@ -90,7 +90,7 @@ function findGmtrLoginScript(): string | null {
|
|
|
90
90
|
}
|
|
91
91
|
// Fallback to installed client dir
|
|
92
92
|
const installedCandidate = join(
|
|
93
|
-
process.env.GRAMATR_DIR || join(getHome(), "
|
|
93
|
+
process.env.GRAMATR_DIR || join(getHome(), ".gramatr"),
|
|
94
94
|
"bin",
|
|
95
95
|
"gmtr-login.ts",
|
|
96
96
|
);
|
|
@@ -144,7 +144,7 @@ export async function resolveAuthToken(opts: ResolveAuthTokenOptions): Promise<s
|
|
|
144
144
|
const envToken = tokenFromEnv();
|
|
145
145
|
if (envToken) return envToken;
|
|
146
146
|
|
|
147
|
-
// 3: ~/.
|
|
147
|
+
// 3: ~/.gramatr.json
|
|
148
148
|
const stored = tokenFromGmtrJson();
|
|
149
149
|
if (stored) return stored;
|
|
150
150
|
|
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
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
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/core/version.ts
CHANGED
|
@@ -8,8 +8,8 @@
|
|
|
8
8
|
* Works in two environments:
|
|
9
9
|
* 1. Source checkout: packages/client/core/version.ts →
|
|
10
10
|
* packages/client/package.json (found one directory up).
|
|
11
|
-
* 2. Installed client:
|
|
12
|
-
*
|
|
11
|
+
* 2. Installed client: ~/.gramatr/core/version.ts →
|
|
12
|
+
* ~/.gramatr/package.json (copied by installClientFiles()).
|
|
13
13
|
*
|
|
14
14
|
* If the file cannot be resolved (unexpected layout), falls back to '0.0.0'
|
|
15
15
|
* rather than throwing — the version check is opportunistic and must never
|
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`);
|
|
@@ -104,10 +108,10 @@ async function testApiKey(key: string): Promise<boolean> {
|
|
|
104
108
|
}
|
|
105
109
|
|
|
106
110
|
async function resolveApiKey(home: string): Promise<string | null> {
|
|
107
|
-
// 1. Check if already stored in ~/.
|
|
111
|
+
// 1. Check if already stored in ~/.gramatr.json (shared with other platforms)
|
|
108
112
|
const stored = readStoredApiKey(home);
|
|
109
113
|
if (stored) {
|
|
110
|
-
log(` Found existing token in ~/.
|
|
114
|
+
log(` Found existing token in ~/.gramatr.json`);
|
|
111
115
|
log(' Testing token...');
|
|
112
116
|
const valid = await testApiKey(stored);
|
|
113
117
|
if (valid) {
|
|
@@ -142,8 +146,8 @@ async function resolveApiKey(home: string): Promise<string | null> {
|
|
|
142
146
|
const valid = await testApiKey(prompted);
|
|
143
147
|
if (valid) {
|
|
144
148
|
log(' OK Token is valid');
|
|
145
|
-
// Save to ~/.
|
|
146
|
-
const configPath = join(home, '.
|
|
149
|
+
// Save to ~/.gramatr.json for cross-platform reuse
|
|
150
|
+
const configPath = join(home, '.gramatr.json');
|
|
147
151
|
let config: Record<string, unknown> = {};
|
|
148
152
|
if (existsSync(configPath)) {
|
|
149
153
|
try {
|
|
@@ -159,7 +163,7 @@ async function resolveApiKey(home: string): Promise<string | null> {
|
|
|
159
163
|
: 'oauth';
|
|
160
164
|
config.authenticated_at = new Date().toISOString();
|
|
161
165
|
writeFileSync(configPath, JSON.stringify(config, null, 2) + '\n');
|
|
162
|
-
log(' OK Saved to ~/.
|
|
166
|
+
log(' OK Saved to ~/.gramatr.json');
|
|
163
167
|
return prompted;
|
|
164
168
|
}
|
|
165
169
|
log(' Token rejected by server.');
|
|
@@ -244,9 +248,9 @@ export async function main(): Promise<void> {
|
|
|
244
248
|
);
|
|
245
249
|
log(' OK Wrote hooks/hooks.json');
|
|
246
250
|
|
|
247
|
-
// ── Store token in ~/.
|
|
251
|
+
// ── Store token in ~/.gramatr.json (canonical source, not in extension dir) ──
|
|
248
252
|
if (apiKey) {
|
|
249
|
-
const gmtrJsonPath = join(home, '.
|
|
253
|
+
const gmtrJsonPath = join(home, '.gramatr.json');
|
|
250
254
|
let gmtrConfig: Record<string, unknown> = {};
|
|
251
255
|
if (existsSync(gmtrJsonPath)) {
|
|
252
256
|
try { gmtrConfig = JSON.parse(readFileSync(gmtrJsonPath, 'utf8')); } catch {}
|
|
@@ -254,7 +258,7 @@ export async function main(): Promise<void> {
|
|
|
254
258
|
gmtrConfig.token = apiKey;
|
|
255
259
|
gmtrConfig.token_updated_at = new Date().toISOString();
|
|
256
260
|
writeFileSync(gmtrJsonPath, JSON.stringify(gmtrConfig, null, 2) + '\n');
|
|
257
|
-
log(' OK Token stored in ~/.
|
|
261
|
+
log(' OK Token stored in ~/.gramatr.json (hooks read from here at runtime)');
|
|
258
262
|
}
|
|
259
263
|
|
|
260
264
|
// ── Summary ──
|
|
@@ -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
|
|
@@ -155,10 +155,10 @@ export function getGramatrExtensionDir(home: string): string {
|
|
|
155
155
|
}
|
|
156
156
|
|
|
157
157
|
/**
|
|
158
|
-
* Read the stored API key from ~/.
|
|
158
|
+
* Read the stored API key from ~/.gramatr.json (shared with other platforms).
|
|
159
159
|
*/
|
|
160
160
|
export function readStoredApiKey(home: string): string | null {
|
|
161
|
-
const configPath = join(home, '.
|
|
161
|
+
const configPath = join(home, '.gramatr.json');
|
|
162
162
|
if (!existsSync(configPath)) return null;
|
|
163
163
|
try {
|
|
164
164
|
const config = JSON.parse(readFileSync(configPath, 'utf8'));
|
|
@@ -40,7 +40,7 @@ async function main() {
|
|
|
40
40
|
let token = '';
|
|
41
41
|
|
|
42
42
|
try {
|
|
43
|
-
const gmtrJson = JSON.parse(readFileSync(`${process.env.HOME}/.
|
|
43
|
+
const gmtrJson = JSON.parse(readFileSync(`${process.env.HOME}/.gramatr.json`, 'utf8'));
|
|
44
44
|
token = gmtrJson.token || '';
|
|
45
45
|
} catch { /* no token */ }
|
|
46
46
|
|
|
@@ -341,16 +341,16 @@ export function createDefaultConfig(options: {
|
|
|
341
341
|
* Resolve auth token. gramatr credentials NEVER live in CLI-specific config files.
|
|
342
342
|
*
|
|
343
343
|
* Priority:
|
|
344
|
-
* 1. ~/.
|
|
344
|
+
* 1. ~/.gramatr.json (canonical, gramatr-owned, vendor-agnostic)
|
|
345
345
|
* 2. GRAMATR_TOKEN env var (CI, headless override)
|
|
346
|
-
* 3.
|
|
346
|
+
* 3. ~/.gramatr/settings.json auth.api_key (legacy, will be migrated)
|
|
347
347
|
*
|
|
348
348
|
* Token is NEVER stored in ~/.claude.json, ~/.codex/, or ~/.gemini/.
|
|
349
349
|
*/
|
|
350
350
|
export function resolveAuthToken(): string | null {
|
|
351
|
-
// 1. ~/.
|
|
351
|
+
// 1. ~/.gramatr.json (canonical source — written by installer, read by all platforms)
|
|
352
352
|
try {
|
|
353
|
-
const configPath = join(HOME, '.
|
|
353
|
+
const configPath = join(HOME, '.gramatr.json');
|
|
354
354
|
const config = JSON.parse(readFileSync(configPath, 'utf8'));
|
|
355
355
|
if (config.token) return config.token;
|
|
356
356
|
} catch {
|
|
@@ -363,9 +363,9 @@ export function resolveAuthToken(): string | null {
|
|
|
363
363
|
// 3. Legacy: AIOS_MCP_TOKEN env var (deprecated)
|
|
364
364
|
if (process.env.AIOS_MCP_TOKEN) return process.env.AIOS_MCP_TOKEN;
|
|
365
365
|
|
|
366
|
-
// 4. Legacy:
|
|
366
|
+
// 4. Legacy: ~/.gramatr/settings.json (will be migrated to ~/.gramatr.json)
|
|
367
367
|
try {
|
|
368
|
-
const gmtrDir = process.env.GRAMATR_DIR || join(HOME, '
|
|
368
|
+
const gmtrDir = process.env.GRAMATR_DIR || join(HOME, '.gramatr');
|
|
369
369
|
const settingsPath = join(gmtrDir, 'settings.json');
|
|
370
370
|
const settings = JSON.parse(readFileSync(settingsPath, 'utf8'));
|
|
371
371
|
if (settings.auth?.api_key && settings.auth.api_key !== 'REPLACE_WITH_YOUR_API_KEY') {
|
|
@@ -382,12 +382,12 @@ export function resolveAuthToken(): string | null {
|
|
|
382
382
|
|
|
383
383
|
/**
|
|
384
384
|
* Resolve MCP server URL from config files.
|
|
385
|
-
* Priority:
|
|
385
|
+
* Priority: ~/.gramatr/settings.json > GRAMATR_URL env > ~/.claude.json > default
|
|
386
386
|
*/
|
|
387
387
|
export function resolveMcpUrl(): string {
|
|
388
|
-
// 1.
|
|
388
|
+
// 1. ~/.gramatr/settings.json (canonical, vendor-agnostic)
|
|
389
389
|
try {
|
|
390
|
-
const gmtrDir = process.env.GRAMATR_DIR || join(HOME, '
|
|
390
|
+
const gmtrDir = process.env.GRAMATR_DIR || join(HOME, '.gramatr');
|
|
391
391
|
const settingsPath = join(gmtrDir, 'settings.json');
|
|
392
392
|
const settings = JSON.parse(readFileSync(settingsPath, 'utf8'));
|
|
393
393
|
if (settings.auth?.server_url) return settings.auth.server_url;
|
package/hooks/lib/identity.ts
CHANGED
|
@@ -10,7 +10,7 @@ import { readFileSync, existsSync } from 'fs';
|
|
|
10
10
|
import { join } from 'path';
|
|
11
11
|
|
|
12
12
|
const HOME = process.env.HOME!;
|
|
13
|
-
const GRAMATR_DIR = process.env.GRAMATR_DIR || join(HOME, '
|
|
13
|
+
const GRAMATR_DIR = process.env.GRAMATR_DIR || join(HOME, '.gramatr');
|
|
14
14
|
const GMTR_SETTINGS_PATH = join(GRAMATR_DIR, 'settings.json'); // gramatr-owned, vendor-agnostic
|
|
15
15
|
const CLAUDE_SETTINGS_PATH = join(HOME, '.claude/settings.json'); // legacy fallback only
|
|
16
16
|
|
|
@@ -82,7 +82,7 @@ let cachedSettings: Settings | null = null;
|
|
|
82
82
|
* Load settings — gramatr-owned config first, Claude vendor file as legacy fallback.
|
|
83
83
|
*
|
|
84
84
|
* Priority:
|
|
85
|
-
* 1.
|
|
85
|
+
* 1. ~/.gramatr/settings.json (gramatr-owned, vendor-agnostic)
|
|
86
86
|
* 2. ~/.claude/settings.json (Claude vendor file — legacy fallback only)
|
|
87
87
|
*
|
|
88
88
|
* Merges both: gramatr settings take precedence for identity fields,
|
package/hooks/lib/paths.ts
CHANGED
|
@@ -50,7 +50,7 @@ export function getGmtrDir(): string {
|
|
|
50
50
|
/**
|
|
51
51
|
* Get the settings.json path
|
|
52
52
|
* Always ~/.claude/settings.json — NOT relative to GRAMATR_DIR.
|
|
53
|
-
* GRAMATR_DIR is the client payload directory (
|
|
53
|
+
* GRAMATR_DIR is the client payload directory (~/.gramatr), not ~/.claude.
|
|
54
54
|
*/
|
|
55
55
|
export function getSettingsPath(): string {
|
|
56
56
|
return join(homedir(), '.claude', 'settings.json');
|
|
@@ -171,7 +171,7 @@ function displayBanner(): void {
|
|
|
171
171
|
// ── Sync Ratings (background, non-blocking) ──
|
|
172
172
|
|
|
173
173
|
function syncRatingsInBackground(): void {
|
|
174
|
-
const syncScript = join(process.env.GRAMATR_DIR || join(process.env.HOME || '', '
|
|
174
|
+
const syncScript = join(process.env.GRAMATR_DIR || join(process.env.HOME || '', '.gramatr'), 'hooks', 'sync-ratings.hook.ts');
|
|
175
175
|
if (existsSync(syncScript)) {
|
|
176
176
|
try {
|
|
177
177
|
const npxBin = process.platform === 'win32' ? 'npx.cmd' : 'npx';
|