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
package/dist/lib/observe.js
CHANGED
|
@@ -1,112 +1,42 @@
|
|
|
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
|
import { existsSync, readFileSync, writeFileSync, mkdirSync, chmodSync, unlinkSync } from "node:fs";
|
|
8
8
|
import { join } from "node:path";
|
|
9
9
|
import { homedir } from "node:os";
|
|
10
|
-
import {
|
|
11
|
-
// ── Agent
|
|
12
|
-
const
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
name: "OpenCode",
|
|
31
|
-
configDir: join(homedir(), ".config", "opencode"),
|
|
32
|
-
hookFormat: "opencode",
|
|
33
|
-
detectBin: "opencode",
|
|
34
|
-
},
|
|
35
|
-
{
|
|
36
|
-
id: "codex",
|
|
37
|
-
name: "Codex CLI",
|
|
38
|
-
configDir: join(homedir(), ".codex"),
|
|
39
|
-
hookFormat: "codex",
|
|
40
|
-
detectBin: "codex",
|
|
41
|
-
},
|
|
42
|
-
{
|
|
43
|
-
id: "gemini",
|
|
44
|
-
name: "Gemini CLI",
|
|
45
|
-
configDir: join(homedir(), ".gemini"),
|
|
46
|
-
hookFormat: "gemini",
|
|
47
|
-
detectBin: "gemini",
|
|
48
|
-
},
|
|
49
|
-
];
|
|
50
|
-
function binExists(name) {
|
|
51
|
-
try {
|
|
52
|
-
execSync(`which ${name}`, { stdio: "ignore" });
|
|
53
|
-
return true;
|
|
54
|
-
}
|
|
55
|
-
catch {
|
|
56
|
-
return false;
|
|
57
|
-
}
|
|
58
|
-
}
|
|
10
|
+
import { getObservableTools } from "./detect-tools.js";
|
|
11
|
+
// ── Agent Config Directories ───────────────────────────────────────────
|
|
12
|
+
const AGENT_CONFIG_DIRS = {
|
|
13
|
+
"claude-code": join(homedir(), ".claude"),
|
|
14
|
+
"cursor": join(homedir(), ".cursor"),
|
|
15
|
+
"opencode": join(homedir(), ".config", "opencode"),
|
|
16
|
+
"codex": join(homedir(), ".codex"),
|
|
17
|
+
"gemini": join(homedir(), ".gemini"),
|
|
18
|
+
};
|
|
19
|
+
const AGENT_HOOK_FORMATS = {
|
|
20
|
+
"claude-code": "claude",
|
|
21
|
+
"cursor": "cursor",
|
|
22
|
+
"opencode": "opencode",
|
|
23
|
+
"codex": "codex",
|
|
24
|
+
"gemini": "gemini",
|
|
25
|
+
};
|
|
26
|
+
/**
|
|
27
|
+
* Detect agents that support observation.
|
|
28
|
+
* Uses the shared detect-tools module for detection.
|
|
29
|
+
*/
|
|
59
30
|
export function detectAgents() {
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
hookInstalled: installed ? isHookInstalled(def.id, def.configDir, def.hookFormat) : false,
|
|
70
|
-
};
|
|
71
|
-
});
|
|
72
|
-
}
|
|
73
|
-
// ── Hook Detection ─────────────────────────────────────────────────────
|
|
74
|
-
function isHookInstalled(agentId, configDir, format) {
|
|
75
|
-
try {
|
|
76
|
-
switch (format) {
|
|
77
|
-
case "claude":
|
|
78
|
-
case "cursor": {
|
|
79
|
-
const hookFile = join(configDir, "hooks.json");
|
|
80
|
-
if (!existsSync(hookFile))
|
|
81
|
-
return false;
|
|
82
|
-
const content = readFileSync(hookFile, "utf-8");
|
|
83
|
-
return content.includes("jobarbiter");
|
|
84
|
-
}
|
|
85
|
-
case "opencode": {
|
|
86
|
-
const pluginDir = join(configDir, "plugins");
|
|
87
|
-
return existsSync(join(pluginDir, "jobarbiter-observer.ts"));
|
|
88
|
-
}
|
|
89
|
-
case "codex": {
|
|
90
|
-
const configFile = join(configDir, "config.toml");
|
|
91
|
-
if (!existsSync(configFile))
|
|
92
|
-
return false;
|
|
93
|
-
const content = readFileSync(configFile, "utf-8");
|
|
94
|
-
return content.includes("jobarbiter");
|
|
95
|
-
}
|
|
96
|
-
case "gemini": {
|
|
97
|
-
const settingsFile = join(configDir, "settings.json");
|
|
98
|
-
if (!existsSync(settingsFile))
|
|
99
|
-
return false;
|
|
100
|
-
const content = readFileSync(settingsFile, "utf-8");
|
|
101
|
-
return content.includes("jobarbiter");
|
|
102
|
-
}
|
|
103
|
-
default:
|
|
104
|
-
return false;
|
|
105
|
-
}
|
|
106
|
-
}
|
|
107
|
-
catch {
|
|
108
|
-
return false;
|
|
109
|
-
}
|
|
31
|
+
const observableTools = getObservableTools();
|
|
32
|
+
return observableTools.map((tool) => ({
|
|
33
|
+
id: tool.id,
|
|
34
|
+
name: tool.name,
|
|
35
|
+
configDir: AGENT_CONFIG_DIRS[tool.id] || tool.configDir || "",
|
|
36
|
+
hookFormat: AGENT_HOOK_FORMATS[tool.id] || "claude",
|
|
37
|
+
installed: tool.installed,
|
|
38
|
+
hookInstalled: tool.observerActive,
|
|
39
|
+
}));
|
|
110
40
|
}
|
|
111
41
|
// ── Observer Data Directory ────────────────────────────────────────────
|
|
112
42
|
const OBSERVER_DIR = join(homedir(), ".config", "jobarbiter", "observer");
|
|
@@ -519,6 +449,54 @@ function installGeminiHook(configDir, scriptPath) {
|
|
|
519
449
|
writeFileSync(settingsFile, JSON.stringify(settings, null, 2) + "\n");
|
|
520
450
|
}
|
|
521
451
|
// ── Public API ─────────────────────────────────────────────────────────
|
|
452
|
+
// ── Agent Name Mapping ─────────────────────────────────────────────────
|
|
453
|
+
const AGENT_NAMES = {
|
|
454
|
+
"claude-code": "Claude Code",
|
|
455
|
+
"cursor": "Cursor",
|
|
456
|
+
"opencode": "OpenCode",
|
|
457
|
+
"codex": "Codex CLI",
|
|
458
|
+
"gemini": "Gemini CLI",
|
|
459
|
+
};
|
|
460
|
+
/**
|
|
461
|
+
* Check if observer hook is installed for an agent.
|
|
462
|
+
*/
|
|
463
|
+
function isHookInstalled(agentId, configDir, format) {
|
|
464
|
+
try {
|
|
465
|
+
switch (format) {
|
|
466
|
+
case "claude":
|
|
467
|
+
case "cursor": {
|
|
468
|
+
const hookFile = join(configDir, "hooks.json");
|
|
469
|
+
if (!existsSync(hookFile))
|
|
470
|
+
return false;
|
|
471
|
+
const content = readFileSync(hookFile, "utf-8");
|
|
472
|
+
return content.includes("jobarbiter");
|
|
473
|
+
}
|
|
474
|
+
case "opencode": {
|
|
475
|
+
const pluginDir = join(configDir, "plugins");
|
|
476
|
+
return existsSync(join(pluginDir, "jobarbiter-observer.js"));
|
|
477
|
+
}
|
|
478
|
+
case "codex": {
|
|
479
|
+
const configFile = join(configDir, "config.toml");
|
|
480
|
+
if (!existsSync(configFile))
|
|
481
|
+
return false;
|
|
482
|
+
const content = readFileSync(configFile, "utf-8");
|
|
483
|
+
return content.includes("jobarbiter");
|
|
484
|
+
}
|
|
485
|
+
case "gemini": {
|
|
486
|
+
const settingsFile = join(configDir, "settings.json");
|
|
487
|
+
if (!existsSync(settingsFile))
|
|
488
|
+
return false;
|
|
489
|
+
const content = readFileSync(settingsFile, "utf-8");
|
|
490
|
+
return content.includes("jobarbiter");
|
|
491
|
+
}
|
|
492
|
+
default:
|
|
493
|
+
return false;
|
|
494
|
+
}
|
|
495
|
+
}
|
|
496
|
+
catch {
|
|
497
|
+
return false;
|
|
498
|
+
}
|
|
499
|
+
}
|
|
522
500
|
/**
|
|
523
501
|
* Install observer hooks for the specified agents.
|
|
524
502
|
* Returns a summary of what was installed.
|
|
@@ -531,39 +509,41 @@ export function installObservers(agentIds) {
|
|
|
531
509
|
errors: [],
|
|
532
510
|
};
|
|
533
511
|
for (const agentId of agentIds) {
|
|
534
|
-
const
|
|
535
|
-
|
|
512
|
+
const configDir = AGENT_CONFIG_DIRS[agentId];
|
|
513
|
+
const hookFormat = AGENT_HOOK_FORMATS[agentId];
|
|
514
|
+
const agentName = AGENT_NAMES[agentId] || agentId;
|
|
515
|
+
if (!configDir || !hookFormat) {
|
|
536
516
|
result.errors.push({ agent: agentId, error: "Unknown agent" });
|
|
537
517
|
continue;
|
|
538
518
|
}
|
|
539
519
|
// Check if already installed
|
|
540
|
-
if (isHookInstalled(
|
|
541
|
-
result.skipped.push(
|
|
520
|
+
if (isHookInstalled(agentId, configDir, hookFormat)) {
|
|
521
|
+
result.skipped.push(agentName);
|
|
542
522
|
continue;
|
|
543
523
|
}
|
|
544
524
|
try {
|
|
545
|
-
switch (
|
|
525
|
+
switch (hookFormat) {
|
|
546
526
|
case "claude":
|
|
547
|
-
installClaudeCodeHook(
|
|
527
|
+
installClaudeCodeHook(configDir, scriptPath);
|
|
548
528
|
break;
|
|
549
529
|
case "cursor":
|
|
550
|
-
installCursorHook(
|
|
530
|
+
installCursorHook(configDir, scriptPath);
|
|
551
531
|
break;
|
|
552
532
|
case "opencode":
|
|
553
|
-
installOpenCodeHook(
|
|
533
|
+
installOpenCodeHook(configDir, scriptPath);
|
|
554
534
|
break;
|
|
555
535
|
case "codex":
|
|
556
|
-
installCodexHook(
|
|
536
|
+
installCodexHook(configDir, scriptPath);
|
|
557
537
|
break;
|
|
558
538
|
case "gemini":
|
|
559
|
-
installGeminiHook(
|
|
539
|
+
installGeminiHook(configDir, scriptPath);
|
|
560
540
|
break;
|
|
561
541
|
}
|
|
562
|
-
result.installed.push(
|
|
542
|
+
result.installed.push(agentName);
|
|
563
543
|
}
|
|
564
544
|
catch (err) {
|
|
565
545
|
result.errors.push({
|
|
566
|
-
agent:
|
|
546
|
+
agent: agentName,
|
|
567
547
|
error: err instanceof Error ? err.message : String(err),
|
|
568
548
|
});
|
|
569
549
|
}
|
|
@@ -576,16 +556,18 @@ export function installObservers(agentIds) {
|
|
|
576
556
|
export function removeObservers(agentIds) {
|
|
577
557
|
const result = { removed: [], notFound: [] };
|
|
578
558
|
for (const agentId of agentIds) {
|
|
579
|
-
const
|
|
580
|
-
|
|
559
|
+
const configDir = AGENT_CONFIG_DIRS[agentId];
|
|
560
|
+
const hookFormat = AGENT_HOOK_FORMATS[agentId];
|
|
561
|
+
const agentName = AGENT_NAMES[agentId] || agentId;
|
|
562
|
+
if (!configDir || !hookFormat) {
|
|
581
563
|
result.notFound.push(agentId);
|
|
582
564
|
continue;
|
|
583
565
|
}
|
|
584
566
|
try {
|
|
585
|
-
switch (
|
|
567
|
+
switch (hookFormat) {
|
|
586
568
|
case "claude":
|
|
587
569
|
case "cursor": {
|
|
588
|
-
const hookFile = join(
|
|
570
|
+
const hookFile = join(configDir, "hooks.json");
|
|
589
571
|
if (existsSync(hookFile)) {
|
|
590
572
|
const config = JSON.parse(readFileSync(hookFile, "utf-8"));
|
|
591
573
|
for (const [key, hooks] of Object.entries(config.hooks || {})) {
|
|
@@ -594,26 +576,26 @@ export function removeObservers(agentIds) {
|
|
|
594
576
|
}
|
|
595
577
|
}
|
|
596
578
|
writeFileSync(hookFile, JSON.stringify(config, null, 2) + "\n");
|
|
597
|
-
result.removed.push(
|
|
579
|
+
result.removed.push(agentName);
|
|
598
580
|
}
|
|
599
581
|
else {
|
|
600
|
-
result.notFound.push(
|
|
582
|
+
result.notFound.push(agentName);
|
|
601
583
|
}
|
|
602
584
|
break;
|
|
603
585
|
}
|
|
604
586
|
case "opencode": {
|
|
605
|
-
const pluginFile = join(
|
|
587
|
+
const pluginFile = join(configDir, "plugins", "jobarbiter-observer.js");
|
|
606
588
|
if (existsSync(pluginFile)) {
|
|
607
589
|
unlinkSync(pluginFile);
|
|
608
|
-
result.removed.push(
|
|
590
|
+
result.removed.push(agentName);
|
|
609
591
|
}
|
|
610
592
|
else {
|
|
611
|
-
result.notFound.push(
|
|
593
|
+
result.notFound.push(agentName);
|
|
612
594
|
}
|
|
613
595
|
break;
|
|
614
596
|
}
|
|
615
597
|
case "codex": {
|
|
616
|
-
const configFile = join(
|
|
598
|
+
const configFile = join(configDir, "config.toml");
|
|
617
599
|
if (existsSync(configFile)) {
|
|
618
600
|
let content = readFileSync(configFile, "utf-8");
|
|
619
601
|
content = content
|
|
@@ -621,15 +603,15 @@ export function removeObservers(agentIds) {
|
|
|
621
603
|
.filter((line) => !line.includes("jobarbiter"))
|
|
622
604
|
.join("\n");
|
|
623
605
|
writeFileSync(configFile, content);
|
|
624
|
-
result.removed.push(
|
|
606
|
+
result.removed.push(agentName);
|
|
625
607
|
}
|
|
626
608
|
else {
|
|
627
|
-
result.notFound.push(
|
|
609
|
+
result.notFound.push(agentName);
|
|
628
610
|
}
|
|
629
611
|
break;
|
|
630
612
|
}
|
|
631
613
|
case "gemini": {
|
|
632
|
-
const settingsFile = join(
|
|
614
|
+
const settingsFile = join(configDir, "settings.json");
|
|
633
615
|
if (existsSync(settingsFile)) {
|
|
634
616
|
const settings = JSON.parse(readFileSync(settingsFile, "utf-8"));
|
|
635
617
|
for (const [key, hookGroups] of Object.entries(settings.hooks || {})) {
|
|
@@ -638,17 +620,17 @@ export function removeObservers(agentIds) {
|
|
|
638
620
|
}
|
|
639
621
|
}
|
|
640
622
|
writeFileSync(settingsFile, JSON.stringify(settings, null, 2) + "\n");
|
|
641
|
-
result.removed.push(
|
|
623
|
+
result.removed.push(agentName);
|
|
642
624
|
}
|
|
643
625
|
else {
|
|
644
|
-
result.notFound.push(
|
|
626
|
+
result.notFound.push(agentName);
|
|
645
627
|
}
|
|
646
628
|
break;
|
|
647
629
|
}
|
|
648
630
|
}
|
|
649
631
|
}
|
|
650
632
|
catch {
|
|
651
|
-
result.notFound.push(
|
|
633
|
+
result.notFound.push(agentName);
|
|
652
634
|
}
|
|
653
635
|
}
|
|
654
636
|
return result;
|
package/dist/lib/onboard.js
CHANGED
|
@@ -10,7 +10,8 @@
|
|
|
10
10
|
import * as readline from "node:readline";
|
|
11
11
|
import { loadConfig, saveConfig, getConfigPath } from "./config.js";
|
|
12
12
|
import { apiUnauthenticated, api, ApiError } from "./api.js";
|
|
13
|
-
import {
|
|
13
|
+
import { installObservers } from "./observe.js";
|
|
14
|
+
import { detectAllTools, formatToolDisplay, } from "./detect-tools.js";
|
|
14
15
|
// ── ANSI Colors ────────────────────────────────────────────────────────
|
|
15
16
|
const colors = {
|
|
16
17
|
reset: "\x1b[0m",
|
|
@@ -164,7 +165,9 @@ async function selectUserType(prompt) {
|
|
|
164
165
|
}
|
|
165
166
|
// ── Email & Verification ───────────────────────────────────────────────
|
|
166
167
|
async function handleEmailVerification(prompt, baseUrl, userType) {
|
|
167
|
-
|
|
168
|
+
// Workers: 1) Account, 2) Tool Detection, 3) Domains, 4) GitHub, 5) LinkedIn, 6) Done
|
|
169
|
+
// Employers: 1) Account, 2) (skip verification), 3) Company, 4) Domain, 5) What You Need, 6) Done
|
|
170
|
+
const totalSteps = 6;
|
|
168
171
|
console.log(`\n${sym.email} ${c.bold(`Step 1/${totalSteps} — Create Your Account`)}\n`);
|
|
169
172
|
// Get email
|
|
170
173
|
let email;
|
|
@@ -240,14 +243,12 @@ async function runWorkerFlow(prompt, state) {
|
|
|
240
243
|
baseUrl: state.baseUrl,
|
|
241
244
|
userType: "worker",
|
|
242
245
|
};
|
|
243
|
-
// Step 2:
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
state.tools = tools;
|
|
250
|
-
console.log(`\nWhat domains do you work in? ${c.dim("(comma-separated)")}`);
|
|
246
|
+
// Step 2: Auto-detect AI Tools
|
|
247
|
+
const detectedToolsResult = await runToolDetectionStep(prompt, config);
|
|
248
|
+
state.tools = detectedToolsResult.tools;
|
|
249
|
+
// Step 3: Domains
|
|
250
|
+
console.log(`${sym.target} ${c.bold("Step 3/6 — Your Domains")}\n`);
|
|
251
|
+
console.log(`What domains do you work in? ${c.dim("(comma-separated)")}`);
|
|
251
252
|
console.log(c.dim("Examples: full-stack dev, data engineering, trading, content creation\n"));
|
|
252
253
|
const domainsInput = await prompt.question(`${sym.arrow} `);
|
|
253
254
|
const domains = domainsInput.split(",").map(s => s.trim()).filter(Boolean);
|
|
@@ -258,7 +259,7 @@ async function runWorkerFlow(prompt, state) {
|
|
|
258
259
|
await api(config, "POST", "/v1/profile", {
|
|
259
260
|
domains,
|
|
260
261
|
tools: {
|
|
261
|
-
primary: tools,
|
|
262
|
+
primary: state.tools,
|
|
262
263
|
},
|
|
263
264
|
});
|
|
264
265
|
console.log(`${sym.check} Profile saved\n`);
|
|
@@ -266,8 +267,6 @@ async function runWorkerFlow(prompt, state) {
|
|
|
266
267
|
catch (err) {
|
|
267
268
|
console.log(`${sym.warning} ${c.warning("Could not save profile details — you can update later with 'jobarbiter profile create'")}\n`);
|
|
268
269
|
}
|
|
269
|
-
// Step 3: Install Coding Agent Observers
|
|
270
|
-
await runObserverStep(prompt, state, 3, 6);
|
|
271
270
|
// Step 4: Connect GitHub (optional)
|
|
272
271
|
console.log(`${sym.link} ${c.bold("Step 4/6 — Connect GitHub")} ${c.dim("(optional)")}\n`);
|
|
273
272
|
console.log(`Connecting your GitHub lets us analyze your AI-assisted work patterns.`);
|
|
@@ -313,81 +312,116 @@ async function runWorkerFlow(prompt, state) {
|
|
|
313
312
|
// Step 6: Done!
|
|
314
313
|
showWorkerCompletion(state);
|
|
315
314
|
}
|
|
316
|
-
// ──
|
|
317
|
-
async function
|
|
318
|
-
console.log(`🔍 ${c.bold(
|
|
319
|
-
console.log(c.dim("Scanning
|
|
320
|
-
const
|
|
321
|
-
const
|
|
322
|
-
const
|
|
323
|
-
//
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
315
|
+
// ── Tool Detection Step ────────────────────────────────────────────────
|
|
316
|
+
async function runToolDetectionStep(prompt, config) {
|
|
317
|
+
console.log(`🔍 ${c.bold("Step 2/6 — Detecting AI Tools")}\n`);
|
|
318
|
+
console.log(c.dim(" Scanning your machine...\n"));
|
|
319
|
+
const allTools = detectAllTools();
|
|
320
|
+
const installed = allTools.filter((t) => t.installed);
|
|
321
|
+
const notInstalled = allTools.filter((t) => !t.installed && t.category === "coding-agent");
|
|
322
|
+
// Group by category
|
|
323
|
+
const codingAgents = installed.filter((t) => t.category === "coding-agent");
|
|
324
|
+
const chatTools = installed.filter((t) => t.category === "chat");
|
|
325
|
+
const orchestration = installed.filter((t) => t.category === "orchestration");
|
|
326
|
+
const apiProviders = installed.filter((t) => t.category === "api-provider");
|
|
327
|
+
// Display found tools
|
|
328
|
+
if (installed.length === 0) {
|
|
329
|
+
console.log(` ${c.dim("No AI tools detected on this system.")}\n`);
|
|
330
|
+
console.log(c.dim(" You can add tools later with 'jobarbiter observe install'.\n"));
|
|
331
|
+
return { tools: [] };
|
|
332
|
+
}
|
|
333
|
+
console.log(` ${c.bold("Found:")}`);
|
|
334
|
+
// Show coding agents with observer status
|
|
335
|
+
for (const tool of codingAgents) {
|
|
336
|
+
const display = formatToolDisplay(tool);
|
|
337
|
+
if (tool.observerAvailable) {
|
|
338
|
+
if (tool.observerActive) {
|
|
339
|
+
console.log(` ${sym.check} ${display} ${c.dim("(observer active)")}`);
|
|
340
|
+
}
|
|
341
|
+
else {
|
|
342
|
+
console.log(` ${sym.check} ${display} ${c.success("(observer available)")}`);
|
|
343
|
+
}
|
|
333
344
|
}
|
|
334
345
|
else {
|
|
335
|
-
console.log(` ${c.
|
|
346
|
+
console.log(` ${sym.check} ${display} ${c.dim("(detected)")}`);
|
|
336
347
|
}
|
|
337
348
|
}
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
}
|
|
341
|
-
// Filter to agents that need installation
|
|
342
|
-
const needsInstall = detected.filter((a) => !a.hookInstalled);
|
|
343
|
-
if (needsInstall.length === 0) {
|
|
344
|
-
console.log(`\n ${c.dim("All detected agents already have observers installed.")}\n`);
|
|
345
|
-
return;
|
|
346
|
-
}
|
|
347
|
-
console.log(`\n JobArbiter observes your coding sessions to build your`);
|
|
348
|
-
console.log(` proficiency profile. ${c.bold("No code or prompts leave your machine")} —`);
|
|
349
|
-
console.log(` only aggregate scores (tool usage, session counts, token volume).\n`);
|
|
350
|
-
console.log(c.dim(` Data stored locally: ~/.config/jobarbiter/observer/observations.json`));
|
|
351
|
-
console.log(c.dim(` Review anytime: jobarbiter observe status\n`));
|
|
352
|
-
const installAll = await prompt.confirm(` Install observers for all ${needsInstall.length} detected agent${needsInstall.length > 1 ? "s" : ""}?`);
|
|
353
|
-
let toInstall;
|
|
354
|
-
if (installAll) {
|
|
355
|
-
toInstall = needsInstall.map((a) => a.id);
|
|
349
|
+
// Show other tools
|
|
350
|
+
for (const tool of chatTools) {
|
|
351
|
+
console.log(` ${sym.check} ${formatToolDisplay(tool)} ${c.dim("(detected)")}`);
|
|
356
352
|
}
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
353
|
+
for (const tool of orchestration) {
|
|
354
|
+
console.log(` ${sym.check} ${formatToolDisplay(tool)} ${c.dim("(detected)")}`);
|
|
355
|
+
}
|
|
356
|
+
for (const tool of apiProviders) {
|
|
357
|
+
console.log(` ${sym.check} ${tool.name} ${c.dim("configured")}`);
|
|
358
|
+
}
|
|
359
|
+
// Show not-detected coding agents
|
|
360
|
+
if (notInstalled.length > 0) {
|
|
361
|
+
console.log(`\n ${c.dim("Not detected (install to track):")}`);
|
|
362
|
+
for (const tool of notInstalled.slice(0, 5)) {
|
|
363
|
+
console.log(` ${c.dim("⬚")} ${tool.name}`);
|
|
363
364
|
}
|
|
364
|
-
|
|
365
|
-
.
|
|
366
|
-
.map(([k]) => k);
|
|
367
|
-
if (toInstall.length === 0) {
|
|
368
|
-
console.log(`\n ${c.dim("No observers installed. You can add them later with 'jobarbiter observe install'.")}\n`);
|
|
369
|
-
return;
|
|
365
|
+
if (notInstalled.length > 5) {
|
|
366
|
+
console.log(` ${c.dim(`... and ${notInstalled.length - 5} more`)}`);
|
|
370
367
|
}
|
|
371
368
|
}
|
|
372
|
-
//
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
console.log(`
|
|
369
|
+
// Collect tool names for profile
|
|
370
|
+
const toolNames = installed.map((t) => t.name);
|
|
371
|
+
// Observer installation for coding agents
|
|
372
|
+
const needsObserver = codingAgents.filter((t) => t.observerAvailable && !t.observerActive);
|
|
373
|
+
if (needsObserver.length > 0) {
|
|
374
|
+
console.log(`\n ${c.bold("Observers")}`);
|
|
375
|
+
console.log(` JobArbiter observes your coding sessions to build your`);
|
|
376
|
+
console.log(` proficiency profile. ${c.bold("No code or prompts leave your machine")} —`);
|
|
377
|
+
console.log(` only aggregate scores (tool usage, session counts, token volume).\n`);
|
|
378
|
+
console.log(c.dim(` Data stored locally: ~/.config/jobarbiter/observer/observations.json`));
|
|
379
|
+
console.log(c.dim(` Review anytime: jobarbiter observe status\n`));
|
|
380
|
+
const observerNames = needsObserver.map((t) => t.name).join(", ");
|
|
381
|
+
const installAll = await prompt.confirm(` Install observers for detected tools? (${observerNames})`);
|
|
382
|
+
if (installAll) {
|
|
383
|
+
const toInstall = needsObserver.map((t) => t.id);
|
|
384
|
+
console.log(c.dim("\n Installing observers..."));
|
|
385
|
+
const result = installObservers(toInstall);
|
|
386
|
+
for (const name of result.installed) {
|
|
387
|
+
console.log(` ${sym.check} ${name}`);
|
|
388
|
+
}
|
|
389
|
+
for (const name of result.skipped) {
|
|
390
|
+
console.log(` ${c.dim("—")} ${name} ${c.dim("(already installed)")}`);
|
|
391
|
+
}
|
|
392
|
+
for (const { agent, error: errMsg } of result.errors) {
|
|
393
|
+
console.log(` ${sym.cross} ${agent}: ${c.error(errMsg)}`);
|
|
394
|
+
}
|
|
395
|
+
if (result.installed.length > 0) {
|
|
396
|
+
console.log(`\n ${sym.check} ${c.success(`${result.installed.length} observer${result.installed.length > 1 ? "s" : ""} installed!`)}`);
|
|
397
|
+
console.log(c.dim(` Your proficiency profile will start building automatically.\n`));
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
else {
|
|
401
|
+
console.log(c.dim("\n Skipped — you can install observers later with 'jobarbiter observe install'.\n"));
|
|
402
|
+
}
|
|
380
403
|
}
|
|
381
|
-
|
|
382
|
-
|
|
404
|
+
else if (codingAgents.length > 0) {
|
|
405
|
+
const hasActiveObservers = codingAgents.some((t) => t.observerActive);
|
|
406
|
+
if (hasActiveObservers) {
|
|
407
|
+
console.log(`\n ${c.dim("All detected agents already have observers installed.")}\n`);
|
|
408
|
+
}
|
|
409
|
+
else {
|
|
410
|
+
console.log();
|
|
411
|
+
}
|
|
383
412
|
}
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
413
|
+
// "Did we miss anything?" prompt
|
|
414
|
+
console.log(` ${c.dim("Did we miss anything?")}`);
|
|
415
|
+
const additionalTools = await prompt.question(` Other AI tools you use ${c.dim("(comma-separated, or press Enter)")}: `);
|
|
416
|
+
if (additionalTools.trim()) {
|
|
417
|
+
const additional = additionalTools.split(",").map((s) => s.trim()).filter(Boolean);
|
|
418
|
+
toolNames.push(...additional);
|
|
419
|
+
console.log(` ${sym.check} Added: ${additional.join(", ")}\n`);
|
|
387
420
|
}
|
|
388
421
|
else {
|
|
389
422
|
console.log();
|
|
390
423
|
}
|
|
424
|
+
return { tools: toolNames };
|
|
391
425
|
}
|
|
392
426
|
function showWorkerCompletion(state) {
|
|
393
427
|
console.log(`${sym.done} ${c.bold("Step 6/6 — You're In!")}\n`);
|
package/package.json
CHANGED
package/src/index.ts
CHANGED
|
@@ -6,6 +6,7 @@ import { api, apiUnauthenticated, ApiError } from "./lib/api.js";
|
|
|
6
6
|
import { output, outputList, success, error, setJsonMode } from "./lib/output.js";
|
|
7
7
|
import { runOnboardWizard } from "./lib/onboard.js";
|
|
8
8
|
import { detectAgents, installObservers, removeObservers, getObservationStatus } from "./lib/observe.js";
|
|
9
|
+
import { getObservableTools, formatToolDisplay } from "./lib/detect-tools.js";
|
|
9
10
|
|
|
10
11
|
const program = new Command();
|
|
11
12
|
|