gramatr 0.3.0
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/CLAUDE.md +18 -0
- package/README.md +78 -0
- package/bin/clean-legacy-install.ts +28 -0
- package/bin/get-token.py +3 -0
- package/bin/gmtr-login.ts +547 -0
- package/bin/gramatr.js +33 -0
- package/bin/gramatr.ts +248 -0
- package/bin/install.ts +756 -0
- package/bin/render-claude-hooks.ts +16 -0
- package/bin/statusline.ts +437 -0
- package/bin/uninstall.ts +289 -0
- package/bin/version-sync.ts +46 -0
- package/codex/README.md +28 -0
- package/codex/hooks/session-start.ts +73 -0
- package/codex/hooks/stop.ts +34 -0
- package/codex/hooks/user-prompt-submit.ts +76 -0
- package/codex/install.ts +99 -0
- package/codex/lib/codex-hook-utils.ts +48 -0
- package/codex/lib/codex-install-utils.ts +123 -0
- package/core/feedback.ts +55 -0
- package/core/formatting.ts +167 -0
- package/core/install.ts +114 -0
- package/core/installer-cli.ts +122 -0
- package/core/migration.ts +244 -0
- package/core/routing.ts +98 -0
- package/core/session.ts +202 -0
- package/core/targets.ts +292 -0
- package/core/types.ts +178 -0
- package/core/version.ts +2 -0
- package/gemini/README.md +95 -0
- package/gemini/hooks/session-start.ts +72 -0
- package/gemini/hooks/stop.ts +30 -0
- package/gemini/hooks/user-prompt-submit.ts +74 -0
- package/gemini/install.ts +272 -0
- package/gemini/lib/gemini-hook-utils.ts +63 -0
- package/gemini/lib/gemini-install-utils.ts +169 -0
- package/hooks/GMTRPromptEnricher.hook.ts +650 -0
- package/hooks/GMTRRatingCapture.hook.ts +198 -0
- package/hooks/GMTRSecurityValidator.hook.ts +399 -0
- package/hooks/GMTRToolTracker.hook.ts +181 -0
- package/hooks/StopOrchestrator.hook.ts +78 -0
- package/hooks/gmtr-tool-tracker-utils.ts +105 -0
- package/hooks/lib/gmtr-hook-utils.ts +771 -0
- package/hooks/lib/identity.ts +227 -0
- package/hooks/lib/notify.ts +46 -0
- package/hooks/lib/paths.ts +104 -0
- package/hooks/lib/transcript-parser.ts +452 -0
- package/hooks/session-end.hook.ts +168 -0
- package/hooks/session-start.hook.ts +490 -0
- package/package.json +54 -0
package/bin/gramatr.js
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* gramatr CLI entry point — thin JS wrapper that bootstraps TypeScript.
|
|
4
|
+
* tsx is a production dependency, resolved directly from node_modules.
|
|
5
|
+
*/
|
|
6
|
+
const { spawnSync, execSync } = require('child_process');
|
|
7
|
+
const { join, dirname } = require('path');
|
|
8
|
+
|
|
9
|
+
const script = join(__dirname, 'gramatr.ts');
|
|
10
|
+
const args = process.argv.slice(2);
|
|
11
|
+
|
|
12
|
+
// Try bun first (fastest)
|
|
13
|
+
let hasBun = false;
|
|
14
|
+
try {
|
|
15
|
+
execSync('bun --version', { stdio: 'ignore' });
|
|
16
|
+
hasBun = true;
|
|
17
|
+
} catch {}
|
|
18
|
+
|
|
19
|
+
if (hasBun) {
|
|
20
|
+
const r = spawnSync('bun', [script, ...args], { stdio: 'inherit' });
|
|
21
|
+
process.exit(r.status ?? 1);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// Resolve tsx from node_modules (installed as dependency)
|
|
25
|
+
try {
|
|
26
|
+
const tsxBin = join(dirname(require.resolve('tsx/package.json')), 'dist', 'cli.mjs');
|
|
27
|
+
const r = spawnSync(process.execPath, [tsxBin, script, ...args], { stdio: 'inherit' });
|
|
28
|
+
process.exit(r.status ?? 1);
|
|
29
|
+
} catch {}
|
|
30
|
+
|
|
31
|
+
// Last resort: npx tsx
|
|
32
|
+
const r = spawnSync('npx', ['--yes', 'tsx', script, ...args], { stdio: 'inherit', shell: true });
|
|
33
|
+
process.exit(r.status ?? 1);
|
package/bin/gramatr.ts
ADDED
|
@@ -0,0 +1,248 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { spawnSync } from 'child_process';
|
|
4
|
+
import { existsSync } from 'fs';
|
|
5
|
+
import { homedir } from 'os';
|
|
6
|
+
import { dirname, join } from 'path';
|
|
7
|
+
import { fileURLToPath } from 'url';
|
|
8
|
+
import { detectTargets, findTarget, summarizeDetectedLocalTargets, type IntegrationTargetId } from '../core/targets.ts';
|
|
9
|
+
import { findStaleArtifacts, runLegacyMigration } from '../core/migration.ts';
|
|
10
|
+
import { detectTsRunner } from '../core/install.ts';
|
|
11
|
+
import {
|
|
12
|
+
formatDetectionLines,
|
|
13
|
+
formatDoctorLines,
|
|
14
|
+
formatInstallMenuLines,
|
|
15
|
+
formatRemoteGuidanceLines,
|
|
16
|
+
resolveInteractiveSelection,
|
|
17
|
+
} from '../core/installer-cli.ts';
|
|
18
|
+
|
|
19
|
+
const currentFile = fileURLToPath(import.meta.url);
|
|
20
|
+
const binDir = dirname(currentFile);
|
|
21
|
+
const clientDir = dirname(binDir);
|
|
22
|
+
const repoRoot = dirname(dirname(clientDir));
|
|
23
|
+
|
|
24
|
+
function log(message: string = ''): void {
|
|
25
|
+
process.stdout.write(`${message}\n`);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function run(command: string, args: string[], env: Record<string, string | undefined> = {}): void {
|
|
29
|
+
const result = spawnSync(command, args, {
|
|
30
|
+
cwd: process.cwd(),
|
|
31
|
+
stdio: 'inherit',
|
|
32
|
+
env: { ...process.env, ...env },
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
if (result.status !== 0) {
|
|
36
|
+
process.exit(result.status ?? 1);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function renderDetections(): void {
|
|
41
|
+
for (const line of formatDetectionLines(detectTargets())) log(line);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function renderRemoteGuidance(): void {
|
|
45
|
+
for (const line of formatRemoteGuidanceLines(detectTargets())) log(line);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function prompt(question: string): string {
|
|
49
|
+
process.stdout.write(question);
|
|
50
|
+
const input = spawnSync('bash', ['-lc', 'IFS= read -r line; printf "%s" "$line"'], {
|
|
51
|
+
stdio: ['inherit', 'pipe', 'inherit'],
|
|
52
|
+
});
|
|
53
|
+
return input.stdout?.toString('utf8').trim() || '';
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function renderInstallMenu(): IntegrationTargetId[] {
|
|
57
|
+
const rendered = formatInstallMenuLines(detectTargets());
|
|
58
|
+
for (const line of rendered.lines) log(line);
|
|
59
|
+
return rendered.targetIds;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function runTs(script: string): void {
|
|
63
|
+
const runner = detectTsRunner();
|
|
64
|
+
if (runner === 'bun') {
|
|
65
|
+
run('bun', [script]);
|
|
66
|
+
} else {
|
|
67
|
+
// Resolve tsx from this package's node_modules (not CWD)
|
|
68
|
+
try {
|
|
69
|
+
const { dirname, join } = require('path');
|
|
70
|
+
const tsxCli = join(dirname(require.resolve('tsx/package.json')), 'dist', 'cli.mjs');
|
|
71
|
+
run(process.execPath, [tsxCli, script]);
|
|
72
|
+
} catch {
|
|
73
|
+
// Fallback: hope tsx is globally available
|
|
74
|
+
run('npx', ['tsx', script]);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function installTarget(targetId: IntegrationTargetId): void {
|
|
80
|
+
switch (targetId) {
|
|
81
|
+
case 'claude-code':
|
|
82
|
+
runTs(join(binDir, 'install.ts'));
|
|
83
|
+
return;
|
|
84
|
+
case 'codex':
|
|
85
|
+
runTs(join(clientDir, 'codex', 'install.ts'));
|
|
86
|
+
return;
|
|
87
|
+
case 'gemini-cli':
|
|
88
|
+
runTs(join(clientDir, 'gemini', 'install.ts'));
|
|
89
|
+
return;
|
|
90
|
+
case 'remote-mcp':
|
|
91
|
+
case 'claude-web':
|
|
92
|
+
case 'chatgpt-web':
|
|
93
|
+
log(`Remote target '${targetId}' is not a local hook install.`);
|
|
94
|
+
log('Use remote MCP / hosted-client setup for this target.');
|
|
95
|
+
log('Current remote targets are documentation/setup targets, not local filesystem installs.');
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function migrate(apply: boolean): void {
|
|
101
|
+
const homeDir = homedir();
|
|
102
|
+
const clientDir = process.env.GMTR_DIR || join(homeDir, 'gmtr-client');
|
|
103
|
+
runLegacyMigration({
|
|
104
|
+
homeDir,
|
|
105
|
+
clientDir,
|
|
106
|
+
includeOptionalUx: process.env.GMTR_ENABLE_OPTIONAL_CLAUDE_UX === '1',
|
|
107
|
+
apply,
|
|
108
|
+
log,
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function doctor(): void {
|
|
113
|
+
const gmtrDir = process.env.GMTR_DIR || join(homedir(), 'gmtr-client');
|
|
114
|
+
const stale = findStaleArtifacts(homedir(), gmtrDir, existsSync);
|
|
115
|
+
for (const line of formatDoctorLines(detectTargets(), gmtrDir, existsSync(gmtrDir), stale)) log(line);
|
|
116
|
+
renderRemoteGuidance();
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
function upgrade(): void {
|
|
120
|
+
const homeDir = homedir();
|
|
121
|
+
const clientDir = process.env.GMTR_DIR || join(homeDir, 'gmtr-client');
|
|
122
|
+
const stale = findStaleArtifacts(homeDir, clientDir, existsSync);
|
|
123
|
+
if (stale.length > 0) {
|
|
124
|
+
log('Cleaning stale legacy artifacts before upgrade...');
|
|
125
|
+
runLegacyMigration({
|
|
126
|
+
homeDir,
|
|
127
|
+
clientDir,
|
|
128
|
+
includeOptionalUx: process.env.GMTR_ENABLE_OPTIONAL_CLAUDE_UX === '1',
|
|
129
|
+
apply: true,
|
|
130
|
+
log,
|
|
131
|
+
});
|
|
132
|
+
log('');
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
const installed = summarizeDetectedLocalTargets();
|
|
136
|
+
if (installed.length === 0) {
|
|
137
|
+
log('No detected local targets to upgrade.');
|
|
138
|
+
renderRemoteGuidance();
|
|
139
|
+
return;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
for (const target of installed) {
|
|
143
|
+
log(`Upgrading ${target}...`);
|
|
144
|
+
installTarget(target);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
renderRemoteGuidance();
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
function interactiveInstall(): void {
|
|
151
|
+
renderDetections();
|
|
152
|
+
log('');
|
|
153
|
+
const targetIds = renderInstallMenu();
|
|
154
|
+
const defaultChoice = summarizeDetectedLocalTargets()[0] || 'claude-code';
|
|
155
|
+
const answer = prompt(`Install target(s) [${defaultChoice}]: `) || defaultChoice;
|
|
156
|
+
const selections = resolveInteractiveSelection(
|
|
157
|
+
answer,
|
|
158
|
+
targetIds,
|
|
159
|
+
summarizeDetectedLocalTargets(),
|
|
160
|
+
(id) => findTarget(id) as { id: IntegrationTargetId; kind: 'local' | 'remote' } | undefined,
|
|
161
|
+
);
|
|
162
|
+
if (selections.length === 0) {
|
|
163
|
+
throw new Error('No install targets selected');
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
for (const selection of selections) {
|
|
167
|
+
installTarget(selection);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
renderRemoteGuidance();
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
function main(): void {
|
|
174
|
+
const [, , command = 'install', targetArg] = process.argv;
|
|
175
|
+
|
|
176
|
+
switch (command) {
|
|
177
|
+
case 'install':
|
|
178
|
+
if (targetArg === '--detect') {
|
|
179
|
+
renderDetections();
|
|
180
|
+
return;
|
|
181
|
+
}
|
|
182
|
+
if (targetArg) {
|
|
183
|
+
if (targetArg === 'all') {
|
|
184
|
+
const targets = summarizeDetectedLocalTargets();
|
|
185
|
+
if (targets.length === 0) {
|
|
186
|
+
log('No detected local targets to install.');
|
|
187
|
+
return;
|
|
188
|
+
}
|
|
189
|
+
for (const target of targets) installTarget(target);
|
|
190
|
+
return;
|
|
191
|
+
}
|
|
192
|
+
const target = findTarget(targetArg);
|
|
193
|
+
if (!target) throw new Error(`Unknown target: ${targetArg}`);
|
|
194
|
+
installTarget(target.id);
|
|
195
|
+
return;
|
|
196
|
+
}
|
|
197
|
+
interactiveInstall();
|
|
198
|
+
return;
|
|
199
|
+
case 'detect':
|
|
200
|
+
renderDetections();
|
|
201
|
+
return;
|
|
202
|
+
case 'doctor':
|
|
203
|
+
doctor();
|
|
204
|
+
return;
|
|
205
|
+
case 'migrate':
|
|
206
|
+
migrate(targetArg === '--apply');
|
|
207
|
+
return;
|
|
208
|
+
case 'upgrade':
|
|
209
|
+
upgrade();
|
|
210
|
+
return;
|
|
211
|
+
case '--help':
|
|
212
|
+
case '-h':
|
|
213
|
+
case 'help':
|
|
214
|
+
log('gramatr — your cross-agent AI brain');
|
|
215
|
+
log('');
|
|
216
|
+
log('Commands:');
|
|
217
|
+
log(' install [target] Install gramatr (claude-code, codex, gemini-cli, all)');
|
|
218
|
+
log(' detect Show detected CLI platforms');
|
|
219
|
+
log(' doctor Check installation health');
|
|
220
|
+
log(' upgrade Upgrade all installed targets');
|
|
221
|
+
log(' migrate [--apply] Clean up legacy artifacts');
|
|
222
|
+
log(' help Show this help');
|
|
223
|
+
log('');
|
|
224
|
+
log('Examples:');
|
|
225
|
+
log(' npx gramatr install # Interactive target selection');
|
|
226
|
+
log(' npx gramatr install claude-code # Install for Claude Code');
|
|
227
|
+
log(' npx gramatr install all # Install all detected targets');
|
|
228
|
+
return;
|
|
229
|
+
case '--version':
|
|
230
|
+
case '-v':
|
|
231
|
+
try {
|
|
232
|
+
const { VERSION } = require('../core/version.ts');
|
|
233
|
+
log(VERSION);
|
|
234
|
+
} catch {
|
|
235
|
+
log('0.3.0');
|
|
236
|
+
}
|
|
237
|
+
return;
|
|
238
|
+
default:
|
|
239
|
+
log(`Unknown command: ${command}. Run 'gramatr help' for usage.`);
|
|
240
|
+
process.exit(1);
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
// import.meta.main is Bun-only; for Node/tsx, always run when executed directly
|
|
245
|
+
const isMain = (import.meta as any).main ?? true;
|
|
246
|
+
if (isMain) {
|
|
247
|
+
main();
|
|
248
|
+
}
|