@omnitype-code/cli 0.1.2 → 0.1.3
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/CHANGELOG.md +18 -0
- package/dist/core/ApiClient.js +15 -0
- package/dist/core/ModelDetector.js +43 -9
- package/dist/core/ToolDetector.js +249 -0
- package/dist/core/ToolHookInstallers.js +508 -48
- package/dist/core/TranscriptScanner.js +153 -6
- package/dist/daemon.js +15 -1
- package/dist/index.js +124 -27
- package/package.json +3 -2
- package/scripts/postinstall.js +94 -0
- package/src/core/ApiClient.ts +15 -0
- package/src/core/ModelDetector.ts +43 -9
- package/src/core/ToolDetector.ts +227 -0
- package/src/core/ToolHookInstallers.ts +441 -53
- package/src/core/TranscriptScanner.ts +125 -9
- package/src/daemon.ts +13 -2
- package/src/index.ts +134 -31
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Detects every AI coding tool present on the machine, whether or not OmniType
|
|
3
|
+
* has a hook for it. Used by the doctor command and for market-analysis telemetry.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import * as fs from 'fs';
|
|
7
|
+
import * as os from 'os';
|
|
8
|
+
import * as path from 'path';
|
|
9
|
+
import { execFileSync } from 'child_process';
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* hooked — OmniType has a sentinel hook (deterministic attribution)
|
|
13
|
+
* config-only — No hook API, but model is readable from config/extension (high confidence)
|
|
14
|
+
* no-hook — Detected but no support yet
|
|
15
|
+
*/
|
|
16
|
+
export type HookSupport = 'hooked' | 'config-only' | 'no-hook';
|
|
17
|
+
|
|
18
|
+
export interface KnownTool {
|
|
19
|
+
id: string;
|
|
20
|
+
name: string;
|
|
21
|
+
hookSupport: HookSupport;
|
|
22
|
+
/** Returns true if the tool appears to be installed on this machine. */
|
|
23
|
+
detect: () => boolean;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const HOME = os.homedir();
|
|
27
|
+
|
|
28
|
+
function exists(...parts: string[]): boolean {
|
|
29
|
+
try { fs.accessSync(path.join(...parts)); return true; } catch { return false; }
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function inPath(bin: string): boolean {
|
|
33
|
+
try { execFileSync('which', [bin], { timeout: 500, stdio: 'pipe' }); return true; } catch { return false; }
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function macApp(name: string): boolean {
|
|
37
|
+
if (process.platform !== 'darwin') return false;
|
|
38
|
+
return exists('/Applications', name) || exists(HOME, 'Applications', name);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export const KNOWN_TOOLS: KnownTool[] = [
|
|
42
|
+
// ── Tools with OmniType hooks ────────────────────────────────────────────
|
|
43
|
+
{
|
|
44
|
+
id: 'claude-code',
|
|
45
|
+
name: 'Claude Code',
|
|
46
|
+
hookSupport: 'hooked',
|
|
47
|
+
detect: () => exists(HOME, '.claude') || inPath('claude'),
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
id: 'cursor',
|
|
51
|
+
name: 'Cursor',
|
|
52
|
+
hookSupport: 'hooked',
|
|
53
|
+
detect: () => exists(HOME, '.cursor') || macApp('Cursor.app'),
|
|
54
|
+
},
|
|
55
|
+
{
|
|
56
|
+
id: 'windsurf',
|
|
57
|
+
name: 'Windsurf',
|
|
58
|
+
hookSupport: 'hooked',
|
|
59
|
+
detect: () => exists(HOME, '.codeium') || macApp('Windsurf.app'),
|
|
60
|
+
},
|
|
61
|
+
{
|
|
62
|
+
id: 'codex',
|
|
63
|
+
name: 'Codex CLI',
|
|
64
|
+
hookSupport: 'hooked',
|
|
65
|
+
detect: () => exists(HOME, '.codex') || inPath('codex'),
|
|
66
|
+
},
|
|
67
|
+
{
|
|
68
|
+
id: 'cline',
|
|
69
|
+
name: 'Cline',
|
|
70
|
+
hookSupport: 'hooked',
|
|
71
|
+
detect: () => exists(HOME, 'Documents', 'Cline', 'Hooks') || exists(HOME, 'Documents', 'Cline'),
|
|
72
|
+
},
|
|
73
|
+
|
|
74
|
+
{
|
|
75
|
+
id: 'gemini-cli',
|
|
76
|
+
name: 'Gemini CLI',
|
|
77
|
+
hookSupport: 'hooked',
|
|
78
|
+
detect: () => inPath('gemini') || exists(HOME, '.gemini'),
|
|
79
|
+
},
|
|
80
|
+
{
|
|
81
|
+
id: 'droid',
|
|
82
|
+
name: 'Droid (Factory)',
|
|
83
|
+
hookSupport: 'hooked',
|
|
84
|
+
detect: () => exists(HOME, '.factory'),
|
|
85
|
+
},
|
|
86
|
+
{
|
|
87
|
+
id: 'firebender',
|
|
88
|
+
name: 'Firebender',
|
|
89
|
+
hookSupport: 'hooked',
|
|
90
|
+
detect: () => exists(HOME, '.firebender'),
|
|
91
|
+
},
|
|
92
|
+
{
|
|
93
|
+
id: 'amp',
|
|
94
|
+
name: 'Amp',
|
|
95
|
+
hookSupport: 'hooked',
|
|
96
|
+
detect: () => {
|
|
97
|
+
const xdg = process.env.XDG_DATA_HOME ?? path.join(HOME, '.local', 'share');
|
|
98
|
+
return inPath('amp') || exists(xdg, 'amp') ||
|
|
99
|
+
(process.platform === 'win32' && exists(process.env.LOCALAPPDATA ?? '', 'amp'));
|
|
100
|
+
},
|
|
101
|
+
},
|
|
102
|
+
{
|
|
103
|
+
id: 'opencode',
|
|
104
|
+
name: 'OpenCode',
|
|
105
|
+
hookSupport: 'hooked',
|
|
106
|
+
detect: () => exists(HOME, '.config', 'opencode') || inPath('opencode'),
|
|
107
|
+
},
|
|
108
|
+
{
|
|
109
|
+
id: 'pi',
|
|
110
|
+
name: 'Pi',
|
|
111
|
+
hookSupport: 'hooked',
|
|
112
|
+
detect: () => exists(HOME, '.pi'),
|
|
113
|
+
},
|
|
114
|
+
|
|
115
|
+
// ── Tools detected but no hook yet ──────────────────────────────────────
|
|
116
|
+
{
|
|
117
|
+
id: 'aider',
|
|
118
|
+
name: 'Aider',
|
|
119
|
+
hookSupport: 'no-hook',
|
|
120
|
+
detect: () => inPath('aider') || exists(HOME, '.aider.conf.yml') || exists(HOME, '.aider'),
|
|
121
|
+
},
|
|
122
|
+
{
|
|
123
|
+
id: 'continue',
|
|
124
|
+
name: 'Continue',
|
|
125
|
+
hookSupport: 'no-hook',
|
|
126
|
+
detect: () => exists(HOME, '.continue') || inPath('continue'),
|
|
127
|
+
},
|
|
128
|
+
{
|
|
129
|
+
id: 'antigravity',
|
|
130
|
+
name: 'Antigravity',
|
|
131
|
+
hookSupport: 'no-hook',
|
|
132
|
+
detect: () => macApp('Antigravity.app') || macApp('Google Antigravity.app'),
|
|
133
|
+
},
|
|
134
|
+
{
|
|
135
|
+
id: 'pearai',
|
|
136
|
+
name: 'PearAI',
|
|
137
|
+
hookSupport: 'no-hook',
|
|
138
|
+
detect: () => macApp('PearAI.app') || exists(HOME, '.pearai'),
|
|
139
|
+
},
|
|
140
|
+
{
|
|
141
|
+
id: 'void',
|
|
142
|
+
name: 'Void',
|
|
143
|
+
hookSupport: 'no-hook',
|
|
144
|
+
detect: () => macApp('Void.app') || exists(HOME, '.void'),
|
|
145
|
+
},
|
|
146
|
+
{
|
|
147
|
+
id: 'zed',
|
|
148
|
+
name: 'Zed',
|
|
149
|
+
hookSupport: 'no-hook',
|
|
150
|
+
detect: () =>
|
|
151
|
+
macApp('Zed.app') ||
|
|
152
|
+
(process.platform === 'linux' && (inPath('zed') || exists(HOME, '.config', 'zed'))),
|
|
153
|
+
},
|
|
154
|
+
{
|
|
155
|
+
id: 'roo-cline',
|
|
156
|
+
name: 'Roo Code',
|
|
157
|
+
hookSupport: 'no-hook',
|
|
158
|
+
detect: () => exists(HOME, '.roo') || exists(HOME, '.roo-cline'),
|
|
159
|
+
},
|
|
160
|
+
{
|
|
161
|
+
id: 'tabnine',
|
|
162
|
+
name: 'Tabnine',
|
|
163
|
+
hookSupport: 'no-hook',
|
|
164
|
+
detect: () => exists(HOME, '.tabnine') || exists(HOME, '.config', 'tabnine'),
|
|
165
|
+
},
|
|
166
|
+
{
|
|
167
|
+
id: 'supermaven',
|
|
168
|
+
name: 'Supermaven',
|
|
169
|
+
hookSupport: 'no-hook',
|
|
170
|
+
detect: () => exists(HOME, '.supermaven'),
|
|
171
|
+
},
|
|
172
|
+
{
|
|
173
|
+
id: 'codeium',
|
|
174
|
+
name: 'Codeium',
|
|
175
|
+
hookSupport: 'no-hook',
|
|
176
|
+
// .codeium overlaps with Windsurf — only count if Windsurf isn't the reason
|
|
177
|
+
detect: () => exists(HOME, '.codeium', 'codeium') || exists(HOME, '.codeium', 'config'),
|
|
178
|
+
},
|
|
179
|
+
{
|
|
180
|
+
id: 'copilot',
|
|
181
|
+
name: 'GitHub Copilot',
|
|
182
|
+
// Copilot supports PreToolUse/PostToolUse hooks at ~/.copilot/hooks/
|
|
183
|
+
hookSupport: 'hooked',
|
|
184
|
+
detect: () =>
|
|
185
|
+
exists(HOME, '.copilot') ||
|
|
186
|
+
exists(HOME, '.vscode') ||
|
|
187
|
+
exists(HOME, 'Library', 'Application Support', 'Code') ||
|
|
188
|
+
exists(HOME, '.config', 'github-copilot'),
|
|
189
|
+
},
|
|
190
|
+
{
|
|
191
|
+
id: 'trae',
|
|
192
|
+
name: 'Trae',
|
|
193
|
+
hookSupport: 'no-hook',
|
|
194
|
+
detect: () => macApp('Trae.app') || exists(HOME, '.trae'),
|
|
195
|
+
},
|
|
196
|
+
{
|
|
197
|
+
id: 'goose',
|
|
198
|
+
name: 'Goose',
|
|
199
|
+
hookSupport: 'no-hook',
|
|
200
|
+
detect: () => inPath('goose') || exists(HOME, '.config', 'goose'),
|
|
201
|
+
},
|
|
202
|
+
{
|
|
203
|
+
id: 'openai-codex-legacy',
|
|
204
|
+
name: 'OpenAI Codex (legacy)',
|
|
205
|
+
hookSupport: 'no-hook',
|
|
206
|
+
detect: () => !!process.env.OPENAI_API_KEY && !exists(HOME, '.codex'),
|
|
207
|
+
},
|
|
208
|
+
];
|
|
209
|
+
|
|
210
|
+
export interface DetectedTool {
|
|
211
|
+
tool: KnownTool;
|
|
212
|
+
detected: boolean;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
/** Returns all known tools with a detected flag. */
|
|
216
|
+
export function detectAllTools(): DetectedTool[] {
|
|
217
|
+
return KNOWN_TOOLS.map(tool => {
|
|
218
|
+
let detected = false;
|
|
219
|
+
try { detected = tool.detect(); } catch {}
|
|
220
|
+
return { tool, detected };
|
|
221
|
+
});
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
/** Returns only the tools that are actually installed. */
|
|
225
|
+
export function detectInstalledTools(): KnownTool[] {
|
|
226
|
+
return detectAllTools().filter(r => r.detected).map(r => r.tool);
|
|
227
|
+
}
|