@gramatr/client 0.5.2 → 0.6.1
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 +12 -12
- package/bin/clean-legacy-install.ts +2 -2
- package/bin/clear-creds.ts +11 -11
- package/bin/gmtr-login.ts +11 -11
- package/bin/gramatr.ts +6 -6
- package/bin/install.ts +20 -20
- package/bin/lib/config.ts +7 -7
- package/bin/logout.ts +5 -5
- package/bin/render-claude-hooks.ts +1 -1
- package/bin/uninstall.ts +16 -16
- package/chatgpt/install.ts +1 -1
- package/codex/install.ts +1 -1
- package/core/auth.ts +10 -8
- package/core/feedback.ts +1 -1
- package/core/install.ts +7 -7
- package/core/routing.ts +2 -2
- package/core/session.ts +7 -7
- package/core/version.ts +2 -2
- package/desktop/install.ts +1 -1
- package/gemini/README.md +1 -1
- package/gemini/install.ts +10 -10
- package/gemini/lib/gemini-install-utils.ts +3 -3
- package/hooks/StopOrchestrator.hook.ts +3 -3
- package/hooks/gmtr-tool-tracker-utils.ts +1 -1
- package/hooks/{GMTRPromptEnricher.hook.ts → gramatr-prompt-enricher.hook.ts} +6 -6
- package/hooks/{GMTRRatingCapture.hook.ts → gramatr-rating-capture.hook.ts} +9 -9
- package/hooks/{GMTRSecurityValidator.hook.ts → gramatr-security-validator.hook.ts} +2 -2
- package/hooks/{GMTRToolTracker.hook.ts → gramatr-tool-tracker.hook.ts} +2 -2
- package/hooks/lib/gmtr-hook-utils.ts +14 -14
- package/hooks/lib/identity.ts +3 -3
- package/hooks/lib/paths.ts +6 -6
- package/hooks/session-end.hook.ts +4 -4
- package/hooks/session-start.hook.ts +3 -3
- package/package.json +1 -1
package/bin/add-api-key.ts
CHANGED
|
@@ -4,13 +4,13 @@
|
|
|
4
4
|
*
|
|
5
5
|
* Three modes:
|
|
6
6
|
* 1. Interactive prompt: gramatr add-api-key
|
|
7
|
-
* 2. Piped stdin: echo "
|
|
7
|
+
* 2. Piped stdin: echo "gramatr_sk_..." | gramatr add-api-key
|
|
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,13 +20,13 @@ 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
|
-
const SERVER_BASE = (process.env.
|
|
25
|
+
const SERVER_BASE = (process.env.GRAMATR_URL || "https://api.gramatr.com").replace(/\/mcp\/?$/, "");
|
|
26
26
|
|
|
27
|
-
// Accept
|
|
27
|
+
// Accept gramatr_sk_, gramatr_pk_, aios_sk_, aios_pk_ (legacy), and Firebase-style
|
|
28
28
|
// long opaque tokens (length >= 32, base64url-ish characters).
|
|
29
|
-
const KEY_FORMAT = /^(gmtr|aios)_(sk|pk)_[A-Za-z0-9_-]+$/;
|
|
29
|
+
const KEY_FORMAT = /^(gramatr|gmtr|aios)_(sk|pk)_[A-Za-z0-9_-]+$/;
|
|
30
30
|
const LEGACY_OPAQUE = /^[A-Za-z0-9_.-]{32,}$/;
|
|
31
31
|
|
|
32
32
|
function log(msg: string = ""): void {
|
|
@@ -59,11 +59,11 @@ 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
|
|
66
|
-
echo "
|
|
66
|
+
echo "gramatr_sk_..." | gramatr add-api-key Read key from piped stdin
|
|
67
67
|
gramatr add-api-key --from-env VAR Read key from named env variable
|
|
68
68
|
gramatr add-api-key --force Skip server validation (offline use)
|
|
69
69
|
|
|
@@ -94,7 +94,7 @@ async function readPipedStdin(): Promise<string | null> {
|
|
|
94
94
|
async function readInteractive(): Promise<string> {
|
|
95
95
|
log("");
|
|
96
96
|
log("Paste your gramatr API key below.");
|
|
97
|
-
log("(Get one at https://gramatr.com/settings — keys start with
|
|
97
|
+
log("(Get one at https://gramatr.com/settings — keys start with gramatr_sk_)");
|
|
98
98
|
log("");
|
|
99
99
|
process.stdout.write(" Key: ");
|
|
100
100
|
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
@@ -168,7 +168,7 @@ function writeKey(key: string): void {
|
|
|
168
168
|
}
|
|
169
169
|
existing.token = key;
|
|
170
170
|
existing.token_type =
|
|
171
|
-
key.startsWith("
|
|
171
|
+
key.startsWith("gramatr_sk_") || key.startsWith("aios_sk_") ? "api_key" : "oauth";
|
|
172
172
|
existing.authenticated_at = new Date().toISOString();
|
|
173
173
|
writeFileSync(gmtrJsonPath(), `${JSON.stringify(existing, null, 2)}\n`, "utf8");
|
|
174
174
|
try {
|
|
@@ -214,7 +214,7 @@ export async function main(argv: string[] = process.argv.slice(2)): Promise<numb
|
|
|
214
214
|
|
|
215
215
|
// Format validation
|
|
216
216
|
if (!validateFormat(key)) {
|
|
217
|
-
err("ERROR: key format is invalid. Expected
|
|
217
|
+
err("ERROR: key format is invalid. Expected gramatr_sk_... or gramatr_pk_...");
|
|
218
218
|
return 1;
|
|
219
219
|
}
|
|
220
220
|
|
|
@@ -14,8 +14,8 @@ function main(): void {
|
|
|
14
14
|
}
|
|
15
15
|
|
|
16
16
|
const apply = process.argv.includes('--apply');
|
|
17
|
-
const includeOptionalUx = process.env.
|
|
18
|
-
const clientDir = process.env.
|
|
17
|
+
const includeOptionalUx = process.env.GRAMATR_ENABLE_OPTIONAL_CLAUDE_UX === '1';
|
|
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,15 +2,15 @@
|
|
|
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
|
-
* Env vars (GRAMATR_API_KEY,
|
|
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.
|
|
15
15
|
*
|
|
16
16
|
* Not-logged-in is not an error: exits 0 with a clean message.
|
|
@@ -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.
|
|
31
|
+
const gmtrDir = process.env.GRAMATR_DIR || join(getHome(), ".gramatr");
|
|
32
32
|
return join(gmtrDir, "settings.json");
|
|
33
33
|
}
|
|
34
34
|
|
|
@@ -40,11 +40,11 @@ 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
|
|
|
47
|
-
Env vars (GRAMATR_API_KEY,
|
|
47
|
+
Env vars (GRAMATR_API_KEY, GRAMATR_TOKEN) cannot be unset from this process.
|
|
48
48
|
If they are set, this command warns and tells you the shell command to run.`);
|
|
49
49
|
}
|
|
50
50
|
|
|
@@ -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 {
|
|
@@ -86,7 +86,7 @@ export function clearAll(): ClearResult {
|
|
|
86
86
|
}
|
|
87
87
|
|
|
88
88
|
// 3. Env vars — detect only, cannot mutate parent shell
|
|
89
|
-
for (const v of ["GRAMATR_API_KEY", "
|
|
89
|
+
for (const v of ["GRAMATR_API_KEY", "GRAMATR_TOKEN"]) {
|
|
90
90
|
if (process.env[v]) result.envVarsSet.push(v);
|
|
91
91
|
}
|
|
92
92
|
|
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,8 +23,8 @@ 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, '.
|
|
27
|
-
const DEFAULT_SERVER = process.env.
|
|
26
|
+
const CONFIG_PATH = join(HOME, '.gramatr.json');
|
|
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\/?$/, '');
|
|
30
30
|
const DASHBOARD_BASE = process.env.GMTR_DASHBOARD_URL || (() => {
|
|
@@ -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> {
|
|
@@ -327,10 +327,10 @@ async function loginWithToken(token: string): Promise<void> {
|
|
|
327
327
|
if (result.valid) {
|
|
328
328
|
const config = readConfig();
|
|
329
329
|
config.token = token;
|
|
330
|
-
config.token_type = token.startsWith('aios_sk_') || token.startsWith('
|
|
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`);
|
|
@@ -539,7 +539,7 @@ export async function main(): Promise<void> {
|
|
|
539
539
|
if (!token) {
|
|
540
540
|
// Interactive paste mode — like Claude's login
|
|
541
541
|
console.log('\n Paste your gramatr token below.');
|
|
542
|
-
console.log(' (API keys start with aios_sk_ or
|
|
542
|
+
console.log(' (API keys start with aios_sk_ or gramatr_sk_)\n');
|
|
543
543
|
process.stdout.write(' Token: ');
|
|
544
544
|
|
|
545
545
|
const { createInterface } = await import('readline');
|
|
@@ -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,11 +205,11 @@ 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.
|
|
208
|
+
const clientDir = process.env.GRAMATR_DIR || join(homeDir, '.gramatr');
|
|
209
209
|
runLegacyMigration({
|
|
210
210
|
homeDir,
|
|
211
211
|
clientDir,
|
|
212
|
-
includeOptionalUx: process.env.
|
|
212
|
+
includeOptionalUx: process.env.GRAMATR_ENABLE_OPTIONAL_CLAUDE_UX === '1',
|
|
213
213
|
apply,
|
|
214
214
|
log,
|
|
215
215
|
});
|
|
@@ -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.
|
|
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,14 +228,14 @@ function doctor(): void {
|
|
|
228
228
|
|
|
229
229
|
function upgrade(): void {
|
|
230
230
|
const homeDir = homedir();
|
|
231
|
-
const clientDir = process.env.
|
|
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...');
|
|
235
235
|
runLegacyMigration({
|
|
236
236
|
homeDir,
|
|
237
237
|
clientDir,
|
|
238
|
-
includeOptionalUx: process.env.
|
|
238
|
+
includeOptionalUx: process.env.GRAMATR_ENABLE_OPTIONAL_CLAUDE_UX === '1',
|
|
239
239
|
apply: true,
|
|
240
240
|
log,
|
|
241
241
|
});
|
|
@@ -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
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
* Usage:
|
|
7
7
|
* npx tsx install.ts # Universal (Node + tsx)
|
|
8
8
|
* bun install.ts # If bun is available
|
|
9
|
-
*
|
|
9
|
+
* GRAMATR_TOKEN=xxx npx tsx install.ts # Headless with token
|
|
10
10
|
* npx tsx install.ts --yes # Non-interactive (auto-accept defaults)
|
|
11
11
|
*/
|
|
12
12
|
|
|
@@ -27,8 +27,8 @@ const HOME = process.env.HOME || process.env.USERPROFILE || '';
|
|
|
27
27
|
const CLAUDE_DIR = join(HOME, '.claude');
|
|
28
28
|
const CLAUDE_SETTINGS = join(CLAUDE_DIR, 'settings.json');
|
|
29
29
|
const CLAUDE_JSON = join(HOME, '.claude.json');
|
|
30
|
-
const CLIENT_DIR = join(HOME, '
|
|
31
|
-
const GMTR_JSON = join(HOME, '.
|
|
30
|
+
const CLIENT_DIR = join(HOME, '.gramatr');
|
|
31
|
+
const GMTR_JSON = join(HOME, '.gramatr.json');
|
|
32
32
|
const SCRIPT_DIR = dirname(dirname(resolve(import.meta.filename || __filename)));
|
|
33
33
|
const DEFAULT_URL = 'https://api.gramatr.com/mcp';
|
|
34
34
|
|
|
@@ -398,13 +398,13 @@ async function handleAuth(legacyToken: string): Promise<{ url: string; token: st
|
|
|
398
398
|
const url = await prompt('gramatr server URL', DEFAULT_URL) || DEFAULT_URL;
|
|
399
399
|
|
|
400
400
|
// Legacy aios token migration: if we cherry-picked a token from a prior
|
|
401
|
-
// aios install and there is no current ~/.
|
|
401
|
+
// aios install and there is no current ~/.gramatr.json token, seed it so the
|
|
402
402
|
// shared resolver picks it up.
|
|
403
403
|
if (legacyToken && !existsSync(GMTR_JSON)) {
|
|
404
404
|
try {
|
|
405
405
|
writeFileSync(GMTR_JSON, `${JSON.stringify({ token: legacyToken }, null, 2)}\n`, 'utf8');
|
|
406
406
|
try { chmodSync(GMTR_JSON, 0o600); } catch { /* ok */ }
|
|
407
|
-
log('OK Seeded ~/.
|
|
407
|
+
log('OK Seeded ~/.gramatr.json from legacy aios installation token');
|
|
408
408
|
} catch { /* ignore */ }
|
|
409
409
|
}
|
|
410
410
|
|
|
@@ -508,16 +508,16 @@ function updateClaudeSettings(url: string, token: string): void {
|
|
|
508
508
|
cpSync(CLAUDE_SETTINGS, backup);
|
|
509
509
|
log(`OK Backed up settings to ${basename(backup)}`);
|
|
510
510
|
|
|
511
|
-
const includeOptionalUx = process.env.
|
|
511
|
+
const includeOptionalUx = process.env.GRAMATR_ENABLE_OPTIONAL_CLAUDE_UX === '1';
|
|
512
512
|
const hooksConfig = buildClaudeHooksFile(CLIENT_DIR, { includeOptionalUx });
|
|
513
513
|
|
|
514
514
|
const settings = readJson(CLAUDE_SETTINGS);
|
|
515
515
|
|
|
516
516
|
// Env vars
|
|
517
517
|
settings.env = settings.env || {};
|
|
518
|
-
settings.env.
|
|
519
|
-
settings.env.
|
|
520
|
-
settings.env.PATH = `${HOME}/.
|
|
518
|
+
settings.env.GRAMATR_DIR = CLIENT_DIR;
|
|
519
|
+
settings.env.GRAMATR_URL = url;
|
|
520
|
+
settings.env.PATH = `${HOME}/.gramatr/bin:/usr/local/bin:/usr/bin:/bin`;
|
|
521
521
|
if (token) settings.env.AIOS_MCP_TOKEN = token;
|
|
522
522
|
|
|
523
523
|
// Identity defaults (don't overwrite)
|
|
@@ -539,7 +539,7 @@ function updateClaudeSettings(url: string, token: string): void {
|
|
|
539
539
|
if (includeOptionalUx) {
|
|
540
540
|
log('OK Optional Claude UX hooks enabled');
|
|
541
541
|
} else {
|
|
542
|
-
log('OK Thin-client hook set (set
|
|
542
|
+
log('OK Thin-client hook set (set GRAMATR_ENABLE_OPTIONAL_CLAUDE_UX=1 for optional hooks)');
|
|
543
543
|
}
|
|
544
544
|
|
|
545
545
|
// Status line
|
|
@@ -564,7 +564,7 @@ function registerMcpServer(url: string, token: string): void {
|
|
|
564
564
|
const backup = `${CLAUDE_JSON}.backup-${timestamp()}`;
|
|
565
565
|
cpSync(CLAUDE_JSON, backup);
|
|
566
566
|
|
|
567
|
-
// Store token in ~/.
|
|
567
|
+
// Store token in ~/.gramatr.json
|
|
568
568
|
if (token) {
|
|
569
569
|
mergeJson(GMTR_JSON, (data) => ({
|
|
570
570
|
...data,
|
|
@@ -572,10 +572,10 @@ function registerMcpServer(url: string, token: string): void {
|
|
|
572
572
|
token_updated_at: new Date().toISOString(),
|
|
573
573
|
}));
|
|
574
574
|
try { chmodSync(GMTR_JSON, 0o600); } catch { /* ok */ }
|
|
575
|
-
log('OK Token stored in ~/.
|
|
575
|
+
log('OK Token stored in ~/.gramatr.json (canonical source)');
|
|
576
576
|
}
|
|
577
577
|
|
|
578
|
-
// Write tool paths to ~/.
|
|
578
|
+
// Write tool paths to ~/.gramatr.json
|
|
579
579
|
mergeJson(GMTR_JSON, (data) => ({
|
|
580
580
|
...data,
|
|
581
581
|
jq_binary: which('jq') || '',
|
|
@@ -585,14 +585,14 @@ function registerMcpServer(url: string, token: string): void {
|
|
|
585
585
|
// Register in ~/.claude.json
|
|
586
586
|
mergeJson(CLAUDE_JSON, (data) => {
|
|
587
587
|
data.env = data.env || {};
|
|
588
|
-
if (token) data.env.
|
|
588
|
+
if (token) data.env.GRAMATR_TOKEN = token;
|
|
589
589
|
delete data.env.AIOS_MCP_TOKEN;
|
|
590
590
|
|
|
591
591
|
data.mcpServers = data.mcpServers || {};
|
|
592
592
|
data.mcpServers.gramatr = {
|
|
593
593
|
type: 'http',
|
|
594
594
|
url,
|
|
595
|
-
headers: { Authorization: 'Bearer ${
|
|
595
|
+
headers: { Authorization: 'Bearer ${GRAMATR_TOKEN}' },
|
|
596
596
|
autoApprove: true,
|
|
597
597
|
};
|
|
598
598
|
return data;
|
|
@@ -624,10 +624,10 @@ function verify(url: string, token: string): boolean {
|
|
|
624
624
|
// exploding at hook execution time. The hook-imports-shipped.test.ts
|
|
625
625
|
// regression guard catches missing entries at PR review.
|
|
626
626
|
for (const f of [
|
|
627
|
-
'hooks/
|
|
628
|
-
'hooks/
|
|
629
|
-
'hooks/
|
|
630
|
-
'hooks/
|
|
627
|
+
'hooks/gramatr-tool-tracker.hook.ts',
|
|
628
|
+
'hooks/gramatr-prompt-enricher.hook.ts',
|
|
629
|
+
'hooks/gramatr-rating-capture.hook.ts',
|
|
630
|
+
'hooks/gramatr-security-validator.hook.ts',
|
|
631
631
|
'hooks/lib/notify.ts',
|
|
632
632
|
'core/routing.ts',
|
|
633
633
|
'core/session.ts',
|
|
@@ -734,7 +734,7 @@ async function main(): Promise<void> {
|
|
|
734
734
|
if (!token) {
|
|
735
735
|
log(' 2. Run /gmtr-login in Claude Code to authenticate');
|
|
736
736
|
} else {
|
|
737
|
-
log(' 2. Already authenticated (token found in ~/.
|
|
737
|
+
log(' 2. Already authenticated (token found in ~/.gramatr.json)');
|
|
738
738
|
}
|
|
739
739
|
log('');
|
|
740
740
|
} else {
|
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 —
|
|
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'));
|
|
@@ -40,15 +40,15 @@ export function getGramatrConfig(): GramatrConfig {
|
|
|
40
40
|
const json = readGmtrJson();
|
|
41
41
|
|
|
42
42
|
const rawUrl =
|
|
43
|
-
process.env.
|
|
43
|
+
process.env.GRAMATR_URL ||
|
|
44
44
|
(typeof json?.url === 'string' && json.url) ||
|
|
45
45
|
DEFAULT_URL;
|
|
46
|
-
//
|
|
46
|
+
// GRAMATR_URL is conventionally the MCP endpoint (e.g. https://api.gramatr.com/mcp).
|
|
47
47
|
// REST calls need the API base — strip a trailing /mcp segment.
|
|
48
48
|
const url = (rawUrl as string).replace(/\/mcp\/?$/, '');
|
|
49
49
|
|
|
50
50
|
const token =
|
|
51
|
-
process.env.
|
|
51
|
+
process.env.GRAMATR_TOKEN ||
|
|
52
52
|
process.env.AIOS_MCP_TOKEN ||
|
|
53
53
|
(typeof json?.token === 'string' && json.token) ||
|
|
54
54
|
null;
|
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 {
|
|
@@ -8,7 +8,7 @@ function main(): void {
|
|
|
8
8
|
throw new Error('Usage: render-claude-hooks.ts <client-dir>');
|
|
9
9
|
}
|
|
10
10
|
|
|
11
|
-
const includeOptionalUx = process.env.
|
|
11
|
+
const includeOptionalUx = process.env.GRAMATR_ENABLE_OPTIONAL_CLAUDE_UX === '1';
|
|
12
12
|
const hooks = buildClaudeHooksFile(clientDir, { includeOptionalUx });
|
|
13
13
|
process.stdout.write(`${JSON.stringify(hooks.hooks, null, 2)}\n`);
|
|
14
14
|
}
|
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
|
|
|
@@ -53,7 +53,7 @@ function removeGramatrFromJson(filePath: string, keys: string[]): boolean {
|
|
|
53
53
|
delete data[key];
|
|
54
54
|
changed = true;
|
|
55
55
|
}
|
|
56
|
-
// Handle nested keys like "env.
|
|
56
|
+
// Handle nested keys like "env.GRAMATR_TOKEN"
|
|
57
57
|
if (key.includes('.')) {
|
|
58
58
|
const [parent, child] = key.split('.');
|
|
59
59
|
if (data[parent] && child in data[parent]) {
|
|
@@ -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');
|
|
@@ -194,7 +194,7 @@ async function main(): Promise<void> {
|
|
|
194
194
|
|
|
195
195
|
// 5. Remove gramatr from ~/.claude.json (MCP server + env vars)
|
|
196
196
|
if (existsSync(claudeJson)) {
|
|
197
|
-
const cleaned = removeGramatrFromJson(claudeJson, ['env.
|
|
197
|
+
const cleaned = removeGramatrFromJson(claudeJson, ['env.GRAMATR_TOKEN', 'env.AIOS_MCP_TOKEN']);
|
|
198
198
|
if (cleaned) log(' OK Removed gramatr MCP server + env vars from ~/.claude.json');
|
|
199
199
|
}
|
|
200
200
|
|
|
@@ -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/chatgpt/install.ts
CHANGED
|
@@ -64,7 +64,7 @@ async function main(): Promise<void> {
|
|
|
64
64
|
// Step 2: Validate server connectivity
|
|
65
65
|
log('');
|
|
66
66
|
log('Step 2: Validating server connectivity...');
|
|
67
|
-
const serverUrl = process.env.
|
|
67
|
+
const serverUrl = process.env.GRAMATR_URL || DEFAULT_MCP_URL;
|
|
68
68
|
const serverReachable = await validateServer(serverUrl);
|
|
69
69
|
if (serverReachable) {
|
|
70
70
|
log(` OK Server reachable at ${serverUrl.replace(/\/mcp$/, '')}`);
|
package/codex/install.ts
CHANGED
|
@@ -47,7 +47,7 @@ export function main(): void {
|
|
|
47
47
|
throw new Error('HOME is not set');
|
|
48
48
|
}
|
|
49
49
|
|
|
50
|
-
const gmtrDir = process.env.
|
|
50
|
+
const gmtrDir = process.env.GRAMATR_DIR || join(home, '.gramatr');
|
|
51
51
|
const codexHome = join(home, '.codex');
|
|
52
52
|
const hooksPath = join(codexHome, 'hooks.json');
|
|
53
53
|
const configPath = join(codexHome, 'config.toml');
|