@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
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,24 @@ All notable changes to the **@omnitype-code/cli** package are documented here.
|
|
|
4
4
|
|
|
5
5
|
---
|
|
6
6
|
|
|
7
|
+
## [0.1.3] — 2026-05-16
|
|
8
|
+
|
|
9
|
+
### Added
|
|
10
|
+
- **`omnitype doctor`** — New command showing hook status for every detected tool: `installed`, `stale`, `not-installed`, or `tool-absent`. `--fix` flag auto-installs or upgrades missing hooks. Fires market-analysis telemetry (signed-in users only).
|
|
11
|
+
- **`omnitype status` workspace fix** — Status command now passes `process.cwd()` into model detection, scoping Claude Code transcript scanning to the current workspace. Sessions from other concurrently open projects no longer bleed in.
|
|
12
|
+
- **Per-project sentinel** — Hooks write to `<project_root>/.omnitype/active-model.json` (`process.cwd()` at invocation) in addition to `~/.omnitype/` fallback. `status` and `daemon` check project-local first, eliminating cross-window contamination.
|
|
13
|
+
- **`initWorkspace()` in daemon** — `startDaemon()` creates `.omnitype/` in the watched directory and appends `.omnitype/` to `.gitignore` on first run.
|
|
14
|
+
- **New hooks auto-installed** — Gemini CLI (`~/.gemini/settings.json` BeforeTool/AfterTool), Droid / Factory (`~/.factory/settings.json` PreToolUse), Firebender (`~/.firebender/hooks.json`), GitHub Copilot (`~/.copilot/hooks/omnitype.json`).
|
|
15
|
+
- **New TS plugins** — Amp (`~/.config/amp/plugins/omnitype.ts`), OpenCode (`~/.config/opencode/plugins/omnitype.ts`), Pi (`~/.pi/agent/extensions/omnitype.ts`). Each dual-writes to project-local and global paths.
|
|
16
|
+
- **New transcript readers** — `TranscriptScanner` now covers Continue (`~/.continue/sessions/`), Copilot CLI (`~/.copilot/session-state/`), and Droid (`~/.factory/sessions/`).
|
|
17
|
+
- **`ToolDetector` updated** — Gemini CLI, Droid, Firebender, Amp, OpenCode, and Pi marked `hooked`.
|
|
18
|
+
|
|
19
|
+
### Changed
|
|
20
|
+
- **`HOOK_VERSION` bumped to `v4`** — All stale v3 hooks auto-replaced on next activation.
|
|
21
|
+
- **Attribution Protocol upgraded to v1.2** — See `PROTOCOL.md`.
|
|
22
|
+
|
|
23
|
+
---
|
|
24
|
+
|
|
7
25
|
## [0.1.2] — 2026-05-16
|
|
8
26
|
|
|
9
27
|
### Fixed
|
package/dist/core/ApiClient.js
CHANGED
|
@@ -193,6 +193,21 @@ class ApiClient {
|
|
|
193
193
|
if (failed.length)
|
|
194
194
|
throw new Error(`Push failed for chunks: ${failed.join(', ')}`);
|
|
195
195
|
}
|
|
196
|
+
/**
|
|
197
|
+
* Fire-and-forget: reports which AI tools are detected on this machine.
|
|
198
|
+
* Used for market analysis — understanding which tools users have alongside OmniType.
|
|
199
|
+
* Never throws. Only sends if the user is signed in (respects opt-in via login).
|
|
200
|
+
*/
|
|
201
|
+
reportToolEnvironment(tools) {
|
|
202
|
+
if (!this.isSignedIn)
|
|
203
|
+
return;
|
|
204
|
+
const body = { tools, platform: process.platform, ts: Date.now() };
|
|
205
|
+
fetch(`${this.apiUrl}/telemetry/tools`, {
|
|
206
|
+
method: 'POST',
|
|
207
|
+
headers: { 'Authorization': `Bearer ${this.config.token}`, 'Content-Type': 'application/json' },
|
|
208
|
+
body: JSON.stringify(body),
|
|
209
|
+
}).catch(() => { }); // best-effort, never surface errors
|
|
210
|
+
}
|
|
196
211
|
async _postWithRetry(url, body, attempts, label) {
|
|
197
212
|
const compressed = await _gzip(Buffer.from(JSON.stringify(body)));
|
|
198
213
|
for (let i = 0; i < attempts; i++) {
|
|
@@ -40,9 +40,11 @@ const path = __importStar(require("path"));
|
|
|
40
40
|
const child_process_1 = require("child_process");
|
|
41
41
|
const TranscriptScanner_1 = require("./TranscriptScanner");
|
|
42
42
|
const UNKNOWN = { model: 'unknown', tool: 'unknown', confidence: 'low' };
|
|
43
|
-
const SENTINEL_MAX_AGE_MS = 30000;
|
|
44
|
-
const
|
|
45
|
-
|
|
43
|
+
const SENTINEL_MAX_AGE_MS = 30000;
|
|
44
|
+
const GLOBAL_SENTINEL_PATH = path.join(os.homedir(), '.omnitype', 'active-model.json');
|
|
45
|
+
function projectSentinelPath(cwd) {
|
|
46
|
+
return path.join(cwd ?? process.cwd(), '.omnitype', 'active-model.json');
|
|
47
|
+
}
|
|
46
48
|
// Matches model identifier strings from all major providers.
|
|
47
49
|
const MODEL_PATTERN = /\b(claude-[\w.-]+|gpt-[\w.-]+|o[134](?:-[\w.-]+)?|gemini-[\w.-]+|gemma[\w.-]*|llama-[\w.-]+|mistral[\w.-]*|codestral[\w.-]*|deepseek[\w.-]*|qwen[\w.-]+|command[\w.-]*|phi[\w.-]+|grok[\w.-]*|kimi[\w.-]*|moonshot[\w.-]*)\b/i;
|
|
48
50
|
// Each tool lists its OWN env vars only. Shared vars (OPENAI_MODEL) are NOT duplicated
|
|
@@ -72,18 +74,18 @@ const PROC_MAP = [
|
|
|
72
74
|
{ match: 'tabby', tool: 'tabby' },
|
|
73
75
|
];
|
|
74
76
|
class ModelDetector {
|
|
75
|
-
detect(changedFilePath) {
|
|
76
|
-
return (this._sentinel(
|
|
77
|
-
this._sentinel(
|
|
77
|
+
detect(changedFilePath, cwd) {
|
|
78
|
+
return (this._sentinel(projectSentinelPath(cwd), changedFilePath) ??
|
|
79
|
+
this._sentinel(GLOBAL_SENTINEL_PATH, changedFilePath) ??
|
|
78
80
|
this._fromEnv() ??
|
|
79
|
-
this._fromTranscripts() ??
|
|
81
|
+
this._fromTranscripts(cwd) ??
|
|
80
82
|
this._fromIdeConfigs() ??
|
|
81
83
|
this._fromPs() ??
|
|
82
84
|
(changedFilePath ? this._fromLsof(changedFilePath) : undefined) ??
|
|
83
85
|
UNKNOWN);
|
|
84
86
|
}
|
|
85
|
-
_fromTranscripts() {
|
|
86
|
-
const r = (0, TranscriptScanner_1.scanTranscripts)();
|
|
87
|
+
_fromTranscripts(cwd) {
|
|
88
|
+
const r = (0, TranscriptScanner_1.scanTranscripts)(cwd);
|
|
87
89
|
if (!r)
|
|
88
90
|
return undefined;
|
|
89
91
|
return { model: r.model, tool: r.tool, confidence: 'high' };
|
|
@@ -130,6 +132,38 @@ class ModelDetector {
|
|
|
130
132
|
if (scan)
|
|
131
133
|
return { model: scan.model, tool: appName.toLowerCase(), confidence: scan.isAuto ? 'medium' : 'high' };
|
|
132
134
|
}
|
|
135
|
+
// Copilot: no hook API, but model is readable from VS Code / fork settings.json
|
|
136
|
+
const copilot = this._fromCopilotConfig();
|
|
137
|
+
if (copilot)
|
|
138
|
+
return copilot;
|
|
139
|
+
return undefined;
|
|
140
|
+
}
|
|
141
|
+
_fromCopilotConfig() {
|
|
142
|
+
const COPILOT_KEYS = [
|
|
143
|
+
'github.copilot.advanced.model',
|
|
144
|
+
'github.copilot.selectedModel',
|
|
145
|
+
'github.copilot.chat.models.default',
|
|
146
|
+
'github.copilot.chat.agent.model',
|
|
147
|
+
];
|
|
148
|
+
// Check VS Code and every known fork's settings.json
|
|
149
|
+
const vscodePaths = (() => {
|
|
150
|
+
switch (process.platform) {
|
|
151
|
+
case 'darwin': return [path.join(os.homedir(), 'Library', 'Application Support', 'Code', 'User', 'settings.json')];
|
|
152
|
+
case 'win32': return [path.join(process.env.APPDATA ?? '', 'Code', 'User', 'settings.json')];
|
|
153
|
+
default: return [path.join(os.homedir(), '.config', 'Code', 'User', 'settings.json')];
|
|
154
|
+
}
|
|
155
|
+
})();
|
|
156
|
+
for (const settingsPath of vscodePaths) {
|
|
157
|
+
try {
|
|
158
|
+
const flat = this._flatten(JSON.parse(fs.readFileSync(settingsPath, 'utf8')));
|
|
159
|
+
for (const key of COPILOT_KEYS) {
|
|
160
|
+
const val = flat[key];
|
|
161
|
+
if (typeof val === 'string' && val && MODEL_PATTERN.test(val))
|
|
162
|
+
return { model: val.toLowerCase(), tool: 'copilot', confidence: 'high' };
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
catch { }
|
|
166
|
+
}
|
|
133
167
|
return undefined;
|
|
134
168
|
}
|
|
135
169
|
_fromPs() {
|
|
@@ -0,0 +1,249 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Detects every AI coding tool present on the machine, whether or not OmniType
|
|
4
|
+
* has a hook for it. Used by the doctor command and for market-analysis telemetry.
|
|
5
|
+
*/
|
|
6
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
7
|
+
if (k2 === undefined) k2 = k;
|
|
8
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
9
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
10
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
11
|
+
}
|
|
12
|
+
Object.defineProperty(o, k2, desc);
|
|
13
|
+
}) : (function(o, m, k, k2) {
|
|
14
|
+
if (k2 === undefined) k2 = k;
|
|
15
|
+
o[k2] = m[k];
|
|
16
|
+
}));
|
|
17
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
18
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
19
|
+
}) : function(o, v) {
|
|
20
|
+
o["default"] = v;
|
|
21
|
+
});
|
|
22
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
23
|
+
var ownKeys = function(o) {
|
|
24
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
25
|
+
var ar = [];
|
|
26
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
27
|
+
return ar;
|
|
28
|
+
};
|
|
29
|
+
return ownKeys(o);
|
|
30
|
+
};
|
|
31
|
+
return function (mod) {
|
|
32
|
+
if (mod && mod.__esModule) return mod;
|
|
33
|
+
var result = {};
|
|
34
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
35
|
+
__setModuleDefault(result, mod);
|
|
36
|
+
return result;
|
|
37
|
+
};
|
|
38
|
+
})();
|
|
39
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
40
|
+
exports.KNOWN_TOOLS = void 0;
|
|
41
|
+
exports.detectAllTools = detectAllTools;
|
|
42
|
+
exports.detectInstalledTools = detectInstalledTools;
|
|
43
|
+
const fs = __importStar(require("fs"));
|
|
44
|
+
const os = __importStar(require("os"));
|
|
45
|
+
const path = __importStar(require("path"));
|
|
46
|
+
const child_process_1 = require("child_process");
|
|
47
|
+
const HOME = os.homedir();
|
|
48
|
+
function exists(...parts) {
|
|
49
|
+
try {
|
|
50
|
+
fs.accessSync(path.join(...parts));
|
|
51
|
+
return true;
|
|
52
|
+
}
|
|
53
|
+
catch {
|
|
54
|
+
return false;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
function inPath(bin) {
|
|
58
|
+
try {
|
|
59
|
+
(0, child_process_1.execFileSync)('which', [bin], { timeout: 500, stdio: 'pipe' });
|
|
60
|
+
return true;
|
|
61
|
+
}
|
|
62
|
+
catch {
|
|
63
|
+
return false;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
function macApp(name) {
|
|
67
|
+
if (process.platform !== 'darwin')
|
|
68
|
+
return false;
|
|
69
|
+
return exists('/Applications', name) || exists(HOME, 'Applications', name);
|
|
70
|
+
}
|
|
71
|
+
exports.KNOWN_TOOLS = [
|
|
72
|
+
// ── Tools with OmniType hooks ────────────────────────────────────────────
|
|
73
|
+
{
|
|
74
|
+
id: 'claude-code',
|
|
75
|
+
name: 'Claude Code',
|
|
76
|
+
hookSupport: 'hooked',
|
|
77
|
+
detect: () => exists(HOME, '.claude') || inPath('claude'),
|
|
78
|
+
},
|
|
79
|
+
{
|
|
80
|
+
id: 'cursor',
|
|
81
|
+
name: 'Cursor',
|
|
82
|
+
hookSupport: 'hooked',
|
|
83
|
+
detect: () => exists(HOME, '.cursor') || macApp('Cursor.app'),
|
|
84
|
+
},
|
|
85
|
+
{
|
|
86
|
+
id: 'windsurf',
|
|
87
|
+
name: 'Windsurf',
|
|
88
|
+
hookSupport: 'hooked',
|
|
89
|
+
detect: () => exists(HOME, '.codeium') || macApp('Windsurf.app'),
|
|
90
|
+
},
|
|
91
|
+
{
|
|
92
|
+
id: 'codex',
|
|
93
|
+
name: 'Codex CLI',
|
|
94
|
+
hookSupport: 'hooked',
|
|
95
|
+
detect: () => exists(HOME, '.codex') || inPath('codex'),
|
|
96
|
+
},
|
|
97
|
+
{
|
|
98
|
+
id: 'cline',
|
|
99
|
+
name: 'Cline',
|
|
100
|
+
hookSupport: 'hooked',
|
|
101
|
+
detect: () => exists(HOME, 'Documents', 'Cline', 'Hooks') || exists(HOME, 'Documents', 'Cline'),
|
|
102
|
+
},
|
|
103
|
+
{
|
|
104
|
+
id: 'gemini-cli',
|
|
105
|
+
name: 'Gemini CLI',
|
|
106
|
+
hookSupport: 'hooked',
|
|
107
|
+
detect: () => inPath('gemini') || exists(HOME, '.gemini'),
|
|
108
|
+
},
|
|
109
|
+
{
|
|
110
|
+
id: 'droid',
|
|
111
|
+
name: 'Droid (Factory)',
|
|
112
|
+
hookSupport: 'hooked',
|
|
113
|
+
detect: () => exists(HOME, '.factory'),
|
|
114
|
+
},
|
|
115
|
+
{
|
|
116
|
+
id: 'firebender',
|
|
117
|
+
name: 'Firebender',
|
|
118
|
+
hookSupport: 'hooked',
|
|
119
|
+
detect: () => exists(HOME, '.firebender'),
|
|
120
|
+
},
|
|
121
|
+
{
|
|
122
|
+
id: 'amp',
|
|
123
|
+
name: 'Amp',
|
|
124
|
+
hookSupport: 'hooked',
|
|
125
|
+
detect: () => {
|
|
126
|
+
const xdg = process.env.XDG_DATA_HOME ?? path.join(HOME, '.local', 'share');
|
|
127
|
+
return inPath('amp') || exists(xdg, 'amp') ||
|
|
128
|
+
(process.platform === 'win32' && exists(process.env.LOCALAPPDATA ?? '', 'amp'));
|
|
129
|
+
},
|
|
130
|
+
},
|
|
131
|
+
{
|
|
132
|
+
id: 'opencode',
|
|
133
|
+
name: 'OpenCode',
|
|
134
|
+
hookSupport: 'hooked',
|
|
135
|
+
detect: () => exists(HOME, '.config', 'opencode') || inPath('opencode'),
|
|
136
|
+
},
|
|
137
|
+
{
|
|
138
|
+
id: 'pi',
|
|
139
|
+
name: 'Pi',
|
|
140
|
+
hookSupport: 'hooked',
|
|
141
|
+
detect: () => exists(HOME, '.pi'),
|
|
142
|
+
},
|
|
143
|
+
// ── Tools detected but no hook yet ──────────────────────────────────────
|
|
144
|
+
{
|
|
145
|
+
id: 'aider',
|
|
146
|
+
name: 'Aider',
|
|
147
|
+
hookSupport: 'no-hook',
|
|
148
|
+
detect: () => inPath('aider') || exists(HOME, '.aider.conf.yml') || exists(HOME, '.aider'),
|
|
149
|
+
},
|
|
150
|
+
{
|
|
151
|
+
id: 'continue',
|
|
152
|
+
name: 'Continue',
|
|
153
|
+
hookSupport: 'no-hook',
|
|
154
|
+
detect: () => exists(HOME, '.continue') || inPath('continue'),
|
|
155
|
+
},
|
|
156
|
+
{
|
|
157
|
+
id: 'antigravity',
|
|
158
|
+
name: 'Antigravity',
|
|
159
|
+
hookSupport: 'no-hook',
|
|
160
|
+
detect: () => macApp('Antigravity.app') || macApp('Google Antigravity.app'),
|
|
161
|
+
},
|
|
162
|
+
{
|
|
163
|
+
id: 'pearai',
|
|
164
|
+
name: 'PearAI',
|
|
165
|
+
hookSupport: 'no-hook',
|
|
166
|
+
detect: () => macApp('PearAI.app') || exists(HOME, '.pearai'),
|
|
167
|
+
},
|
|
168
|
+
{
|
|
169
|
+
id: 'void',
|
|
170
|
+
name: 'Void',
|
|
171
|
+
hookSupport: 'no-hook',
|
|
172
|
+
detect: () => macApp('Void.app') || exists(HOME, '.void'),
|
|
173
|
+
},
|
|
174
|
+
{
|
|
175
|
+
id: 'zed',
|
|
176
|
+
name: 'Zed',
|
|
177
|
+
hookSupport: 'no-hook',
|
|
178
|
+
detect: () => macApp('Zed.app') ||
|
|
179
|
+
(process.platform === 'linux' && (inPath('zed') || exists(HOME, '.config', 'zed'))),
|
|
180
|
+
},
|
|
181
|
+
{
|
|
182
|
+
id: 'roo-cline',
|
|
183
|
+
name: 'Roo Code',
|
|
184
|
+
hookSupport: 'no-hook',
|
|
185
|
+
detect: () => exists(HOME, '.roo') || exists(HOME, '.roo-cline'),
|
|
186
|
+
},
|
|
187
|
+
{
|
|
188
|
+
id: 'tabnine',
|
|
189
|
+
name: 'Tabnine',
|
|
190
|
+
hookSupport: 'no-hook',
|
|
191
|
+
detect: () => exists(HOME, '.tabnine') || exists(HOME, '.config', 'tabnine'),
|
|
192
|
+
},
|
|
193
|
+
{
|
|
194
|
+
id: 'supermaven',
|
|
195
|
+
name: 'Supermaven',
|
|
196
|
+
hookSupport: 'no-hook',
|
|
197
|
+
detect: () => exists(HOME, '.supermaven'),
|
|
198
|
+
},
|
|
199
|
+
{
|
|
200
|
+
id: 'codeium',
|
|
201
|
+
name: 'Codeium',
|
|
202
|
+
hookSupport: 'no-hook',
|
|
203
|
+
// .codeium overlaps with Windsurf — only count if Windsurf isn't the reason
|
|
204
|
+
detect: () => exists(HOME, '.codeium', 'codeium') || exists(HOME, '.codeium', 'config'),
|
|
205
|
+
},
|
|
206
|
+
{
|
|
207
|
+
id: 'copilot',
|
|
208
|
+
name: 'GitHub Copilot',
|
|
209
|
+
// Copilot supports PreToolUse/PostToolUse hooks at ~/.copilot/hooks/
|
|
210
|
+
hookSupport: 'hooked',
|
|
211
|
+
detect: () => exists(HOME, '.copilot') ||
|
|
212
|
+
exists(HOME, '.vscode') ||
|
|
213
|
+
exists(HOME, 'Library', 'Application Support', 'Code') ||
|
|
214
|
+
exists(HOME, '.config', 'github-copilot'),
|
|
215
|
+
},
|
|
216
|
+
{
|
|
217
|
+
id: 'trae',
|
|
218
|
+
name: 'Trae',
|
|
219
|
+
hookSupport: 'no-hook',
|
|
220
|
+
detect: () => macApp('Trae.app') || exists(HOME, '.trae'),
|
|
221
|
+
},
|
|
222
|
+
{
|
|
223
|
+
id: 'goose',
|
|
224
|
+
name: 'Goose',
|
|
225
|
+
hookSupport: 'no-hook',
|
|
226
|
+
detect: () => inPath('goose') || exists(HOME, '.config', 'goose'),
|
|
227
|
+
},
|
|
228
|
+
{
|
|
229
|
+
id: 'openai-codex-legacy',
|
|
230
|
+
name: 'OpenAI Codex (legacy)',
|
|
231
|
+
hookSupport: 'no-hook',
|
|
232
|
+
detect: () => !!process.env.OPENAI_API_KEY && !exists(HOME, '.codex'),
|
|
233
|
+
},
|
|
234
|
+
];
|
|
235
|
+
/** Returns all known tools with a detected flag. */
|
|
236
|
+
function detectAllTools() {
|
|
237
|
+
return exports.KNOWN_TOOLS.map(tool => {
|
|
238
|
+
let detected = false;
|
|
239
|
+
try {
|
|
240
|
+
detected = tool.detect();
|
|
241
|
+
}
|
|
242
|
+
catch { }
|
|
243
|
+
return { tool, detected };
|
|
244
|
+
});
|
|
245
|
+
}
|
|
246
|
+
/** Returns only the tools that are actually installed. */
|
|
247
|
+
function detectInstalledTools() {
|
|
248
|
+
return detectAllTools().filter(r => r.detected).map(r => r.tool);
|
|
249
|
+
}
|