jobarbiter 0.3.1 → 0.3.2
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/dist/lib/detect-tools.d.ts +46 -0
- package/dist/lib/detect-tools.js +473 -0
- package/dist/lib/observe.d.ts +6 -2
- package/dist/lib/observe.js +111 -129
- package/dist/lib/onboard.js +108 -74
- package/package.json +1 -1
- package/src/index.ts +1 -0
- package/src/lib/detect-tools.ts +526 -0
- package/src/lib/observe.ts +116 -131
- package/src/lib/onboard.ts +124 -88
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AI Tool Detection Module
|
|
3
|
+
*
|
|
4
|
+
* Comprehensive detection of AI tools installed on the user's system.
|
|
5
|
+
* Checks binaries in PATH, config directories, VS Code extensions,
|
|
6
|
+
* pip/npm packages, macOS apps, and environment variables.
|
|
7
|
+
*
|
|
8
|
+
* Never logs or stores API key values — only checks for existence.
|
|
9
|
+
*/
|
|
10
|
+
export type ToolCategory = "coding-agent" | "chat" | "orchestration" | "api-provider";
|
|
11
|
+
export interface DetectedTool {
|
|
12
|
+
id: string;
|
|
13
|
+
name: string;
|
|
14
|
+
category: ToolCategory;
|
|
15
|
+
installed: boolean;
|
|
16
|
+
version?: string;
|
|
17
|
+
configDir?: string;
|
|
18
|
+
observerAvailable: boolean;
|
|
19
|
+
observerActive: boolean;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Detect all AI tools on the system.
|
|
23
|
+
* Returns a list of tools with installation status, version info,
|
|
24
|
+
* and observer availability.
|
|
25
|
+
*/
|
|
26
|
+
export declare function detectAllTools(): DetectedTool[];
|
|
27
|
+
/**
|
|
28
|
+
* Get only installed tools.
|
|
29
|
+
*/
|
|
30
|
+
export declare function getInstalledTools(): DetectedTool[];
|
|
31
|
+
/**
|
|
32
|
+
* Get tools by category.
|
|
33
|
+
*/
|
|
34
|
+
export declare function getToolsByCategory(category: ToolCategory): DetectedTool[];
|
|
35
|
+
/**
|
|
36
|
+
* Get tools that have observers available.
|
|
37
|
+
*/
|
|
38
|
+
export declare function getObservableTools(): DetectedTool[];
|
|
39
|
+
/**
|
|
40
|
+
* Get tools that need observer installation.
|
|
41
|
+
*/
|
|
42
|
+
export declare function getToolsNeedingObserver(): DetectedTool[];
|
|
43
|
+
/**
|
|
44
|
+
* Format display name for a tool (includes version if available).
|
|
45
|
+
*/
|
|
46
|
+
export declare function formatToolDisplay(tool: DetectedTool): string;
|
|
@@ -0,0 +1,473 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AI Tool Detection Module
|
|
3
|
+
*
|
|
4
|
+
* Comprehensive detection of AI tools installed on the user's system.
|
|
5
|
+
* Checks binaries in PATH, config directories, VS Code extensions,
|
|
6
|
+
* pip/npm packages, macOS apps, and environment variables.
|
|
7
|
+
*
|
|
8
|
+
* Never logs or stores API key values — only checks for existence.
|
|
9
|
+
*/
|
|
10
|
+
import { existsSync } from "node:fs";
|
|
11
|
+
import { join } from "node:path";
|
|
12
|
+
import { homedir, platform } from "node:os";
|
|
13
|
+
import { execSync } from "node:child_process";
|
|
14
|
+
// ── Tool Definitions ───────────────────────────────────────────────────
|
|
15
|
+
const TOOL_DEFINITIONS = [
|
|
16
|
+
// AI Coding Agents
|
|
17
|
+
{
|
|
18
|
+
id: "claude-code",
|
|
19
|
+
name: "Claude Code",
|
|
20
|
+
category: "coding-agent",
|
|
21
|
+
binary: "claude",
|
|
22
|
+
configDir: join(homedir(), ".claude"),
|
|
23
|
+
observerAvailable: true,
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
id: "cursor",
|
|
27
|
+
name: "Cursor",
|
|
28
|
+
category: "coding-agent",
|
|
29
|
+
binary: "cursor",
|
|
30
|
+
configDir: join(homedir(), ".cursor"),
|
|
31
|
+
macApp: "/Applications/Cursor.app",
|
|
32
|
+
observerAvailable: true,
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
id: "github-copilot",
|
|
36
|
+
name: "GitHub Copilot",
|
|
37
|
+
category: "coding-agent",
|
|
38
|
+
configDir: join(homedir(), ".config", "github-copilot"),
|
|
39
|
+
vscodeExtension: "github.copilot",
|
|
40
|
+
cursorExtension: "github.copilot",
|
|
41
|
+
observerAvailable: false,
|
|
42
|
+
},
|
|
43
|
+
{
|
|
44
|
+
id: "codex",
|
|
45
|
+
name: "Codex CLI",
|
|
46
|
+
category: "coding-agent",
|
|
47
|
+
binary: "codex",
|
|
48
|
+
configDir: join(homedir(), ".codex"),
|
|
49
|
+
observerAvailable: true,
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
id: "opencode",
|
|
53
|
+
name: "OpenCode",
|
|
54
|
+
category: "coding-agent",
|
|
55
|
+
binary: "opencode",
|
|
56
|
+
configDir: join(homedir(), ".config", "opencode"),
|
|
57
|
+
observerAvailable: true,
|
|
58
|
+
},
|
|
59
|
+
{
|
|
60
|
+
id: "aider",
|
|
61
|
+
name: "Aider",
|
|
62
|
+
category: "coding-agent",
|
|
63
|
+
binary: "aider",
|
|
64
|
+
configDir: join(homedir(), ".aider"),
|
|
65
|
+
pipPackage: "aider-chat",
|
|
66
|
+
observerAvailable: false,
|
|
67
|
+
},
|
|
68
|
+
{
|
|
69
|
+
id: "continue",
|
|
70
|
+
name: "Continue",
|
|
71
|
+
category: "coding-agent",
|
|
72
|
+
vscodeExtension: "continue.continue",
|
|
73
|
+
cursorExtension: "continue.continue",
|
|
74
|
+
observerAvailable: false,
|
|
75
|
+
},
|
|
76
|
+
{
|
|
77
|
+
id: "cline",
|
|
78
|
+
name: "Cline",
|
|
79
|
+
category: "coding-agent",
|
|
80
|
+
vscodeExtension: "saoudrizwan.claude-dev",
|
|
81
|
+
cursorExtension: "saoudrizwan.claude-dev",
|
|
82
|
+
observerAvailable: false,
|
|
83
|
+
},
|
|
84
|
+
{
|
|
85
|
+
id: "windsurf",
|
|
86
|
+
name: "Windsurf",
|
|
87
|
+
category: "coding-agent",
|
|
88
|
+
binary: "windsurf",
|
|
89
|
+
macApp: "/Applications/Windsurf.app",
|
|
90
|
+
observerAvailable: false,
|
|
91
|
+
},
|
|
92
|
+
{
|
|
93
|
+
id: "letta",
|
|
94
|
+
name: "Letta Code",
|
|
95
|
+
category: "coding-agent",
|
|
96
|
+
binary: "letta",
|
|
97
|
+
configDir: join(homedir(), ".letta"),
|
|
98
|
+
pipPackage: "letta",
|
|
99
|
+
observerAvailable: false,
|
|
100
|
+
},
|
|
101
|
+
{
|
|
102
|
+
id: "gemini",
|
|
103
|
+
name: "Gemini CLI",
|
|
104
|
+
category: "coding-agent",
|
|
105
|
+
binary: "gemini",
|
|
106
|
+
configDir: join(homedir(), ".gemini"),
|
|
107
|
+
observerAvailable: true,
|
|
108
|
+
},
|
|
109
|
+
// AI Chat/Desktop
|
|
110
|
+
{
|
|
111
|
+
id: "chatgpt-desktop",
|
|
112
|
+
name: "ChatGPT Desktop",
|
|
113
|
+
category: "chat",
|
|
114
|
+
macApp: "/Applications/ChatGPT.app",
|
|
115
|
+
observerAvailable: false,
|
|
116
|
+
},
|
|
117
|
+
{
|
|
118
|
+
id: "claude-desktop",
|
|
119
|
+
name: "Claude Desktop",
|
|
120
|
+
category: "chat",
|
|
121
|
+
macApp: "/Applications/Claude.app",
|
|
122
|
+
observerAvailable: false,
|
|
123
|
+
},
|
|
124
|
+
{
|
|
125
|
+
id: "ollama",
|
|
126
|
+
name: "Ollama",
|
|
127
|
+
category: "chat",
|
|
128
|
+
binary: "ollama",
|
|
129
|
+
configDir: join(homedir(), ".ollama"),
|
|
130
|
+
observerAvailable: false,
|
|
131
|
+
},
|
|
132
|
+
// AI Orchestration
|
|
133
|
+
{
|
|
134
|
+
id: "openclaw",
|
|
135
|
+
name: "OpenClaw",
|
|
136
|
+
category: "orchestration",
|
|
137
|
+
binary: "openclaw",
|
|
138
|
+
configDir: join(homedir(), ".openclaw"),
|
|
139
|
+
observerAvailable: false,
|
|
140
|
+
},
|
|
141
|
+
{
|
|
142
|
+
id: "langchain",
|
|
143
|
+
name: "LangChain",
|
|
144
|
+
category: "orchestration",
|
|
145
|
+
pipPackage: "langchain",
|
|
146
|
+
observerAvailable: false,
|
|
147
|
+
},
|
|
148
|
+
{
|
|
149
|
+
id: "crewai",
|
|
150
|
+
name: "CrewAI",
|
|
151
|
+
category: "orchestration",
|
|
152
|
+
pipPackage: "crewai",
|
|
153
|
+
observerAvailable: false,
|
|
154
|
+
},
|
|
155
|
+
// API Providers (detected via env vars)
|
|
156
|
+
{
|
|
157
|
+
id: "anthropic-api",
|
|
158
|
+
name: "Anthropic API",
|
|
159
|
+
category: "api-provider",
|
|
160
|
+
envVars: ["ANTHROPIC_API_KEY"],
|
|
161
|
+
observerAvailable: false,
|
|
162
|
+
},
|
|
163
|
+
{
|
|
164
|
+
id: "openai-api",
|
|
165
|
+
name: "OpenAI API",
|
|
166
|
+
category: "api-provider",
|
|
167
|
+
envVars: ["OPENAI_API_KEY"],
|
|
168
|
+
observerAvailable: false,
|
|
169
|
+
},
|
|
170
|
+
{
|
|
171
|
+
id: "google-api",
|
|
172
|
+
name: "Google AI API",
|
|
173
|
+
category: "api-provider",
|
|
174
|
+
envVars: ["GOOGLE_API_KEY", "GEMINI_API_KEY"],
|
|
175
|
+
observerAvailable: false,
|
|
176
|
+
},
|
|
177
|
+
{
|
|
178
|
+
id: "groq-api",
|
|
179
|
+
name: "Groq API",
|
|
180
|
+
category: "api-provider",
|
|
181
|
+
envVars: ["GROQ_API_KEY"],
|
|
182
|
+
observerAvailable: false,
|
|
183
|
+
},
|
|
184
|
+
{
|
|
185
|
+
id: "mistral-api",
|
|
186
|
+
name: "Mistral API",
|
|
187
|
+
category: "api-provider",
|
|
188
|
+
envVars: ["MISTRAL_API_KEY"],
|
|
189
|
+
observerAvailable: false,
|
|
190
|
+
},
|
|
191
|
+
];
|
|
192
|
+
// ── Detection Helpers ──────────────────────────────────────────────────
|
|
193
|
+
function binExists(name) {
|
|
194
|
+
try {
|
|
195
|
+
execSync(`command -v ${name}`, { stdio: "ignore" });
|
|
196
|
+
return true;
|
|
197
|
+
}
|
|
198
|
+
catch {
|
|
199
|
+
return false;
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
function getBinVersion(name) {
|
|
203
|
+
// Only try --version since -v and "version" can start interactive modes
|
|
204
|
+
// for some tools (gemini, openclaw)
|
|
205
|
+
try {
|
|
206
|
+
const output = execSync(`${name} --version 2>/dev/null`, {
|
|
207
|
+
encoding: "utf-8",
|
|
208
|
+
timeout: 3000,
|
|
209
|
+
}).trim();
|
|
210
|
+
// Extract version number (e.g., "1.0.16", "v2.1.0")
|
|
211
|
+
const match = output.match(/v?(\d+\.\d+(?:\.\d+)?(?:-[a-zA-Z0-9.-]+)?)/);
|
|
212
|
+
if (match)
|
|
213
|
+
return match[1];
|
|
214
|
+
return undefined;
|
|
215
|
+
}
|
|
216
|
+
catch {
|
|
217
|
+
return undefined;
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
function macAppExists(appPath) {
|
|
221
|
+
if (platform() !== "darwin")
|
|
222
|
+
return false;
|
|
223
|
+
return existsSync(appPath);
|
|
224
|
+
}
|
|
225
|
+
let vscodeExtensionsCache = null;
|
|
226
|
+
function getVSCodeExtensions() {
|
|
227
|
+
if (vscodeExtensionsCache !== null)
|
|
228
|
+
return vscodeExtensionsCache;
|
|
229
|
+
try {
|
|
230
|
+
const output = execSync("code --list-extensions 2>/dev/null", {
|
|
231
|
+
encoding: "utf-8",
|
|
232
|
+
timeout: 10000,
|
|
233
|
+
});
|
|
234
|
+
vscodeExtensionsCache = output.trim().toLowerCase().split("\n").filter(Boolean);
|
|
235
|
+
return vscodeExtensionsCache;
|
|
236
|
+
}
|
|
237
|
+
catch {
|
|
238
|
+
vscodeExtensionsCache = [];
|
|
239
|
+
return [];
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
let cursorExtensionsCache = null;
|
|
243
|
+
function getCursorExtensions() {
|
|
244
|
+
if (cursorExtensionsCache !== null)
|
|
245
|
+
return cursorExtensionsCache;
|
|
246
|
+
try {
|
|
247
|
+
// Cursor uses same extension format as VS Code
|
|
248
|
+
const output = execSync("cursor --list-extensions 2>/dev/null", {
|
|
249
|
+
encoding: "utf-8",
|
|
250
|
+
timeout: 10000,
|
|
251
|
+
});
|
|
252
|
+
cursorExtensionsCache = output.trim().toLowerCase().split("\n").filter(Boolean);
|
|
253
|
+
return cursorExtensionsCache;
|
|
254
|
+
}
|
|
255
|
+
catch {
|
|
256
|
+
cursorExtensionsCache = [];
|
|
257
|
+
return [];
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
let pipPackagesCache = null;
|
|
261
|
+
function getPipPackages() {
|
|
262
|
+
if (pipPackagesCache !== null)
|
|
263
|
+
return pipPackagesCache;
|
|
264
|
+
try {
|
|
265
|
+
const output = execSync("pip list --format=freeze 2>/dev/null", {
|
|
266
|
+
encoding: "utf-8",
|
|
267
|
+
timeout: 10000,
|
|
268
|
+
});
|
|
269
|
+
const packages = new Set();
|
|
270
|
+
for (const line of output.split("\n")) {
|
|
271
|
+
const match = line.match(/^([^=]+)==/);
|
|
272
|
+
if (match)
|
|
273
|
+
packages.add(match[1].toLowerCase());
|
|
274
|
+
}
|
|
275
|
+
pipPackagesCache = packages;
|
|
276
|
+
return packages;
|
|
277
|
+
}
|
|
278
|
+
catch {
|
|
279
|
+
// Try pip3 as fallback
|
|
280
|
+
try {
|
|
281
|
+
const output = execSync("pip3 list --format=freeze 2>/dev/null", {
|
|
282
|
+
encoding: "utf-8",
|
|
283
|
+
timeout: 10000,
|
|
284
|
+
});
|
|
285
|
+
const packages = new Set();
|
|
286
|
+
for (const line of output.split("\n")) {
|
|
287
|
+
const match = line.match(/^([^=]+)==/);
|
|
288
|
+
if (match)
|
|
289
|
+
packages.add(match[1].toLowerCase());
|
|
290
|
+
}
|
|
291
|
+
pipPackagesCache = packages;
|
|
292
|
+
return packages;
|
|
293
|
+
}
|
|
294
|
+
catch {
|
|
295
|
+
pipPackagesCache = new Set();
|
|
296
|
+
return new Set();
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
let npmPackagesCache = null;
|
|
301
|
+
function getNpmGlobalPackages() {
|
|
302
|
+
if (npmPackagesCache !== null)
|
|
303
|
+
return npmPackagesCache;
|
|
304
|
+
try {
|
|
305
|
+
const output = execSync("npm list -g --depth=0 --json 2>/dev/null", {
|
|
306
|
+
encoding: "utf-8",
|
|
307
|
+
timeout: 10000,
|
|
308
|
+
});
|
|
309
|
+
const data = JSON.parse(output);
|
|
310
|
+
npmPackagesCache = new Set(Object.keys(data.dependencies || {}).map(s => s.toLowerCase()));
|
|
311
|
+
return npmPackagesCache;
|
|
312
|
+
}
|
|
313
|
+
catch {
|
|
314
|
+
npmPackagesCache = new Set();
|
|
315
|
+
return new Set();
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
function hasEnvVar(varNames) {
|
|
319
|
+
return varNames.some((name) => !!process.env[name]);
|
|
320
|
+
}
|
|
321
|
+
// ── Observer Detection ─────────────────────────────────────────────────
|
|
322
|
+
function isObserverInstalled(toolId, configDir) {
|
|
323
|
+
if (!configDir)
|
|
324
|
+
return false;
|
|
325
|
+
try {
|
|
326
|
+
switch (toolId) {
|
|
327
|
+
case "claude-code":
|
|
328
|
+
case "cursor": {
|
|
329
|
+
const hookFile = join(configDir, "hooks.json");
|
|
330
|
+
if (!existsSync(hookFile))
|
|
331
|
+
return false;
|
|
332
|
+
const { readFileSync } = require("node:fs");
|
|
333
|
+
const content = readFileSync(hookFile, "utf-8");
|
|
334
|
+
return content.includes("jobarbiter");
|
|
335
|
+
}
|
|
336
|
+
case "opencode": {
|
|
337
|
+
const pluginDir = join(configDir, "plugins");
|
|
338
|
+
return existsSync(join(pluginDir, "jobarbiter-observer.js"));
|
|
339
|
+
}
|
|
340
|
+
case "codex": {
|
|
341
|
+
const configFile = join(configDir, "config.toml");
|
|
342
|
+
if (!existsSync(configFile))
|
|
343
|
+
return false;
|
|
344
|
+
const { readFileSync } = require("node:fs");
|
|
345
|
+
const content = readFileSync(configFile, "utf-8");
|
|
346
|
+
return content.includes("jobarbiter");
|
|
347
|
+
}
|
|
348
|
+
case "gemini": {
|
|
349
|
+
const settingsFile = join(configDir, "settings.json");
|
|
350
|
+
if (!existsSync(settingsFile))
|
|
351
|
+
return false;
|
|
352
|
+
const { readFileSync } = require("node:fs");
|
|
353
|
+
const content = readFileSync(settingsFile, "utf-8");
|
|
354
|
+
return content.includes("jobarbiter");
|
|
355
|
+
}
|
|
356
|
+
default:
|
|
357
|
+
return false;
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
catch {
|
|
361
|
+
return false;
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
// ── Main Detection Function ────────────────────────────────────────────
|
|
365
|
+
/**
|
|
366
|
+
* Detect all AI tools on the system.
|
|
367
|
+
* Returns a list of tools with installation status, version info,
|
|
368
|
+
* and observer availability.
|
|
369
|
+
*/
|
|
370
|
+
export function detectAllTools() {
|
|
371
|
+
const results = [];
|
|
372
|
+
for (const def of TOOL_DEFINITIONS) {
|
|
373
|
+
let installed = false;
|
|
374
|
+
let version;
|
|
375
|
+
let configDir = def.configDir;
|
|
376
|
+
// Check binary in PATH
|
|
377
|
+
if (def.binary && binExists(def.binary)) {
|
|
378
|
+
installed = true;
|
|
379
|
+
version = getBinVersion(def.binary);
|
|
380
|
+
}
|
|
381
|
+
// Check config directory
|
|
382
|
+
if (!installed && def.configDir && existsSync(def.configDir)) {
|
|
383
|
+
installed = true;
|
|
384
|
+
}
|
|
385
|
+
// Check macOS app
|
|
386
|
+
if (!installed && def.macApp && macAppExists(def.macApp)) {
|
|
387
|
+
installed = true;
|
|
388
|
+
}
|
|
389
|
+
// Check VS Code extension
|
|
390
|
+
if (!installed && def.vscodeExtension) {
|
|
391
|
+
const extensions = getVSCodeExtensions();
|
|
392
|
+
if (extensions.includes(def.vscodeExtension.toLowerCase())) {
|
|
393
|
+
installed = true;
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
// Check Cursor extension
|
|
397
|
+
if (!installed && def.cursorExtension) {
|
|
398
|
+
const extensions = getCursorExtensions();
|
|
399
|
+
if (extensions.includes(def.cursorExtension.toLowerCase())) {
|
|
400
|
+
installed = true;
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
// Check pip package
|
|
404
|
+
if (!installed && def.pipPackage) {
|
|
405
|
+
const packages = getPipPackages();
|
|
406
|
+
if (packages.has(def.pipPackage.toLowerCase())) {
|
|
407
|
+
installed = true;
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
// Check npm package
|
|
411
|
+
if (!installed && def.npmPackage) {
|
|
412
|
+
const packages = getNpmGlobalPackages();
|
|
413
|
+
if (packages.has(def.npmPackage.toLowerCase())) {
|
|
414
|
+
installed = true;
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
// Check environment variables (for API providers)
|
|
418
|
+
if (!installed && def.envVars && def.envVars.length > 0) {
|
|
419
|
+
if (hasEnvVar(def.envVars)) {
|
|
420
|
+
installed = true;
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
// Check if observer is installed (only if tool is installed and observer is available)
|
|
424
|
+
const observerActive = installed && def.observerAvailable
|
|
425
|
+
? isObserverInstalled(def.id, configDir)
|
|
426
|
+
: false;
|
|
427
|
+
results.push({
|
|
428
|
+
id: def.id,
|
|
429
|
+
name: def.name,
|
|
430
|
+
category: def.category,
|
|
431
|
+
installed,
|
|
432
|
+
version,
|
|
433
|
+
configDir: installed ? configDir : undefined,
|
|
434
|
+
observerAvailable: def.observerAvailable,
|
|
435
|
+
observerActive,
|
|
436
|
+
});
|
|
437
|
+
}
|
|
438
|
+
return results;
|
|
439
|
+
}
|
|
440
|
+
/**
|
|
441
|
+
* Get only installed tools.
|
|
442
|
+
*/
|
|
443
|
+
export function getInstalledTools() {
|
|
444
|
+
return detectAllTools().filter((t) => t.installed);
|
|
445
|
+
}
|
|
446
|
+
/**
|
|
447
|
+
* Get tools by category.
|
|
448
|
+
*/
|
|
449
|
+
export function getToolsByCategory(category) {
|
|
450
|
+
return detectAllTools().filter((t) => t.category === category);
|
|
451
|
+
}
|
|
452
|
+
/**
|
|
453
|
+
* Get tools that have observers available.
|
|
454
|
+
*/
|
|
455
|
+
export function getObservableTools() {
|
|
456
|
+
return detectAllTools().filter((t) => t.installed && t.observerAvailable);
|
|
457
|
+
}
|
|
458
|
+
/**
|
|
459
|
+
* Get tools that need observer installation.
|
|
460
|
+
*/
|
|
461
|
+
export function getToolsNeedingObserver() {
|
|
462
|
+
return detectAllTools().filter((t) => t.installed && t.observerAvailable && !t.observerActive);
|
|
463
|
+
}
|
|
464
|
+
/**
|
|
465
|
+
* Format display name for a tool (includes version if available).
|
|
466
|
+
*/
|
|
467
|
+
export function formatToolDisplay(tool) {
|
|
468
|
+
let display = tool.name;
|
|
469
|
+
if (tool.version) {
|
|
470
|
+
display += ` v${tool.version}`;
|
|
471
|
+
}
|
|
472
|
+
return display;
|
|
473
|
+
}
|
package/dist/lib/observe.d.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* JobArbiter Observer — Hook installer for coding agent CLIs
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
4
|
+
* Installs observation hooks that extract proficiency signals from
|
|
5
|
+
* session transcripts. Uses detect-tools.ts for agent detection.
|
|
6
6
|
*/
|
|
7
7
|
export interface DetectedAgent {
|
|
8
8
|
id: string;
|
|
@@ -12,6 +12,10 @@ export interface DetectedAgent {
|
|
|
12
12
|
installed: boolean;
|
|
13
13
|
hookInstalled: boolean;
|
|
14
14
|
}
|
|
15
|
+
/**
|
|
16
|
+
* Detect agents that support observation.
|
|
17
|
+
* Uses the shared detect-tools module for detection.
|
|
18
|
+
*/
|
|
15
19
|
export declare function detectAgents(): DetectedAgent[];
|
|
16
20
|
/**
|
|
17
21
|
* Install observer hooks for the specified agents.
|