@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/core/auth.ts
CHANGED
|
@@ -7,9 +7,9 @@
|
|
|
7
7
|
*
|
|
8
8
|
* Resolution chain (first non-empty wins):
|
|
9
9
|
* 1. GRAMATR_API_KEY env var
|
|
10
|
-
* 2.
|
|
11
|
-
* 3. ~/.
|
|
12
|
-
* 4.
|
|
10
|
+
* 2. GRAMATR_TOKEN env var (legacy)
|
|
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,17 +44,19 @@ 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.
|
|
51
|
+
const gmtrDir = process.env.GRAMATR_DIR || join(getHome(), ".gramatr");
|
|
52
52
|
return join(gmtrDir, "settings.json");
|
|
53
53
|
}
|
|
54
54
|
|
|
55
55
|
function tokenFromEnv(): string | null {
|
|
56
|
+
// GRAMATR_API_KEY is the canonical env var for long-lived API keys.
|
|
57
|
+
// Legacy GMTR_TOKEN slot was removed in v0.6.0; see #512 for the full
|
|
58
|
+
// auth architecture refactor (split API keys from OAuth refresh tokens).
|
|
56
59
|
if (process.env.GRAMATR_API_KEY) return process.env.GRAMATR_API_KEY;
|
|
57
|
-
if (process.env.GMTR_TOKEN) return process.env.GMTR_TOKEN;
|
|
58
60
|
return null;
|
|
59
61
|
}
|
|
60
62
|
|
|
@@ -88,7 +90,7 @@ function findGmtrLoginScript(): string | null {
|
|
|
88
90
|
}
|
|
89
91
|
// Fallback to installed client dir
|
|
90
92
|
const installedCandidate = join(
|
|
91
|
-
process.env.
|
|
93
|
+
process.env.GRAMATR_DIR || join(getHome(), ".gramatr"),
|
|
92
94
|
"bin",
|
|
93
95
|
"gmtr-login.ts",
|
|
94
96
|
);
|
|
@@ -142,7 +144,7 @@ export async function resolveAuthToken(opts: ResolveAuthTokenOptions): Promise<s
|
|
|
142
144
|
const envToken = tokenFromEnv();
|
|
143
145
|
if (envToken) return envToken;
|
|
144
146
|
|
|
145
|
-
// 3: ~/.
|
|
147
|
+
// 3: ~/.gramatr.json
|
|
146
148
|
const stored = tokenFromGmtrJson();
|
|
147
149
|
if (stored) return stored;
|
|
148
150
|
|
package/core/feedback.ts
CHANGED
package/core/install.ts
CHANGED
|
@@ -27,13 +27,13 @@ interface ClaudeHookOptions {
|
|
|
27
27
|
}
|
|
28
28
|
|
|
29
29
|
const CLAUDE_HOOKS: HookSpec[] = [
|
|
30
|
-
{ event: 'PreToolUse', matcher: 'Bash', relativeCommand: 'hooks/
|
|
31
|
-
{ event: 'PreToolUse', matcher: 'Edit', relativeCommand: 'hooks/
|
|
32
|
-
{ event: 'PreToolUse', matcher: 'Write', relativeCommand: 'hooks/
|
|
33
|
-
{ event: 'PreToolUse', matcher: 'Read', relativeCommand: 'hooks/
|
|
34
|
-
{ event: 'PostToolUse', matcher: 'mcp__.*gramatr.*__', relativeCommand: 'hooks/
|
|
35
|
-
{ event: 'UserPromptSubmit', relativeCommand: 'hooks/
|
|
36
|
-
{ event: 'UserPromptSubmit', relativeCommand: 'hooks/
|
|
30
|
+
{ event: 'PreToolUse', matcher: 'Bash', relativeCommand: 'hooks/gramatr-security-validator.hook.ts' },
|
|
31
|
+
{ event: 'PreToolUse', matcher: 'Edit', relativeCommand: 'hooks/gramatr-security-validator.hook.ts' },
|
|
32
|
+
{ event: 'PreToolUse', matcher: 'Write', relativeCommand: 'hooks/gramatr-security-validator.hook.ts' },
|
|
33
|
+
{ event: 'PreToolUse', matcher: 'Read', relativeCommand: 'hooks/gramatr-security-validator.hook.ts' },
|
|
34
|
+
{ event: 'PostToolUse', matcher: 'mcp__.*gramatr.*__', relativeCommand: 'hooks/gramatr-tool-tracker.hook.ts' },
|
|
35
|
+
{ event: 'UserPromptSubmit', relativeCommand: 'hooks/gramatr-rating-capture.hook.ts' },
|
|
36
|
+
{ event: 'UserPromptSubmit', relativeCommand: 'hooks/gramatr-prompt-enricher.hook.ts' },
|
|
37
37
|
{ event: 'SessionStart', relativeCommand: 'hooks/session-start.hook.ts' },
|
|
38
38
|
{ event: 'SessionEnd', relativeCommand: 'hooks/session-end.hook.ts' },
|
|
39
39
|
{ event: 'Stop', relativeCommand: 'hooks/StopOrchestrator.hook.ts' },
|
package/core/routing.ts
CHANGED
|
@@ -30,7 +30,7 @@ export async function routePrompt(options: {
|
|
|
30
30
|
statuslineSize?: 'small' | 'medium' | 'large';
|
|
31
31
|
}): Promise<{ route: RouteResponse | null; error: MctToolCallError | null }> {
|
|
32
32
|
const result = await callMcpToolDetailed<RouteResponse>(
|
|
33
|
-
'
|
|
33
|
+
'gramatr_route_request',
|
|
34
34
|
{
|
|
35
35
|
prompt: options.prompt,
|
|
36
36
|
...(options.projectId ? { project_id: options.projectId } : {}),
|
|
@@ -79,7 +79,7 @@ export function describeRoutingFailure(error: MctToolCallError): {
|
|
|
79
79
|
return {
|
|
80
80
|
title: 'Routing request failed before intelligence could be injected.',
|
|
81
81
|
detail: error.detail,
|
|
82
|
-
action: `Inspect the response from ${resolveMcpUrl()} and the
|
|
82
|
+
action: `Inspect the response from ${resolveMcpUrl()} and the gramatr_route_request handler.`,
|
|
83
83
|
};
|
|
84
84
|
}
|
|
85
85
|
}
|
package/core/session.ts
CHANGED
|
@@ -28,7 +28,7 @@ export interface CurrentProjectContextPayload {
|
|
|
28
28
|
project_name: string;
|
|
29
29
|
working_directory: string;
|
|
30
30
|
session_start: string;
|
|
31
|
-
|
|
31
|
+
gramatr_config_path: string;
|
|
32
32
|
project_entity_id: string | null;
|
|
33
33
|
action_required: string;
|
|
34
34
|
project_id?: string;
|
|
@@ -114,7 +114,7 @@ export async function startRemoteSession(options: {
|
|
|
114
114
|
directory: string;
|
|
115
115
|
}): Promise<SessionStartResponse | null> {
|
|
116
116
|
return (await callMcpTool(
|
|
117
|
-
'
|
|
117
|
+
'gramatr_session_start',
|
|
118
118
|
{
|
|
119
119
|
client_type: options.clientType,
|
|
120
120
|
project_id: options.projectId,
|
|
@@ -129,7 +129,7 @@ export async function startRemoteSession(options: {
|
|
|
129
129
|
|
|
130
130
|
export async function loadProjectHandoff(projectId: string): Promise<HandoffResponse | null> {
|
|
131
131
|
return (await callMcpTool(
|
|
132
|
-
'
|
|
132
|
+
'gramatr_load_handoff',
|
|
133
133
|
{ project_id: projectId },
|
|
134
134
|
15000,
|
|
135
135
|
)) as HandoffResponse | null;
|
|
@@ -143,7 +143,7 @@ export function persistSessionRegistration(rootDir: string, response: SessionSta
|
|
|
143
143
|
if (!config) return null;
|
|
144
144
|
config.current_session = config.current_session || {};
|
|
145
145
|
if (normalized.interactionId) config.current_session.interaction_id = normalized.interactionId;
|
|
146
|
-
if (normalized.entityId) config.current_session.
|
|
146
|
+
if (normalized.entityId) config.current_session.gramatr_entity_id = normalized.entityId;
|
|
147
147
|
writeGmtrConfig(rootDir, config);
|
|
148
148
|
return config;
|
|
149
149
|
}
|
|
@@ -176,7 +176,7 @@ export function buildGitProjectContextPayload(options: {
|
|
|
176
176
|
git_remote: options.git.remote,
|
|
177
177
|
working_directory: options.workingDirectory,
|
|
178
178
|
session_start: options.sessionStart,
|
|
179
|
-
|
|
179
|
+
gramatr_config_path: join(options.git.root, '.gmtr', 'settings.json'),
|
|
180
180
|
project_entity_id: options.projectEntityId,
|
|
181
181
|
restore_needed: options.restoreNeeded,
|
|
182
182
|
action_required: 'check_or_create_project_entity',
|
|
@@ -195,8 +195,8 @@ export function buildNonGitProjectContextPayload(options: {
|
|
|
195
195
|
project_name: basename(options.cwd),
|
|
196
196
|
working_directory: options.cwd,
|
|
197
197
|
session_start: options.sessionStart,
|
|
198
|
-
|
|
198
|
+
gramatr_config_path: join(options.cwd, '.gmtr', 'settings.json'),
|
|
199
199
|
project_entity_id: options.projectEntityId,
|
|
200
|
-
action_required: '
|
|
200
|
+
action_required: 'gramatr_init_needed',
|
|
201
201
|
};
|
|
202
202
|
}
|
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/desktop/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/gemini/README.md
CHANGED
|
@@ -74,7 +74,7 @@ bun packages/client/bin/gmtr-login.ts
|
|
|
74
74
|
|
|
75
75
|
This stores the token in `~/.gmtr.json`, which the installer reads automatically.
|
|
76
76
|
|
|
77
|
-
API keys start with `
|
|
77
|
+
API keys start with `gramatr_sk_` and can be created at [gramatr.com](https://gramatr.com) or via the `gramatr_create_api_key` MCP tool.
|
|
78
78
|
|
|
79
79
|
## Hook Event Mapping
|
|
80
80
|
|
package/gemini/install.ts
CHANGED
|
@@ -46,7 +46,7 @@ async function promptForApiKey(): Promise<string | null> {
|
|
|
46
46
|
log(' gramatr requires authentication.');
|
|
47
47
|
log(' Options:');
|
|
48
48
|
log(' 1. Run `bun gmtr-login.ts` first to authenticate via browser');
|
|
49
|
-
log(' 2. Paste an API key below (starts with
|
|
49
|
+
log(' 2. Paste an API key below (starts with gramatr_sk_)');
|
|
50
50
|
log('');
|
|
51
51
|
process.stdout.write(' API Key (enter to skip): ');
|
|
52
52
|
|
|
@@ -104,10 +104,10 @@ async function testApiKey(key: string): Promise<boolean> {
|
|
|
104
104
|
}
|
|
105
105
|
|
|
106
106
|
async function resolveApiKey(home: string): Promise<string | null> {
|
|
107
|
-
// 1. Check if already stored in ~/.
|
|
107
|
+
// 1. Check if already stored in ~/.gramatr.json (shared with other platforms)
|
|
108
108
|
const stored = readStoredApiKey(home);
|
|
109
109
|
if (stored) {
|
|
110
|
-
log(` Found existing token in ~/.
|
|
110
|
+
log(` Found existing token in ~/.gramatr.json`);
|
|
111
111
|
log(' Testing token...');
|
|
112
112
|
const valid = await testApiKey(stored);
|
|
113
113
|
if (valid) {
|
|
@@ -142,8 +142,8 @@ async function resolveApiKey(home: string): Promise<string | null> {
|
|
|
142
142
|
const valid = await testApiKey(prompted);
|
|
143
143
|
if (valid) {
|
|
144
144
|
log(' OK Token is valid');
|
|
145
|
-
// Save to ~/.
|
|
146
|
-
const configPath = join(home, '.
|
|
145
|
+
// Save to ~/.gramatr.json for cross-platform reuse
|
|
146
|
+
const configPath = join(home, '.gramatr.json');
|
|
147
147
|
let config: Record<string, unknown> = {};
|
|
148
148
|
if (existsSync(configPath)) {
|
|
149
149
|
try {
|
|
@@ -154,12 +154,12 @@ async function resolveApiKey(home: string): Promise<string | null> {
|
|
|
154
154
|
}
|
|
155
155
|
config.token = prompted;
|
|
156
156
|
config.token_type =
|
|
157
|
-
prompted.startsWith('
|
|
157
|
+
prompted.startsWith('gramatr_sk_') || prompted.startsWith('aios_sk_')
|
|
158
158
|
? 'api_key'
|
|
159
159
|
: 'oauth';
|
|
160
160
|
config.authenticated_at = new Date().toISOString();
|
|
161
161
|
writeFileSync(configPath, JSON.stringify(config, null, 2) + '\n');
|
|
162
|
-
log(' OK Saved to ~/.
|
|
162
|
+
log(' OK Saved to ~/.gramatr.json');
|
|
163
163
|
return prompted;
|
|
164
164
|
}
|
|
165
165
|
log(' Token rejected by server.');
|
|
@@ -244,9 +244,9 @@ export async function main(): Promise<void> {
|
|
|
244
244
|
);
|
|
245
245
|
log(' OK Wrote hooks/hooks.json');
|
|
246
246
|
|
|
247
|
-
// ── Store token in ~/.
|
|
247
|
+
// ── Store token in ~/.gramatr.json (canonical source, not in extension dir) ──
|
|
248
248
|
if (apiKey) {
|
|
249
|
-
const gmtrJsonPath = join(home, '.
|
|
249
|
+
const gmtrJsonPath = join(home, '.gramatr.json');
|
|
250
250
|
let gmtrConfig: Record<string, unknown> = {};
|
|
251
251
|
if (existsSync(gmtrJsonPath)) {
|
|
252
252
|
try { gmtrConfig = JSON.parse(readFileSync(gmtrJsonPath, 'utf8')); } catch {}
|
|
@@ -254,7 +254,7 @@ export async function main(): Promise<void> {
|
|
|
254
254
|
gmtrConfig.token = apiKey;
|
|
255
255
|
gmtrConfig.token_updated_at = new Date().toISOString();
|
|
256
256
|
writeFileSync(gmtrJsonPath, JSON.stringify(gmtrConfig, null, 2) + '\n');
|
|
257
|
-
log(' OK Token stored in ~/.
|
|
257
|
+
log(' OK Token stored in ~/.gramatr.json (hooks read from here at runtime)');
|
|
258
258
|
}
|
|
259
259
|
|
|
260
260
|
// ── Summary ──
|
|
@@ -67,7 +67,7 @@ export function buildExtensionManifest(): GeminiExtensionManifest {
|
|
|
67
67
|
name: 'API Key',
|
|
68
68
|
envVar: 'GRAMATR_API_KEY',
|
|
69
69
|
sensitive: true,
|
|
70
|
-
description: 'gramatr API key (starts with
|
|
70
|
+
description: 'gramatr API key (starts with gramatr_sk_). Get one at gramatr.com or via gmtr-login.',
|
|
71
71
|
},
|
|
72
72
|
],
|
|
73
73
|
};
|
|
@@ -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'));
|
|
@@ -36,11 +36,11 @@ async function main() {
|
|
|
36
36
|
|
|
37
37
|
// Read server URL and token for direct HTTP feedback
|
|
38
38
|
const gmtrDir = getGmtrDir();
|
|
39
|
-
let serverUrl = process.env.
|
|
39
|
+
let serverUrl = process.env.GRAMATR_URL || 'https://api.gramatr.com/mcp';
|
|
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
|
|
|
@@ -61,7 +61,7 @@ async function main() {
|
|
|
61
61
|
id: 1,
|
|
62
62
|
method: 'tools/call',
|
|
63
63
|
params: {
|
|
64
|
-
name: '
|
|
64
|
+
name: 'gramatr_classification_feedback',
|
|
65
65
|
arguments: {
|
|
66
66
|
original_prompt: parsed.userMessages?.[0] || '',
|
|
67
67
|
session_id: hookInput.session_id,
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* gmtr-tool-tracker-utils.ts — Pure utility functions for the GMTR Tool Tracker hook.
|
|
3
3
|
*
|
|
4
|
-
* Extracted from
|
|
4
|
+
* Extracted from gramatr-tool-tracker.hook.ts for testability. These are pure functions
|
|
5
5
|
* with no Bun dependencies, no I/O, no side effects.
|
|
6
6
|
*/
|
|
7
7
|
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
/**
|
|
3
|
-
*
|
|
3
|
+
* gramatr-prompt-enricher.hook.ts — grāmatr UserPromptSubmit Hook
|
|
4
4
|
*
|
|
5
5
|
* Fires before the agent processes a user prompt. Calls the grāmatr
|
|
6
6
|
* decision router to pre-classify the request, then injects
|
|
7
7
|
* the intelligence as additionalContext so the agent sees it.
|
|
8
8
|
*
|
|
9
9
|
* TRIGGER: UserPromptSubmit
|
|
10
|
-
* TIMEOUT: 15s default (configurable via
|
|
10
|
+
* TIMEOUT: 15s default (configurable via GRAMATR_TIMEOUT)
|
|
11
11
|
* SAFETY: Never blocks. On any error, prompt passes through unmodified.
|
|
12
12
|
*
|
|
13
13
|
* What it injects:
|
|
@@ -42,8 +42,8 @@ interface HookInput {
|
|
|
42
42
|
|
|
43
43
|
// ── Configuration ──
|
|
44
44
|
|
|
45
|
-
const TIMEOUT_MS = parseInt(process.env.
|
|
46
|
-
const ENABLED = process.env.
|
|
45
|
+
const TIMEOUT_MS = parseInt(process.env.GRAMATR_TIMEOUT || '30000', 10);
|
|
46
|
+
const ENABLED = process.env.GRAMATR_ENRICH !== '0'; // disable with GRAMATR_ENRICH=0
|
|
47
47
|
|
|
48
48
|
// ── Project ID Resolution (Issue #76 — project-scoped memory) ──
|
|
49
49
|
|
|
@@ -398,9 +398,9 @@ function formatIntelligence(data: RouteResponse): string {
|
|
|
398
398
|
lines.push(` Context: ${ca.context_summary || 'memory-aware'}`);
|
|
399
399
|
lines.push(' ACTION: Use the Task tool with subagent_type="general-purpose" and inject this system prompt:');
|
|
400
400
|
lines.push(' --- AGENT SYSTEM PROMPT START ---');
|
|
401
|
-
// Truncate to avoid overwhelming the context — full prompt available via
|
|
401
|
+
// Truncate to avoid overwhelming the context — full prompt available via gramatr_invoke_agent
|
|
402
402
|
const promptPreview = (ca.system_prompt || '').substring(0, 800);
|
|
403
|
-
lines.push(` ${promptPreview}${(ca.system_prompt || '').length > 800 ? '... [truncated — use
|
|
403
|
+
lines.push(` ${promptPreview}${(ca.system_prompt || '').length > 800 ? '... [truncated — use gramatr_invoke_agent for full prompt]' : ''}`);
|
|
404
404
|
lines.push(' --- AGENT SYSTEM PROMPT END ---');
|
|
405
405
|
}
|
|
406
406
|
}
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
/**
|
|
3
|
-
*
|
|
3
|
+
* gramatr-rating-capture.hook.ts — gramatr-native Rating Capture (UserPromptSubmit)
|
|
4
4
|
*
|
|
5
5
|
* Captures explicit ratings (1-10 pattern) from user prompts and writes
|
|
6
|
-
* to $
|
|
6
|
+
* to $GRAMATR_DIR/.state/ratings.jsonl for sparkline consumption.
|
|
7
7
|
*
|
|
8
8
|
* TRIGGER: UserPromptSubmit
|
|
9
9
|
*
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
* - Self-contained: only depends on ./lib/paths
|
|
16
16
|
*
|
|
17
17
|
* SIDE EFFECTS:
|
|
18
|
-
* - Writes to: $
|
|
18
|
+
* - Writes to: $GRAMATR_DIR/.state/ratings.jsonl
|
|
19
19
|
* - macOS notification for low ratings (<= 3)
|
|
20
20
|
*
|
|
21
21
|
* PERFORMANCE:
|
|
@@ -100,13 +100,13 @@ function parseExplicitRating(prompt: string): { rating: number; comment?: string
|
|
|
100
100
|
function writeRating(entry: RatingEntry): void {
|
|
101
101
|
if (!existsSync(STATE_DIR)) mkdirSync(STATE_DIR, { recursive: true });
|
|
102
102
|
appendFileSync(RATINGS_FILE, JSON.stringify(entry) + '\n', 'utf-8');
|
|
103
|
-
console.error(`[
|
|
103
|
+
console.error(`[gramatr-rating-capture] Wrote rating ${entry.rating} to ${RATINGS_FILE}`);
|
|
104
104
|
}
|
|
105
105
|
|
|
106
106
|
// ── Push to GMTR Server (fire-and-forget) ──
|
|
107
107
|
|
|
108
108
|
function pushToServer(entry: RatingEntry): void {
|
|
109
|
-
const gmtrUrl = process.env.
|
|
109
|
+
const gmtrUrl = process.env.GRAMATR_URL || 'https://api.gramatr.com/mcp';
|
|
110
110
|
const apiBase = gmtrUrl.replace(/\/mcp$/, '/api/v1');
|
|
111
111
|
|
|
112
112
|
fetch(`${apiBase}/feedback`, {
|
|
@@ -124,9 +124,9 @@ function pushToServer(entry: RatingEntry): void {
|
|
|
124
124
|
})
|
|
125
125
|
.then((res) => {
|
|
126
126
|
if (res.ok) {
|
|
127
|
-
console.error(`[
|
|
127
|
+
console.error(`[gramatr-rating-capture] Pushed rating ${entry.rating} to server`);
|
|
128
128
|
} else {
|
|
129
|
-
console.error(`[
|
|
129
|
+
console.error(`[gramatr-rating-capture] Server push failed: HTTP ${res.status}`);
|
|
130
130
|
}
|
|
131
131
|
})
|
|
132
132
|
.catch(() => {
|
|
@@ -169,7 +169,7 @@ async function main() {
|
|
|
169
169
|
process.exit(0);
|
|
170
170
|
}
|
|
171
171
|
|
|
172
|
-
console.error(`[
|
|
172
|
+
console.error(`[gramatr-rating-capture] Explicit rating: ${result.rating}${result.comment ? ` - ${result.comment}` : ''}`);
|
|
173
173
|
|
|
174
174
|
const entry: RatingEntry = {
|
|
175
175
|
timestamp: new Date().toISOString(),
|
|
@@ -190,7 +190,7 @@ async function main() {
|
|
|
190
190
|
|
|
191
191
|
process.exit(0);
|
|
192
192
|
} catch (err) {
|
|
193
|
-
console.error(`[
|
|
193
|
+
console.error(`[gramatr-rating-capture] Error: ${err}`);
|
|
194
194
|
process.exit(0);
|
|
195
195
|
}
|
|
196
196
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
/**
|
|
3
|
-
*
|
|
3
|
+
* gramatr-security-validator.hook.ts — gramatr-native Security Validation (PreToolUse)
|
|
4
4
|
*
|
|
5
5
|
* Validates Bash commands and file operations against security patterns
|
|
6
6
|
* before execution. Prevents destructive operations and protects sensitive files.
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
* This is the gramatr-native replacement for PAI's SecurityValidator.hook.ts.
|
|
11
11
|
* Key differences:
|
|
12
12
|
* - Inline default patterns (no yaml dependency, no PAI skill dir)
|
|
13
|
-
* - Logs to $
|
|
13
|
+
* - Logs to $GRAMATR_DIR/.state/security/ (not PAI MEMORY/SECURITY/)
|
|
14
14
|
* - Self-contained: only depends on ./lib/paths
|
|
15
15
|
*
|
|
16
16
|
* OUTPUT:
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
/**
|
|
3
|
-
*
|
|
3
|
+
* gramatr-tool-tracker.hook.ts — grāmatr PostToolUse Hook
|
|
4
4
|
*
|
|
5
5
|
* Fires after any GMTR MCP tool call. Extracts execution metrics from
|
|
6
6
|
* tool_result and surfaces them as a user-visible status line.
|
|
@@ -146,7 +146,7 @@ async function main() {
|
|
|
146
146
|
try { stats = JSON.parse(readFileSync(statsFile, 'utf8')); } catch {}
|
|
147
147
|
|
|
148
148
|
// Track search count
|
|
149
|
-
if (shortName.includes('search') || shortName === '
|
|
149
|
+
if (shortName.includes('search') || shortName === 'gramatr_execute_intent') {
|
|
150
150
|
stats.search_count = ((stats.search_count as number) || 0) + 1;
|
|
151
151
|
}
|
|
152
152
|
|
|
@@ -46,7 +46,7 @@ export interface GmtrConfig {
|
|
|
46
46
|
last_updated?: string;
|
|
47
47
|
token_limit?: number;
|
|
48
48
|
interaction_id?: string;
|
|
49
|
-
|
|
49
|
+
gramatr_entity_id?: string;
|
|
50
50
|
helper_pid?: number | null;
|
|
51
51
|
last_classification?: {
|
|
52
52
|
timestamp?: string;
|
|
@@ -341,31 +341,31 @@ 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. ~/.
|
|
345
|
-
* 2.
|
|
346
|
-
* 3.
|
|
344
|
+
* 1. ~/.gramatr.json (canonical, gramatr-owned, vendor-agnostic)
|
|
345
|
+
* 2. GRAMATR_TOKEN env var (CI, headless override)
|
|
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 {
|
|
357
357
|
// No config file or parse error
|
|
358
358
|
}
|
|
359
359
|
|
|
360
|
-
// 2.
|
|
361
|
-
if (process.env.
|
|
360
|
+
// 2. GRAMATR_TOKEN env var (CI, headless, or shell profile override)
|
|
361
|
+
if (process.env.GRAMATR_TOKEN) return process.env.GRAMATR_TOKEN;
|
|
362
362
|
|
|
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.
|
|
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.
|
|
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;
|
|
@@ -396,7 +396,7 @@ export function resolveMcpUrl(): string {
|
|
|
396
396
|
}
|
|
397
397
|
|
|
398
398
|
// 2. Environment variable
|
|
399
|
-
if (process.env.
|
|
399
|
+
if (process.env.GRAMATR_URL) return process.env.GRAMATR_URL;
|
|
400
400
|
|
|
401
401
|
// 3. ~/.claude.json (Claude-specific MCP config)
|
|
402
402
|
try {
|
package/hooks/lib/identity.ts
CHANGED
|
@@ -10,8 +10,8 @@ import { readFileSync, existsSync } from 'fs';
|
|
|
10
10
|
import { join } from 'path';
|
|
11
11
|
|
|
12
12
|
const HOME = process.env.HOME!;
|
|
13
|
-
const
|
|
14
|
-
const GMTR_SETTINGS_PATH = join(
|
|
13
|
+
const GRAMATR_DIR = process.env.GRAMATR_DIR || join(HOME, '.gramatr');
|
|
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
|
|
|
17
17
|
// Default identity (fallback if settings.json doesn't have identity section)
|
|
@@ -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
|
@@ -28,15 +28,15 @@ export function expandPath(path: string): string {
|
|
|
28
28
|
|
|
29
29
|
/**
|
|
30
30
|
* Get the GMTR base directory (expanded)
|
|
31
|
-
* Priority:
|
|
31
|
+
* Priority: GRAMATR_DIR env var → PAI_DIR env var (migration) → ~/.claude
|
|
32
32
|
*/
|
|
33
33
|
export function getGmtrDir(): string {
|
|
34
|
-
const envGmtrDir = process.env.
|
|
34
|
+
const envGmtrDir = process.env.GRAMATR_DIR;
|
|
35
35
|
if (envGmtrDir) {
|
|
36
36
|
return expandPath(envGmtrDir);
|
|
37
37
|
}
|
|
38
38
|
|
|
39
|
-
// Migration fallback: honor PAI_DIR if
|
|
39
|
+
// Migration fallback: honor PAI_DIR if GRAMATR_DIR not set yet
|
|
40
40
|
const envPaiDir = process.env.PAI_DIR;
|
|
41
41
|
if (envPaiDir) {
|
|
42
42
|
return expandPath(envPaiDir);
|
|
@@ -49,15 +49,15 @@ export function getGmtrDir(): string {
|
|
|
49
49
|
|
|
50
50
|
/**
|
|
51
51
|
* Get the settings.json path
|
|
52
|
-
* Always ~/.claude/settings.json — NOT relative to
|
|
53
|
-
*
|
|
52
|
+
* Always ~/.claude/settings.json — NOT relative to GRAMATR_DIR.
|
|
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');
|
|
57
57
|
}
|
|
58
58
|
|
|
59
59
|
/**
|
|
60
|
-
* Get a path relative to
|
|
60
|
+
* Get a path relative to GRAMATR_DIR
|
|
61
61
|
*/
|
|
62
62
|
export function gmtrPath(...segments: string[]): string {
|
|
63
63
|
return join(getGmtrDir(), ...segments);
|
|
@@ -123,17 +123,17 @@ async function main(): Promise<void> {
|
|
|
123
123
|
}
|
|
124
124
|
|
|
125
125
|
// Get session state from config
|
|
126
|
-
const sessionEntityId = config?.current_session?.
|
|
126
|
+
const sessionEntityId = config?.current_session?.gramatr_entity_id || '';
|
|
127
127
|
const interactionId = config?.current_session?.interaction_id || '';
|
|
128
128
|
|
|
129
129
|
log('');
|
|
130
130
|
log('Saving session state to gramatr...');
|
|
131
131
|
|
|
132
|
-
// Session lifecycle only —
|
|
133
|
-
// Handoffs are saved by the AGENT in the LEARN phase via
|
|
132
|
+
// Session lifecycle only — gramatr_session_end records git summary on the session entity.
|
|
133
|
+
// Handoffs are saved by the AGENT in the LEARN phase via gramatr_save_handoff (HARD GATE).
|
|
134
134
|
// The hook does NOT save handoffs — it lacks conversation context.
|
|
135
135
|
try {
|
|
136
|
-
const rawResult = await callMcpToolRaw('
|
|
136
|
+
const rawResult = await callMcpToolRaw('gramatr_session_end', {
|
|
137
137
|
entity_id: sessionEntityId || sessionId,
|
|
138
138
|
session_id: sessionId,
|
|
139
139
|
interaction_id: interactionId,
|
|
@@ -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.
|
|
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';
|
|
@@ -336,9 +336,9 @@ async function main(): Promise<void> {
|
|
|
336
336
|
log(' - Show observation timestamps');
|
|
337
337
|
log(`3. Load related entities from \`${gmtrConfigPath}\`:`);
|
|
338
338
|
log(' - Check .related_entities for linked databases, people, services, concepts');
|
|
339
|
-
log(' - Optionally fetch details for key related entities (use
|
|
339
|
+
log(' - Optionally fetch details for key related entities (use gramatr_execute_intent with detail_level: summary)');
|
|
340
340
|
log('4. Keep summary concise - just enough context to resume work');
|
|
341
|
-
log(' NOTE: Using intelligent tools (
|
|
341
|
+
log(' NOTE: Using intelligent tools (gramatr_execute_intent) provides 80-95% token reduction vs direct get_entities calls');
|
|
342
342
|
log('');
|
|
343
343
|
}
|
|
344
344
|
|