@gramatr/client 0.5.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/AGENTS.md +17 -0
- package/CLAUDE.md +18 -0
- package/README.md +108 -0
- package/bin/add-api-key.ts +264 -0
- package/bin/clean-legacy-install.ts +28 -0
- package/bin/clear-creds.ts +141 -0
- package/bin/get-token.py +3 -0
- package/bin/gmtr-login.ts +599 -0
- package/bin/gramatr.js +36 -0
- package/bin/gramatr.ts +374 -0
- package/bin/install.ts +716 -0
- package/bin/lib/config.ts +57 -0
- package/bin/lib/git.ts +111 -0
- package/bin/lib/stdin.ts +53 -0
- package/bin/logout.ts +76 -0
- package/bin/render-claude-hooks.ts +16 -0
- package/bin/statusline.ts +81 -0
- package/bin/uninstall.ts +289 -0
- package/chatgpt/README.md +95 -0
- package/chatgpt/install.ts +140 -0
- package/chatgpt/lib/chatgpt-install-utils.ts +89 -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 +79 -0
- package/codex/install.ts +116 -0
- package/codex/lib/codex-hook-utils.ts +48 -0
- package/codex/lib/codex-install-utils.ts +123 -0
- package/core/auth.ts +170 -0
- package/core/feedback.ts +55 -0
- package/core/formatting.ts +179 -0
- package/core/install.ts +107 -0
- package/core/installer-cli.ts +122 -0
- package/core/migration.ts +479 -0
- package/core/routing.ts +108 -0
- package/core/session.ts +202 -0
- package/core/targets.ts +292 -0
- package/core/types.ts +179 -0
- package/core/version-check.ts +219 -0
- package/core/version.ts +47 -0
- package/desktop/README.md +72 -0
- package/desktop/build-mcpb.ts +166 -0
- package/desktop/install.ts +136 -0
- package/desktop/lib/desktop-install-utils.ts +70 -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 +77 -0
- package/gemini/install.ts +281 -0
- package/gemini/lib/gemini-hook-utils.ts +63 -0
- package/gemini/lib/gemini-install-utils.ts +169 -0
- package/hooks/GMTRPromptEnricher.hook.ts +651 -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 +770 -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 +501 -0
- package/package.json +63 -0
package/core/session.ts
ADDED
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
import { existsSync, mkdirSync, writeFileSync } from 'fs';
|
|
2
|
+
import { basename, join } from 'path';
|
|
3
|
+
import {
|
|
4
|
+
callMcpTool,
|
|
5
|
+
createDefaultConfig,
|
|
6
|
+
deriveProjectId,
|
|
7
|
+
type GitContext,
|
|
8
|
+
type GmtrConfig,
|
|
9
|
+
migrateConfig,
|
|
10
|
+
now,
|
|
11
|
+
readGmtrConfig,
|
|
12
|
+
writeGmtrConfig,
|
|
13
|
+
} from '../hooks/lib/gmtr-hook-utils.ts';
|
|
14
|
+
import type { HandoffResponse, SessionStartResponse } from './types.ts';
|
|
15
|
+
|
|
16
|
+
export interface PreparedProjectSession {
|
|
17
|
+
projectId: string;
|
|
18
|
+
config: GmtrConfig;
|
|
19
|
+
projectEntityId: string | null;
|
|
20
|
+
restoreNeeded: boolean;
|
|
21
|
+
hasRestoreContext: boolean;
|
|
22
|
+
created: boolean;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export interface CurrentProjectContextPayload {
|
|
26
|
+
type: 'git_project' | 'non_git';
|
|
27
|
+
session_id: string;
|
|
28
|
+
project_name: string;
|
|
29
|
+
working_directory: string;
|
|
30
|
+
session_start: string;
|
|
31
|
+
gmtr_config_path: string;
|
|
32
|
+
project_entity_id: string | null;
|
|
33
|
+
action_required: string;
|
|
34
|
+
project_id?: string;
|
|
35
|
+
git_root?: string;
|
|
36
|
+
git_branch?: string;
|
|
37
|
+
git_commit?: string;
|
|
38
|
+
git_remote?: string;
|
|
39
|
+
restore_needed?: boolean;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export function normalizeSessionStartResponse(
|
|
43
|
+
response: SessionStartResponse | null | undefined,
|
|
44
|
+
): {
|
|
45
|
+
interactionId: string | null;
|
|
46
|
+
entityId: string | null;
|
|
47
|
+
resumed: boolean;
|
|
48
|
+
handoffContext: string | null;
|
|
49
|
+
} {
|
|
50
|
+
return {
|
|
51
|
+
interactionId: response?.interaction_id || response?.interactionId || null,
|
|
52
|
+
entityId: response?.entity_id || response?.entityId || null,
|
|
53
|
+
resumed: response?.interaction_resumed === true || response?.interactionResumed === true,
|
|
54
|
+
handoffContext: response?.handoff_context || response?.handoffContext || null,
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export function prepareProjectSessionState(options: {
|
|
59
|
+
git: GitContext;
|
|
60
|
+
sessionId: string;
|
|
61
|
+
transcriptPath: string;
|
|
62
|
+
}): PreparedProjectSession {
|
|
63
|
+
const { git, sessionId, transcriptPath } = options;
|
|
64
|
+
const projectId = deriveProjectId(git.remote, git.projectName);
|
|
65
|
+
const timestamp = now();
|
|
66
|
+
|
|
67
|
+
let config = readGmtrConfig(git.root);
|
|
68
|
+
let created = false;
|
|
69
|
+
|
|
70
|
+
if (config) {
|
|
71
|
+
config = migrateConfig(config, projectId);
|
|
72
|
+
config.last_session_id = sessionId;
|
|
73
|
+
config.project_id = projectId;
|
|
74
|
+
config.current_session = {
|
|
75
|
+
...config.current_session,
|
|
76
|
+
session_id: sessionId,
|
|
77
|
+
transcript_path: transcriptPath,
|
|
78
|
+
last_updated: timestamp,
|
|
79
|
+
token_limit: 200000,
|
|
80
|
+
};
|
|
81
|
+
config.continuity_stats = config.continuity_stats || {};
|
|
82
|
+
config.continuity_stats.total_sessions = (config.continuity_stats.total_sessions || 0) + 1;
|
|
83
|
+
config.metadata = config.metadata || {};
|
|
84
|
+
config.metadata.updated = timestamp;
|
|
85
|
+
} else {
|
|
86
|
+
config = createDefaultConfig({
|
|
87
|
+
projectId,
|
|
88
|
+
projectName: git.projectName,
|
|
89
|
+
gitRemote: git.remote,
|
|
90
|
+
sessionId,
|
|
91
|
+
transcriptPath,
|
|
92
|
+
});
|
|
93
|
+
created = true;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
writeGmtrConfig(git.root, config);
|
|
97
|
+
|
|
98
|
+
return {
|
|
99
|
+
projectId,
|
|
100
|
+
config,
|
|
101
|
+
projectEntityId: config.project_entity_id || null,
|
|
102
|
+
restoreNeeded: config.last_compact?.timestamp != null,
|
|
103
|
+
hasRestoreContext: config.last_compact?.summary != null,
|
|
104
|
+
created,
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
export async function startRemoteSession(options: {
|
|
109
|
+
clientType: string;
|
|
110
|
+
sessionId?: string;
|
|
111
|
+
projectId: string;
|
|
112
|
+
projectName?: string;
|
|
113
|
+
gitRemote: string;
|
|
114
|
+
directory: string;
|
|
115
|
+
}): Promise<SessionStartResponse | null> {
|
|
116
|
+
return (await callMcpTool(
|
|
117
|
+
'gmtr_session_start',
|
|
118
|
+
{
|
|
119
|
+
client_type: options.clientType,
|
|
120
|
+
project_id: options.projectId,
|
|
121
|
+
...(options.projectName ? { project_name: options.projectName } : {}),
|
|
122
|
+
git_remote: options.gitRemote,
|
|
123
|
+
directory: options.directory,
|
|
124
|
+
...(options.sessionId ? { session_id: options.sessionId } : {}),
|
|
125
|
+
},
|
|
126
|
+
15000,
|
|
127
|
+
)) as SessionStartResponse | null;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
export async function loadProjectHandoff(projectId: string): Promise<HandoffResponse | null> {
|
|
131
|
+
return (await callMcpTool(
|
|
132
|
+
'gmtr_load_handoff',
|
|
133
|
+
{ project_id: projectId },
|
|
134
|
+
15000,
|
|
135
|
+
)) as HandoffResponse | null;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
export function persistSessionRegistration(rootDir: string, response: SessionStartResponse | null): GmtrConfig | null {
|
|
139
|
+
const normalized = normalizeSessionStartResponse(response);
|
|
140
|
+
if (!normalized.interactionId && !normalized.entityId) return readGmtrConfig(rootDir);
|
|
141
|
+
|
|
142
|
+
const config = readGmtrConfig(rootDir);
|
|
143
|
+
if (!config) return null;
|
|
144
|
+
config.current_session = config.current_session || {};
|
|
145
|
+
if (normalized.interactionId) config.current_session.interaction_id = normalized.interactionId;
|
|
146
|
+
if (normalized.entityId) config.current_session.gmtr_entity_id = normalized.entityId;
|
|
147
|
+
writeGmtrConfig(rootDir, config);
|
|
148
|
+
return config;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
export function writeCurrentProjectContextFile(payload: CurrentProjectContextPayload): void {
|
|
152
|
+
const home = process.env.HOME || process.env.USERPROFILE || '';
|
|
153
|
+
const claudeDir = join(home, '.claude');
|
|
154
|
+
if (!existsSync(claudeDir)) mkdirSync(claudeDir, { recursive: true });
|
|
155
|
+
const contextFile = join(claudeDir, 'current-project-context.json');
|
|
156
|
+
writeFileSync(contextFile, JSON.stringify(payload, null, 2) + '\n');
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
export function buildGitProjectContextPayload(options: {
|
|
160
|
+
git: GitContext;
|
|
161
|
+
sessionId: string;
|
|
162
|
+
workingDirectory: string;
|
|
163
|
+
sessionStart: string;
|
|
164
|
+
projectId: string;
|
|
165
|
+
projectEntityId: string | null;
|
|
166
|
+
restoreNeeded: boolean;
|
|
167
|
+
}): CurrentProjectContextPayload {
|
|
168
|
+
return {
|
|
169
|
+
type: 'git_project',
|
|
170
|
+
session_id: options.sessionId,
|
|
171
|
+
project_name: options.git.projectName,
|
|
172
|
+
project_id: options.projectId,
|
|
173
|
+
git_root: options.git.root,
|
|
174
|
+
git_branch: options.git.branch,
|
|
175
|
+
git_commit: options.git.commit,
|
|
176
|
+
git_remote: options.git.remote,
|
|
177
|
+
working_directory: options.workingDirectory,
|
|
178
|
+
session_start: options.sessionStart,
|
|
179
|
+
gmtr_config_path: join(options.git.root, '.gmtr', 'settings.json'),
|
|
180
|
+
project_entity_id: options.projectEntityId,
|
|
181
|
+
restore_needed: options.restoreNeeded,
|
|
182
|
+
action_required: 'check_or_create_project_entity',
|
|
183
|
+
};
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
export function buildNonGitProjectContextPayload(options: {
|
|
187
|
+
cwd: string;
|
|
188
|
+
sessionId: string;
|
|
189
|
+
sessionStart: string;
|
|
190
|
+
projectEntityId: string | null;
|
|
191
|
+
}): CurrentProjectContextPayload {
|
|
192
|
+
return {
|
|
193
|
+
type: 'non_git',
|
|
194
|
+
session_id: options.sessionId,
|
|
195
|
+
project_name: basename(options.cwd),
|
|
196
|
+
working_directory: options.cwd,
|
|
197
|
+
session_start: options.sessionStart,
|
|
198
|
+
gmtr_config_path: join(options.cwd, '.gmtr', 'settings.json'),
|
|
199
|
+
project_entity_id: options.projectEntityId,
|
|
200
|
+
action_required: 'gmtr_init_needed',
|
|
201
|
+
};
|
|
202
|
+
}
|
package/core/targets.ts
ADDED
|
@@ -0,0 +1,292 @@
|
|
|
1
|
+
import { existsSync, readFileSync } from 'fs';
|
|
2
|
+
import { homedir } from 'os';
|
|
3
|
+
import { join } from 'path';
|
|
4
|
+
|
|
5
|
+
export type IntegrationTargetId =
|
|
6
|
+
| 'claude-code'
|
|
7
|
+
| 'claude-desktop'
|
|
8
|
+
| 'chatgpt-desktop'
|
|
9
|
+
| 'codex'
|
|
10
|
+
| 'gemini-cli'
|
|
11
|
+
| 'remote-mcp'
|
|
12
|
+
| 'claude-web'
|
|
13
|
+
| 'chatgpt-web';
|
|
14
|
+
|
|
15
|
+
export interface IntegrationTarget {
|
|
16
|
+
id: IntegrationTargetId;
|
|
17
|
+
label: string;
|
|
18
|
+
kind: 'local' | 'remote';
|
|
19
|
+
description: string;
|
|
20
|
+
detect: () => DetectionResult;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export interface DetectionResult {
|
|
24
|
+
detected: boolean;
|
|
25
|
+
location?: string;
|
|
26
|
+
details?: string;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export interface DetectionContext {
|
|
30
|
+
exists: (path: string) => boolean;
|
|
31
|
+
readText: (path: string) => string;
|
|
32
|
+
home: string;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function defaultContext(): DetectionContext {
|
|
36
|
+
return {
|
|
37
|
+
exists: (path) => existsSync(path),
|
|
38
|
+
readText: (path) => (existsSync(path) ? readFileSync(path, 'utf8') : ''),
|
|
39
|
+
home: homedir(),
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function homePath(ctx: DetectionContext, ...segments: string[]): string {
|
|
44
|
+
return join(ctx.home, ...segments);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function detectClaudeCode(ctx: DetectionContext): DetectionResult {
|
|
48
|
+
const claudeDir = homePath(ctx, '.claude');
|
|
49
|
+
if (!ctx.exists(claudeDir)) {
|
|
50
|
+
return { detected: false, details: '~/.claude not found' };
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const settingsPath = join(claudeDir, 'settings.json');
|
|
54
|
+
return {
|
|
55
|
+
detected: true,
|
|
56
|
+
location: claudeDir,
|
|
57
|
+
details: ctx.exists(settingsPath) ? settingsPath : 'Claude directory exists',
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function detectCodex(ctx: DetectionContext): DetectionResult {
|
|
62
|
+
const codexDir = homePath(ctx, '.codex');
|
|
63
|
+
if (!ctx.exists(codexDir)) {
|
|
64
|
+
return { detected: false, details: '~/.codex not found' };
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const hooksPath = join(codexDir, 'hooks.json');
|
|
68
|
+
const configPath = join(codexDir, 'config.toml');
|
|
69
|
+
const details = [hooksPath, configPath].filter((path) => ctx.exists(path)).join(', ') || codexDir;
|
|
70
|
+
|
|
71
|
+
return {
|
|
72
|
+
detected: true,
|
|
73
|
+
location: codexDir,
|
|
74
|
+
details,
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function detectGeminiCli(ctx: DetectionContext): DetectionResult {
|
|
79
|
+
const geminiDir = homePath(ctx, '.gemini');
|
|
80
|
+
if (!ctx.exists(geminiDir)) {
|
|
81
|
+
return { detected: false, details: '~/.gemini not found' };
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
const settingsPath = join(geminiDir, 'settings.json');
|
|
85
|
+
const extensionsDir = join(geminiDir, 'extensions');
|
|
86
|
+
const details = [settingsPath, extensionsDir].filter((path) => ctx.exists(path)).join(', ') || geminiDir;
|
|
87
|
+
|
|
88
|
+
return {
|
|
89
|
+
detected: true,
|
|
90
|
+
location: geminiDir,
|
|
91
|
+
details,
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function detectClaudeDesktop(ctx: DetectionContext): DetectionResult {
|
|
96
|
+
// macOS path — Claude Desktop is only available on macOS and Windows
|
|
97
|
+
const macConfigPath = join(
|
|
98
|
+
ctx.home,
|
|
99
|
+
'Library',
|
|
100
|
+
'Application Support',
|
|
101
|
+
'Claude',
|
|
102
|
+
'claude_desktop_config.json',
|
|
103
|
+
);
|
|
104
|
+
// Windows path — APPDATA is typically <home>\AppData\Roaming
|
|
105
|
+
const winConfigPath = join(ctx.home, 'AppData', 'Roaming', 'Claude', 'claude_desktop_config.json');
|
|
106
|
+
|
|
107
|
+
if (ctx.exists(macConfigPath)) {
|
|
108
|
+
return {
|
|
109
|
+
detected: true,
|
|
110
|
+
location: macConfigPath,
|
|
111
|
+
details: 'Claude Desktop config found (macOS)',
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
if (ctx.exists(winConfigPath)) {
|
|
116
|
+
return {
|
|
117
|
+
detected: true,
|
|
118
|
+
location: winConfigPath,
|
|
119
|
+
details: 'Claude Desktop config found (Windows)',
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
return {
|
|
124
|
+
detected: false,
|
|
125
|
+
details: 'Claude Desktop config not found',
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
function detectChatGPTDesktop(ctx: DetectionContext): DetectionResult {
|
|
130
|
+
// macOS path — ChatGPT Desktop uses ~/.chatgpt/mcp.json
|
|
131
|
+
const macConfigDir = join(ctx.home, '.chatgpt');
|
|
132
|
+
const macConfigPath = join(macConfigDir, 'mcp.json');
|
|
133
|
+
|
|
134
|
+
// Windows path — APPDATA is typically <home>\AppData\Roaming
|
|
135
|
+
const winConfigDir = join(ctx.home, 'AppData', 'Roaming', 'ChatGPT');
|
|
136
|
+
const winConfigPath = join(winConfigDir, 'mcp.json');
|
|
137
|
+
|
|
138
|
+
if (ctx.exists(macConfigPath)) {
|
|
139
|
+
return {
|
|
140
|
+
detected: true,
|
|
141
|
+
location: macConfigPath,
|
|
142
|
+
details: 'ChatGPT Desktop config found (macOS)',
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
if (ctx.exists(macConfigDir)) {
|
|
147
|
+
return {
|
|
148
|
+
detected: true,
|
|
149
|
+
location: macConfigDir,
|
|
150
|
+
details: 'ChatGPT Desktop directory found (macOS, no mcp.json yet)',
|
|
151
|
+
};
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
if (ctx.exists(winConfigPath)) {
|
|
155
|
+
return {
|
|
156
|
+
detected: true,
|
|
157
|
+
location: winConfigPath,
|
|
158
|
+
details: 'ChatGPT Desktop config found (Windows)',
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
if (ctx.exists(winConfigDir)) {
|
|
163
|
+
return {
|
|
164
|
+
detected: true,
|
|
165
|
+
location: winConfigDir,
|
|
166
|
+
details: 'ChatGPT Desktop directory found (Windows, no mcp.json yet)',
|
|
167
|
+
};
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
return {
|
|
171
|
+
detected: false,
|
|
172
|
+
details: 'ChatGPT Desktop config not found',
|
|
173
|
+
};
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
function detectRemoteTarget(
|
|
177
|
+
kind: 'claude-web' | 'chatgpt-web' | 'remote-mcp',
|
|
178
|
+
ctx: DetectionContext,
|
|
179
|
+
): DetectionResult {
|
|
180
|
+
const claudeJson = ctx.readText(homePath(ctx, '.claude.json'));
|
|
181
|
+
if (claudeJson.includes('gramatr') || claudeJson.includes('mcpServers')) {
|
|
182
|
+
return {
|
|
183
|
+
detected: true,
|
|
184
|
+
location: homePath(ctx, '.claude.json'),
|
|
185
|
+
details: 'Remote MCP-capable configuration file found',
|
|
186
|
+
};
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
return {
|
|
190
|
+
detected: false,
|
|
191
|
+
details:
|
|
192
|
+
kind === 'remote-mcp'
|
|
193
|
+
? 'Remote MCP setup is available but not configured locally'
|
|
194
|
+
: `${kind} is a hosted target and requires remote MCP setup`,
|
|
195
|
+
};
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
export const INTEGRATION_TARGETS: IntegrationTarget[] = [
|
|
199
|
+
{
|
|
200
|
+
id: 'claude-code',
|
|
201
|
+
label: 'Claude Code',
|
|
202
|
+
kind: 'local',
|
|
203
|
+
description: 'Local Claude Code hook integration',
|
|
204
|
+
detect: () => detectClaudeCode(defaultContext()),
|
|
205
|
+
},
|
|
206
|
+
{
|
|
207
|
+
id: 'claude-desktop',
|
|
208
|
+
label: 'Claude Desktop',
|
|
209
|
+
kind: 'local',
|
|
210
|
+
description: 'Claude Desktop app with MCP server support (Tier 3 — MCP only, no hooks)',
|
|
211
|
+
detect: () => detectClaudeDesktop(defaultContext()),
|
|
212
|
+
},
|
|
213
|
+
{
|
|
214
|
+
id: 'chatgpt-desktop',
|
|
215
|
+
label: 'ChatGPT Desktop',
|
|
216
|
+
kind: 'local',
|
|
217
|
+
description: 'ChatGPT Desktop app with MCP server support (Tier 3 — MCP only, no hooks)',
|
|
218
|
+
detect: () => detectChatGPTDesktop(defaultContext()),
|
|
219
|
+
},
|
|
220
|
+
{
|
|
221
|
+
id: 'codex',
|
|
222
|
+
label: 'Codex',
|
|
223
|
+
kind: 'local',
|
|
224
|
+
description: 'Local Codex hook integration',
|
|
225
|
+
detect: () => detectCodex(defaultContext()),
|
|
226
|
+
},
|
|
227
|
+
{
|
|
228
|
+
id: 'gemini-cli',
|
|
229
|
+
label: 'Gemini CLI',
|
|
230
|
+
kind: 'local',
|
|
231
|
+
description: 'Local Gemini CLI extension integration',
|
|
232
|
+
detect: () => detectGeminiCli(defaultContext()),
|
|
233
|
+
},
|
|
234
|
+
{
|
|
235
|
+
id: 'remote-mcp',
|
|
236
|
+
label: 'Remote MCP',
|
|
237
|
+
kind: 'remote',
|
|
238
|
+
description: 'Shared remote MCP setup for hosted integrations',
|
|
239
|
+
detect: () => detectRemoteTarget('remote-mcp', defaultContext()),
|
|
240
|
+
},
|
|
241
|
+
{
|
|
242
|
+
id: 'claude-web',
|
|
243
|
+
label: 'Claude Web',
|
|
244
|
+
kind: 'remote',
|
|
245
|
+
description: 'Claude.ai web connector via remote MCP (Settings > Connectors)',
|
|
246
|
+
detect: () => detectRemoteTarget('claude-web', defaultContext()),
|
|
247
|
+
},
|
|
248
|
+
{
|
|
249
|
+
id: 'chatgpt-web',
|
|
250
|
+
label: 'ChatGPT Web',
|
|
251
|
+
kind: 'remote',
|
|
252
|
+
description: 'Hosted ChatGPT target via remote MCP',
|
|
253
|
+
detect: () => detectRemoteTarget('chatgpt-web', defaultContext()),
|
|
254
|
+
},
|
|
255
|
+
];
|
|
256
|
+
|
|
257
|
+
export function listTargets(): IntegrationTarget[] {
|
|
258
|
+
return INTEGRATION_TARGETS;
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
export function findTarget(id: string): IntegrationTarget | undefined {
|
|
262
|
+
return INTEGRATION_TARGETS.find((target) => target.id === id);
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
export function detectTargets(): Array<IntegrationTarget & { detection: DetectionResult }> {
|
|
266
|
+
return detectTargetsWithContext(defaultContext());
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
export function detectTargetsWithContext(
|
|
270
|
+
ctx: DetectionContext,
|
|
271
|
+
): Array<IntegrationTarget & { detection: DetectionResult }> {
|
|
272
|
+
const detectors: Record<string, (ctx: DetectionContext) => DetectionResult> = {
|
|
273
|
+
'claude-code': detectClaudeCode,
|
|
274
|
+
'claude-desktop': detectClaudeDesktop,
|
|
275
|
+
'chatgpt-desktop': detectChatGPTDesktop,
|
|
276
|
+
codex: detectCodex,
|
|
277
|
+
'gemini-cli': detectGeminiCli,
|
|
278
|
+
};
|
|
279
|
+
|
|
280
|
+
return INTEGRATION_TARGETS.map((target) => ({
|
|
281
|
+
...target,
|
|
282
|
+
detection: detectors[target.id]
|
|
283
|
+
? detectors[target.id](ctx)
|
|
284
|
+
: detectRemoteTarget(target.id, ctx),
|
|
285
|
+
}));
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
export function summarizeDetectedLocalTargets(): IntegrationTargetId[] {
|
|
289
|
+
return detectTargets()
|
|
290
|
+
.filter((target) => target.kind === 'local' && target.detection.detected)
|
|
291
|
+
.map((target) => target.id);
|
|
292
|
+
}
|
package/core/types.ts
ADDED
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
export interface RouteClassification {
|
|
2
|
+
effort_level?: string;
|
|
3
|
+
intent_type?: string;
|
|
4
|
+
confidence?: number;
|
|
5
|
+
memory_tier?: string;
|
|
6
|
+
matched_skills?: string[];
|
|
7
|
+
constraints_extracted?: string[];
|
|
8
|
+
reverse_engineering?: {
|
|
9
|
+
explicit_wants?: string[];
|
|
10
|
+
implicit_wants?: string[];
|
|
11
|
+
explicit_dont_wants?: string[];
|
|
12
|
+
implicit_dont_wants?: string[];
|
|
13
|
+
gotchas?: string[];
|
|
14
|
+
};
|
|
15
|
+
suggested_capabilities?: string[];
|
|
16
|
+
isc_scaffold?: string[];
|
|
17
|
+
is_fallback?: boolean;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export interface CapabilityAuditEntry {
|
|
21
|
+
id?: number;
|
|
22
|
+
name?: string;
|
|
23
|
+
section?: string;
|
|
24
|
+
disposition?: string;
|
|
25
|
+
reason?: string;
|
|
26
|
+
phase?: string;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export interface CapabilityAuditResult {
|
|
30
|
+
entries?: CapabilityAuditEntry[];
|
|
31
|
+
use_count?: number;
|
|
32
|
+
decline_count?: number;
|
|
33
|
+
na_count?: number;
|
|
34
|
+
formatted_summary?: string;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export interface PhaseTemplate {
|
|
38
|
+
header?: string;
|
|
39
|
+
time_check?: string;
|
|
40
|
+
voice_message?: string;
|
|
41
|
+
phase_description?: string;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export interface QualityGateRule {
|
|
45
|
+
id?: string;
|
|
46
|
+
name?: string;
|
|
47
|
+
description?: string;
|
|
48
|
+
min_effort?: string | null;
|
|
49
|
+
automated?: boolean;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export interface QualityGateConfig {
|
|
53
|
+
rules?: QualityGateRule[];
|
|
54
|
+
min_criteria?: number;
|
|
55
|
+
anti_required?: boolean;
|
|
56
|
+
word_range?: { min?: number; max?: number };
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export interface ContextPreLoadPlan {
|
|
60
|
+
entity_types?: string[];
|
|
61
|
+
fetch_limits?: Record<string, number>;
|
|
62
|
+
tier?: string;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export interface RouteResponse {
|
|
66
|
+
classification?: RouteClassification;
|
|
67
|
+
statusline_markdown?: string;
|
|
68
|
+
capability_audit?: CapabilityAuditResult;
|
|
69
|
+
phase_template?: PhaseTemplate;
|
|
70
|
+
quality_gate_config?: QualityGateConfig;
|
|
71
|
+
context_pre_load_plan?: ContextPreLoadPlan;
|
|
72
|
+
project_state?: {
|
|
73
|
+
project_id?: string;
|
|
74
|
+
active_prd_id?: string | null;
|
|
75
|
+
active_prd_title?: string | null;
|
|
76
|
+
isc_summary?: { total?: number; passing?: number; failing?: number; pending?: number };
|
|
77
|
+
current_phase?: string | null;
|
|
78
|
+
active_client?: { client_id?: string; client_type?: string } | null;
|
|
79
|
+
last_updated?: string;
|
|
80
|
+
session_history_summary?: string;
|
|
81
|
+
} | null;
|
|
82
|
+
packet_diagnostics?: {
|
|
83
|
+
memory_context?: {
|
|
84
|
+
status?: string;
|
|
85
|
+
requested_types?: string[];
|
|
86
|
+
delivered_count?: number;
|
|
87
|
+
error?: string;
|
|
88
|
+
};
|
|
89
|
+
project_state?: {
|
|
90
|
+
status?: string;
|
|
91
|
+
project_id?: string;
|
|
92
|
+
error?: string;
|
|
93
|
+
};
|
|
94
|
+
};
|
|
95
|
+
curated_context?: string;
|
|
96
|
+
context_references?: Array<{
|
|
97
|
+
id?: string;
|
|
98
|
+
type?: string;
|
|
99
|
+
name?: string;
|
|
100
|
+
}> | string[];
|
|
101
|
+
behavioral_directives?: string[];
|
|
102
|
+
suggested_agents?: Array<{
|
|
103
|
+
display_name?: string;
|
|
104
|
+
model?: string;
|
|
105
|
+
reason?: string;
|
|
106
|
+
}>;
|
|
107
|
+
composed_agents?: Array<{
|
|
108
|
+
name?: string;
|
|
109
|
+
display_name?: string;
|
|
110
|
+
system_prompt?: string;
|
|
111
|
+
expertise_areas?: string[];
|
|
112
|
+
task_domain?: string;
|
|
113
|
+
model_preference?: string;
|
|
114
|
+
context_summary?: string;
|
|
115
|
+
}>;
|
|
116
|
+
memory_context?: {
|
|
117
|
+
total_count?: number;
|
|
118
|
+
results?: Array<{
|
|
119
|
+
entity_name?: string;
|
|
120
|
+
entity_type?: string;
|
|
121
|
+
content?: string;
|
|
122
|
+
similarity?: number;
|
|
123
|
+
}>;
|
|
124
|
+
};
|
|
125
|
+
token_savings?: {
|
|
126
|
+
claude_md_reduction?: number;
|
|
127
|
+
observe_work_offloaded?: number;
|
|
128
|
+
reasoning_tokens_used?: number;
|
|
129
|
+
total_saved?: number;
|
|
130
|
+
tokens_saved?: number;
|
|
131
|
+
savings_ratio?: number;
|
|
132
|
+
};
|
|
133
|
+
execution_summary?: {
|
|
134
|
+
classifier_model?: string; // previously: qwen_model
|
|
135
|
+
classifier_time_ms?: number; // previously: qwen_time_ms
|
|
136
|
+
execution_time_ms?: number;
|
|
137
|
+
server_version?: string;
|
|
138
|
+
stage_timing?: Record<string, number>;
|
|
139
|
+
degraded_components?: string[];
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
export interface SessionStartResponse {
|
|
144
|
+
project_id?: string;
|
|
145
|
+
interaction_id?: string;
|
|
146
|
+
entity_id?: string;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
export interface HandoffMeta {
|
|
150
|
+
saved_at?: string;
|
|
151
|
+
branch?: string;
|
|
152
|
+
session_id?: string;
|
|
153
|
+
conversation_id?: string;
|
|
154
|
+
platform?: string;
|
|
155
|
+
legacy_missing_platform?: boolean;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
export interface HandoffResponse {
|
|
159
|
+
status?: string;
|
|
160
|
+
source?: string;
|
|
161
|
+
project_id?: string;
|
|
162
|
+
session_id?: string;
|
|
163
|
+
branch?: string;
|
|
164
|
+
platform?: string;
|
|
165
|
+
created_at?: string;
|
|
166
|
+
section_count?: number;
|
|
167
|
+
_meta?: HandoffMeta;
|
|
168
|
+
where_we_are?: string;
|
|
169
|
+
what_shipped?: string;
|
|
170
|
+
whats_next?: string;
|
|
171
|
+
key_context?: string;
|
|
172
|
+
dont_forget?: string;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
export interface HookFailure {
|
|
176
|
+
title: string;
|
|
177
|
+
detail: string;
|
|
178
|
+
action?: string;
|
|
179
|
+
}
|