jobarbiter 0.3.2 ā 0.3.4
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/index.js +7 -7
- package/dist/lib/config.d.ts +2 -0
- package/dist/lib/detect-tools.d.ts +1 -1
- package/dist/lib/detect-tools.js +12 -12
- package/dist/lib/observe.d.ts +1 -1
- package/dist/lib/observe.js +4 -4
- package/dist/lib/onboard.js +211 -76
- package/dist/lib/providers.d.ts +48 -0
- package/dist/lib/providers.js +157 -0
- package/package.json +1 -1
- package/src/index.ts +7 -7
- package/src/lib/config.ts +2 -0
- package/src/lib/detect-tools.ts +13 -13
- package/src/lib/observe.ts +4 -4
- package/src/lib/onboard.ts +228 -75
- package/src/lib/providers.ts +205 -0
package/dist/index.js
CHANGED
|
@@ -743,9 +743,9 @@ program
|
|
|
743
743
|
}
|
|
744
744
|
});
|
|
745
745
|
// ============================================================
|
|
746
|
-
// observe (manage
|
|
746
|
+
// observe (manage AI agent observers)
|
|
747
747
|
// ============================================================
|
|
748
|
-
const observe = program.command("observe").description("Manage
|
|
748
|
+
const observe = program.command("observe").description("Manage AI agent proficiency observers");
|
|
749
749
|
observe
|
|
750
750
|
.command("status")
|
|
751
751
|
.description("Show observer status and accumulated data")
|
|
@@ -754,7 +754,7 @@ observe
|
|
|
754
754
|
const agents = detectAgents();
|
|
755
755
|
const status = getObservationStatus();
|
|
756
756
|
const detected = agents.filter((a) => a.installed);
|
|
757
|
-
console.log("\nš
|
|
757
|
+
console.log("\nš AI Agent Observers\n");
|
|
758
758
|
console.log(" Agents:");
|
|
759
759
|
for (const agent of agents) {
|
|
760
760
|
if (!agent.installed) {
|
|
@@ -795,7 +795,7 @@ observe
|
|
|
795
795
|
});
|
|
796
796
|
observe
|
|
797
797
|
.command("install")
|
|
798
|
-
.description("Install observers for detected
|
|
798
|
+
.description("Install observers for detected AI agents")
|
|
799
799
|
.option("--agent <id>", "Install for specific agent (claude-code, cursor, opencode, codex, gemini)")
|
|
800
800
|
.option("--all", "Install for all detected agents")
|
|
801
801
|
.action(async (opts) => {
|
|
@@ -803,7 +803,7 @@ observe
|
|
|
803
803
|
const agents = detectAgents();
|
|
804
804
|
const detected = agents.filter((a) => a.installed);
|
|
805
805
|
if (detected.length === 0) {
|
|
806
|
-
error("No
|
|
806
|
+
error("No AI agents detected on this system.");
|
|
807
807
|
console.log(" Supported: Claude Code, Cursor, OpenCode, Codex CLI, Gemini CLI");
|
|
808
808
|
process.exit(1);
|
|
809
809
|
}
|
|
@@ -841,7 +841,7 @@ observe
|
|
|
841
841
|
});
|
|
842
842
|
observe
|
|
843
843
|
.command("remove")
|
|
844
|
-
.description("Remove observers from
|
|
844
|
+
.description("Remove observers from AI agents")
|
|
845
845
|
.option("--agent <id>", "Remove from specific agent")
|
|
846
846
|
.option("--all", "Remove from all agents")
|
|
847
847
|
.action(async (opts) => {
|
|
@@ -880,7 +880,7 @@ observe
|
|
|
880
880
|
const status = getObservationStatus();
|
|
881
881
|
if (!status.hasData) {
|
|
882
882
|
console.log("\nNo observation data collected yet.");
|
|
883
|
-
console.log("Use your
|
|
883
|
+
console.log("Use your AI agents normally ā data accumulates automatically.\n");
|
|
884
884
|
process.exit(0);
|
|
885
885
|
}
|
|
886
886
|
console.log("\nš Observation Data Review\n");
|
package/dist/lib/config.d.ts
CHANGED
|
@@ -2,6 +2,8 @@ export interface Config {
|
|
|
2
2
|
apiKey: string;
|
|
3
3
|
baseUrl: string;
|
|
4
4
|
userType: "worker" | "employer" | "seeker" | "poster";
|
|
5
|
+
onboardingComplete?: boolean;
|
|
6
|
+
onboardingStep?: number;
|
|
5
7
|
}
|
|
6
8
|
export declare function getConfigPath(): string;
|
|
7
9
|
export declare function loadConfig(): Config | null;
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
*
|
|
8
8
|
* Never logs or stores API key values ā only checks for existence.
|
|
9
9
|
*/
|
|
10
|
-
export type ToolCategory = "
|
|
10
|
+
export type ToolCategory = "ai-agent" | "chat" | "orchestration" | "api-provider";
|
|
11
11
|
export interface DetectedTool {
|
|
12
12
|
id: string;
|
|
13
13
|
name: string;
|
package/dist/lib/detect-tools.js
CHANGED
|
@@ -13,11 +13,11 @@ import { homedir, platform } from "node:os";
|
|
|
13
13
|
import { execSync } from "node:child_process";
|
|
14
14
|
// āā Tool Definitions āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
|
|
15
15
|
const TOOL_DEFINITIONS = [
|
|
16
|
-
// AI
|
|
16
|
+
// AI AI Agents
|
|
17
17
|
{
|
|
18
18
|
id: "claude-code",
|
|
19
19
|
name: "Claude Code",
|
|
20
|
-
category: "
|
|
20
|
+
category: "ai-agent",
|
|
21
21
|
binary: "claude",
|
|
22
22
|
configDir: join(homedir(), ".claude"),
|
|
23
23
|
observerAvailable: true,
|
|
@@ -25,7 +25,7 @@ const TOOL_DEFINITIONS = [
|
|
|
25
25
|
{
|
|
26
26
|
id: "cursor",
|
|
27
27
|
name: "Cursor",
|
|
28
|
-
category: "
|
|
28
|
+
category: "ai-agent",
|
|
29
29
|
binary: "cursor",
|
|
30
30
|
configDir: join(homedir(), ".cursor"),
|
|
31
31
|
macApp: "/Applications/Cursor.app",
|
|
@@ -34,7 +34,7 @@ const TOOL_DEFINITIONS = [
|
|
|
34
34
|
{
|
|
35
35
|
id: "github-copilot",
|
|
36
36
|
name: "GitHub Copilot",
|
|
37
|
-
category: "
|
|
37
|
+
category: "ai-agent",
|
|
38
38
|
configDir: join(homedir(), ".config", "github-copilot"),
|
|
39
39
|
vscodeExtension: "github.copilot",
|
|
40
40
|
cursorExtension: "github.copilot",
|
|
@@ -43,7 +43,7 @@ const TOOL_DEFINITIONS = [
|
|
|
43
43
|
{
|
|
44
44
|
id: "codex",
|
|
45
45
|
name: "Codex CLI",
|
|
46
|
-
category: "
|
|
46
|
+
category: "ai-agent",
|
|
47
47
|
binary: "codex",
|
|
48
48
|
configDir: join(homedir(), ".codex"),
|
|
49
49
|
observerAvailable: true,
|
|
@@ -51,7 +51,7 @@ const TOOL_DEFINITIONS = [
|
|
|
51
51
|
{
|
|
52
52
|
id: "opencode",
|
|
53
53
|
name: "OpenCode",
|
|
54
|
-
category: "
|
|
54
|
+
category: "ai-agent",
|
|
55
55
|
binary: "opencode",
|
|
56
56
|
configDir: join(homedir(), ".config", "opencode"),
|
|
57
57
|
observerAvailable: true,
|
|
@@ -59,7 +59,7 @@ const TOOL_DEFINITIONS = [
|
|
|
59
59
|
{
|
|
60
60
|
id: "aider",
|
|
61
61
|
name: "Aider",
|
|
62
|
-
category: "
|
|
62
|
+
category: "ai-agent",
|
|
63
63
|
binary: "aider",
|
|
64
64
|
configDir: join(homedir(), ".aider"),
|
|
65
65
|
pipPackage: "aider-chat",
|
|
@@ -68,7 +68,7 @@ const TOOL_DEFINITIONS = [
|
|
|
68
68
|
{
|
|
69
69
|
id: "continue",
|
|
70
70
|
name: "Continue",
|
|
71
|
-
category: "
|
|
71
|
+
category: "ai-agent",
|
|
72
72
|
vscodeExtension: "continue.continue",
|
|
73
73
|
cursorExtension: "continue.continue",
|
|
74
74
|
observerAvailable: false,
|
|
@@ -76,7 +76,7 @@ const TOOL_DEFINITIONS = [
|
|
|
76
76
|
{
|
|
77
77
|
id: "cline",
|
|
78
78
|
name: "Cline",
|
|
79
|
-
category: "
|
|
79
|
+
category: "ai-agent",
|
|
80
80
|
vscodeExtension: "saoudrizwan.claude-dev",
|
|
81
81
|
cursorExtension: "saoudrizwan.claude-dev",
|
|
82
82
|
observerAvailable: false,
|
|
@@ -84,7 +84,7 @@ const TOOL_DEFINITIONS = [
|
|
|
84
84
|
{
|
|
85
85
|
id: "windsurf",
|
|
86
86
|
name: "Windsurf",
|
|
87
|
-
category: "
|
|
87
|
+
category: "ai-agent",
|
|
88
88
|
binary: "windsurf",
|
|
89
89
|
macApp: "/Applications/Windsurf.app",
|
|
90
90
|
observerAvailable: false,
|
|
@@ -92,7 +92,7 @@ const TOOL_DEFINITIONS = [
|
|
|
92
92
|
{
|
|
93
93
|
id: "letta",
|
|
94
94
|
name: "Letta Code",
|
|
95
|
-
category: "
|
|
95
|
+
category: "ai-agent",
|
|
96
96
|
binary: "letta",
|
|
97
97
|
configDir: join(homedir(), ".letta"),
|
|
98
98
|
pipPackage: "letta",
|
|
@@ -101,7 +101,7 @@ const TOOL_DEFINITIONS = [
|
|
|
101
101
|
{
|
|
102
102
|
id: "gemini",
|
|
103
103
|
name: "Gemini CLI",
|
|
104
|
-
category: "
|
|
104
|
+
category: "ai-agent",
|
|
105
105
|
binary: "gemini",
|
|
106
106
|
configDir: join(homedir(), ".gemini"),
|
|
107
107
|
observerAvailable: true,
|
package/dist/lib/observe.d.ts
CHANGED
package/dist/lib/observe.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* JobArbiter Observer ā Hook installer for
|
|
2
|
+
* JobArbiter Observer ā Hook installer for AI agent CLIs
|
|
3
3
|
*
|
|
4
4
|
* Installs observation hooks that extract proficiency signals from
|
|
5
5
|
* session transcripts. Uses detect-tools.ts for agent detection.
|
|
@@ -64,7 +64,7 @@ function ensureObserverDirs() {
|
|
|
64
64
|
}
|
|
65
65
|
// āā Core Observer Script āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
|
|
66
66
|
/**
|
|
67
|
-
* The universal observer script. Runs as a hook in any
|
|
67
|
+
* The universal observer script. Runs as a hook in any AI agent.
|
|
68
68
|
* Reads session transcript data from stdin (JSON), extracts proficiency
|
|
69
69
|
* signals, and appends them to the local observations file.
|
|
70
70
|
*
|
|
@@ -75,7 +75,7 @@ function getObserverScript() {
|
|
|
75
75
|
return `#!/usr/bin/env node
|
|
76
76
|
/**
|
|
77
77
|
* JobArbiter Observer Hook
|
|
78
|
-
* Extracts proficiency signals from
|
|
78
|
+
* Extracts proficiency signals from AI agent sessions.
|
|
79
79
|
*
|
|
80
80
|
* Reads JSON from stdin, writes observations to:
|
|
81
81
|
* ~/.config/jobarbiter/observer/observations.json
|
|
@@ -105,7 +105,7 @@ process.stdin.on("end", () => {
|
|
|
105
105
|
const observation = extractSignals(data);
|
|
106
106
|
if (observation) appendObservation(observation);
|
|
107
107
|
} catch (err) {
|
|
108
|
-
// Silent failure ā never block the
|
|
108
|
+
// Silent failure ā never block the AI agent
|
|
109
109
|
fs.appendFileSync(
|
|
110
110
|
path.join(os.homedir(), ".config", "jobarbiter", "observer", "errors.log"),
|
|
111
111
|
\`[\${new Date().toISOString()}] \${err.message}\\n\`
|
package/dist/lib/onboard.js
CHANGED
|
@@ -12,6 +12,7 @@ import { loadConfig, saveConfig, getConfigPath } from "./config.js";
|
|
|
12
12
|
import { apiUnauthenticated, api, ApiError } from "./api.js";
|
|
13
13
|
import { installObservers } from "./observe.js";
|
|
14
14
|
import { detectAllTools, formatToolDisplay, } from "./detect-tools.js";
|
|
15
|
+
import { loadProviderKeys, saveProviderKey, validateProviderKey, } from "./providers.js";
|
|
15
16
|
// āā ANSI Colors āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
|
|
16
17
|
const colors = {
|
|
17
18
|
reset: "\x1b[0m",
|
|
@@ -92,13 +93,45 @@ class Prompt {
|
|
|
92
93
|
// āā Main Wizard āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
|
|
93
94
|
export async function runOnboardWizard(opts) {
|
|
94
95
|
const baseUrl = opts.baseUrl || "https://jobarbiter-api-production.up.railway.app";
|
|
95
|
-
// Check for existing config
|
|
96
|
+
// Check for existing config ā resume if onboarding incomplete
|
|
96
97
|
const existingConfig = loadConfig();
|
|
97
98
|
if (existingConfig && !opts.force) {
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
99
|
+
if (existingConfig.onboardingComplete) {
|
|
100
|
+
console.log(`\n${sym.check} ${c.success("You're already onboarded!")}`);
|
|
101
|
+
console.log(`\n Run ${c.highlight("jobarbiter status")} to check your account.`);
|
|
102
|
+
console.log(` Run ${c.highlight("jobarbiter onboard --force")} to start fresh.\n`);
|
|
103
|
+
process.exit(0);
|
|
104
|
+
}
|
|
105
|
+
// Onboarding incomplete ā resume
|
|
106
|
+
const resumeStep = (existingConfig.onboardingStep ?? 1) + 1;
|
|
107
|
+
console.log(`\n${sym.rocket} ${c.bold("Resuming onboarding")} from step ${resumeStep}/7\n`);
|
|
108
|
+
console.log(c.dim(` Account: ${existingConfig.userType} | API key configured`));
|
|
109
|
+
console.log(c.dim(` Run ${c.highlight("jobarbiter onboard --force")} to start over.\n`));
|
|
110
|
+
const prompt = new Prompt();
|
|
111
|
+
const state = {
|
|
112
|
+
baseUrl,
|
|
113
|
+
apiKey: existingConfig.apiKey,
|
|
114
|
+
userType: existingConfig.userType,
|
|
115
|
+
userId: "",
|
|
116
|
+
email: "",
|
|
117
|
+
};
|
|
118
|
+
try {
|
|
119
|
+
if (existingConfig.userType === "worker" || existingConfig.userType === "seeker") {
|
|
120
|
+
await runWorkerFlow(prompt, state, resumeStep);
|
|
121
|
+
}
|
|
122
|
+
else {
|
|
123
|
+
await runEmployerFlow(prompt, state);
|
|
124
|
+
}
|
|
125
|
+
prompt.close();
|
|
126
|
+
}
|
|
127
|
+
catch (err) {
|
|
128
|
+
prompt.close();
|
|
129
|
+
if (err instanceof Error) {
|
|
130
|
+
console.log(`\n${sym.cross} ${c.error(err.message)}`);
|
|
131
|
+
}
|
|
132
|
+
process.exit(1);
|
|
133
|
+
}
|
|
134
|
+
return;
|
|
102
135
|
}
|
|
103
136
|
const prompt = new Prompt();
|
|
104
137
|
const state = { baseUrl };
|
|
@@ -112,11 +145,13 @@ export async function runOnboardWizard(opts) {
|
|
|
112
145
|
state.email = email;
|
|
113
146
|
state.apiKey = apiKey;
|
|
114
147
|
state.userId = userId;
|
|
115
|
-
// Save config immediately after verification
|
|
148
|
+
// Save config immediately after verification (with step progress)
|
|
116
149
|
saveConfig({
|
|
117
150
|
apiKey,
|
|
118
151
|
baseUrl,
|
|
119
152
|
userType,
|
|
153
|
+
onboardingStep: 1,
|
|
154
|
+
onboardingComplete: false,
|
|
120
155
|
});
|
|
121
156
|
if (userType === "worker") {
|
|
122
157
|
await runWorkerFlow(prompt, state);
|
|
@@ -165,9 +200,9 @@ async function selectUserType(prompt) {
|
|
|
165
200
|
}
|
|
166
201
|
// āā Email & Verification āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
|
|
167
202
|
async function handleEmailVerification(prompt, baseUrl, userType) {
|
|
168
|
-
// Workers: 1) Account, 2) Tool Detection, 3)
|
|
169
|
-
// Employers: 1) Account, 2) (skip verification), 3) Company, 4) Domain, 5) What You Need, 6) Done
|
|
170
|
-
const totalSteps = 6;
|
|
203
|
+
// Workers: 1) Account, 2) Tool Detection, 3) AI Accounts, 4) Domains, 5) GitHub, 6) LinkedIn, 7) Done
|
|
204
|
+
// Employers: 1) Account, 2) (skip verification), 3) Company, 4) Domain, 5) What You Need, 6) Done (stays at 6)
|
|
205
|
+
const totalSteps = userType === "employer" ? 6 : 7;
|
|
171
206
|
console.log(`\n${sym.email} ${c.bold(`Step 1/${totalSteps} ā Create Your Account`)}\n`);
|
|
172
207
|
// Get email
|
|
173
208
|
let email;
|
|
@@ -237,90 +272,111 @@ async function handleEmailVerification(prompt, baseUrl, userType) {
|
|
|
237
272
|
return { email, apiKey, userId };
|
|
238
273
|
}
|
|
239
274
|
// āā Worker Flow āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
|
|
240
|
-
async function runWorkerFlow(prompt, state) {
|
|
275
|
+
async function runWorkerFlow(prompt, state, startStep = 2) {
|
|
241
276
|
const config = {
|
|
242
277
|
apiKey: state.apiKey,
|
|
243
278
|
baseUrl: state.baseUrl,
|
|
244
279
|
userType: "worker",
|
|
245
280
|
};
|
|
281
|
+
const saveProgress = (step) => {
|
|
282
|
+
saveConfig({ ...config, onboardingStep: step });
|
|
283
|
+
};
|
|
246
284
|
// Step 2: Auto-detect AI Tools
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
catch (err) {
|
|
268
|
-
console.log(`${sym.warning} ${c.warning("Could not save profile details ā you can update later with 'jobarbiter profile create'")}\n`);
|
|
269
|
-
}
|
|
270
|
-
// Step 4: Connect GitHub (optional)
|
|
271
|
-
console.log(`${sym.link} ${c.bold("Step 4/6 ā Connect GitHub")} ${c.dim("(optional)")}\n`);
|
|
272
|
-
console.log(`Connecting your GitHub lets us analyze your AI-assisted work patterns.`);
|
|
273
|
-
console.log(`This significantly boosts your proficiency score.\n`);
|
|
274
|
-
const githubUsername = await prompt.question(`GitHub username ${c.dim("(press Enter to skip)")}: `);
|
|
275
|
-
if (githubUsername) {
|
|
276
|
-
console.log(c.dim("\nConnecting GitHub..."));
|
|
285
|
+
if (startStep <= 2) {
|
|
286
|
+
const detectedToolsResult = await runToolDetectionStep(prompt, config);
|
|
287
|
+
state.tools = detectedToolsResult.tools;
|
|
288
|
+
saveProgress(2);
|
|
289
|
+
}
|
|
290
|
+
// Step 3: Connect AI Accounts (optional)
|
|
291
|
+
if (startStep <= 3) {
|
|
292
|
+
await runConnectAIAccountsStep(prompt);
|
|
293
|
+
saveProgress(3);
|
|
294
|
+
}
|
|
295
|
+
// Step 4: Domains
|
|
296
|
+
if (startStep <= 4) {
|
|
297
|
+
console.log(`${sym.target} ${c.bold("Step 4/7 ā Your Domains")}\n`);
|
|
298
|
+
console.log(`What domains do you work in? ${c.dim("(comma-separated)")}`);
|
|
299
|
+
console.log(c.dim("Examples: full-stack dev, data engineering, trading, content creation\n"));
|
|
300
|
+
const domainsInput = await prompt.question(`${sym.arrow} `);
|
|
301
|
+
const domains = domainsInput.split(",").map(s => s.trim()).filter(Boolean);
|
|
302
|
+
state.domains = domains;
|
|
303
|
+
// Create/update profile
|
|
304
|
+
console.log(c.dim("\nSaving profile..."));
|
|
277
305
|
try {
|
|
278
|
-
await api(config, "POST", "/v1/
|
|
279
|
-
|
|
280
|
-
|
|
306
|
+
await api(config, "POST", "/v1/profile", {
|
|
307
|
+
domains,
|
|
308
|
+
tools: {
|
|
309
|
+
primary: state.tools,
|
|
310
|
+
},
|
|
281
311
|
});
|
|
282
|
-
console.log(`${sym.check}
|
|
283
|
-
state.githubUsername = githubUsername;
|
|
312
|
+
console.log(`${sym.check} Profile saved\n`);
|
|
284
313
|
}
|
|
285
314
|
catch (err) {
|
|
286
|
-
console.log(`${sym.warning} ${c.warning("Could not
|
|
315
|
+
console.log(`${sym.warning} ${c.warning("Could not save profile details ā you can update later with 'jobarbiter profile create'")}\n`);
|
|
287
316
|
}
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
317
|
+
saveProgress(4);
|
|
318
|
+
}
|
|
319
|
+
// Step 5: Connect GitHub (optional)
|
|
320
|
+
if (startStep <= 5) {
|
|
321
|
+
console.log(`${sym.link} ${c.bold("Step 5/7 ā Connect GitHub")} ${c.dim("(optional)")}\n`);
|
|
322
|
+
console.log(`Connecting your GitHub lets us analyze your AI-assisted work patterns.`);
|
|
323
|
+
console.log(`This significantly boosts your proficiency score.\n`);
|
|
324
|
+
const githubUsername = await prompt.question(`GitHub username ${c.dim("(press Enter to skip)")}: `);
|
|
325
|
+
if (githubUsername) {
|
|
326
|
+
console.log(c.dim("\nConnecting GitHub..."));
|
|
327
|
+
try {
|
|
328
|
+
await api(config, "POST", "/v1/attestations/git/connect", {
|
|
329
|
+
provider: "github",
|
|
330
|
+
username: githubUsername,
|
|
331
|
+
});
|
|
332
|
+
console.log(`${sym.check} GitHub connected: ${c.highlight(githubUsername)}\n`);
|
|
333
|
+
state.githubUsername = githubUsername;
|
|
334
|
+
}
|
|
335
|
+
catch (err) {
|
|
336
|
+
console.log(`${sym.warning} ${c.warning("Could not connect GitHub ā you can try later with 'jobarbiter git connect'")}\n`);
|
|
337
|
+
}
|
|
304
338
|
}
|
|
305
|
-
|
|
306
|
-
console.log(`${
|
|
339
|
+
else {
|
|
340
|
+
console.log(`${c.dim("Skipped ā you can connect later with 'jobarbiter git connect'")}\n`);
|
|
307
341
|
}
|
|
342
|
+
saveProgress(5);
|
|
343
|
+
}
|
|
344
|
+
// Step 6: Connect LinkedIn (optional)
|
|
345
|
+
if (startStep <= 6) {
|
|
346
|
+
console.log(`${sym.link} ${c.bold("Step 6/7 ā Connect LinkedIn")} ${c.dim("(optional)")}\n`);
|
|
347
|
+
console.log(`Your LinkedIn profile strengthens identity verification.`);
|
|
348
|
+
console.log(c.dim("We never post on your behalf or access your connections.\n"));
|
|
349
|
+
const linkedinUrl = await prompt.question(`LinkedIn URL ${c.dim("(press Enter to skip)")}: `);
|
|
350
|
+
if (linkedinUrl) {
|
|
351
|
+
console.log(c.dim("\nSubmitting for verification..."));
|
|
352
|
+
try {
|
|
353
|
+
await api(config, "POST", "/v1/verification/linkedin", {
|
|
354
|
+
linkedinUrl: linkedinUrl.trim(),
|
|
355
|
+
});
|
|
356
|
+
console.log(`${sym.check} LinkedIn submitted for verification\n`);
|
|
357
|
+
}
|
|
358
|
+
catch (err) {
|
|
359
|
+
console.log(`${sym.warning} ${c.warning("Could not submit LinkedIn ā you can try later with 'jobarbiter identity linkedin <url>'")}\n`);
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
else {
|
|
363
|
+
console.log(`${c.dim("Skipped ā you can connect later with 'jobarbiter identity linkedin <url>'")}\n`);
|
|
364
|
+
}
|
|
365
|
+
saveProgress(6);
|
|
308
366
|
}
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
}
|
|
312
|
-
// Step 6: Done!
|
|
367
|
+
// Step 7: Done!
|
|
368
|
+
saveConfig({ ...config, onboardingComplete: true, onboardingStep: 7 });
|
|
313
369
|
showWorkerCompletion(state);
|
|
314
370
|
}
|
|
315
371
|
// āā Tool Detection Step āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
|
|
316
372
|
async function runToolDetectionStep(prompt, config) {
|
|
317
|
-
console.log(`š ${c.bold("Step 2/
|
|
373
|
+
console.log(`š ${c.bold("Step 2/7 ā Detecting AI Tools")}\n`);
|
|
318
374
|
console.log(c.dim(" Scanning your machine...\n"));
|
|
319
375
|
const allTools = detectAllTools();
|
|
320
376
|
const installed = allTools.filter((t) => t.installed);
|
|
321
|
-
const notInstalled = allTools.filter((t) => !t.installed && t.category === "
|
|
377
|
+
const notInstalled = allTools.filter((t) => !t.installed && t.category === "ai-agent");
|
|
322
378
|
// Group by category
|
|
323
|
-
const codingAgents = installed.filter((t) => t.category === "
|
|
379
|
+
const codingAgents = installed.filter((t) => t.category === "ai-agent");
|
|
324
380
|
const chatTools = installed.filter((t) => t.category === "chat");
|
|
325
381
|
const orchestration = installed.filter((t) => t.category === "orchestration");
|
|
326
382
|
const apiProviders = installed.filter((t) => t.category === "api-provider");
|
|
@@ -331,7 +387,7 @@ async function runToolDetectionStep(prompt, config) {
|
|
|
331
387
|
return { tools: [] };
|
|
332
388
|
}
|
|
333
389
|
console.log(` ${c.bold("Found:")}`);
|
|
334
|
-
// Show
|
|
390
|
+
// Show AI agents with observer status
|
|
335
391
|
for (const tool of codingAgents) {
|
|
336
392
|
const display = formatToolDisplay(tool);
|
|
337
393
|
if (tool.observerAvailable) {
|
|
@@ -356,7 +412,7 @@ async function runToolDetectionStep(prompt, config) {
|
|
|
356
412
|
for (const tool of apiProviders) {
|
|
357
413
|
console.log(` ${sym.check} ${tool.name} ${c.dim("configured")}`);
|
|
358
414
|
}
|
|
359
|
-
// Show not-detected
|
|
415
|
+
// Show not-detected AI agents
|
|
360
416
|
if (notInstalled.length > 0) {
|
|
361
417
|
console.log(`\n ${c.dim("Not detected (install to track):")}`);
|
|
362
418
|
for (const tool of notInstalled.slice(0, 5)) {
|
|
@@ -368,11 +424,11 @@ async function runToolDetectionStep(prompt, config) {
|
|
|
368
424
|
}
|
|
369
425
|
// Collect tool names for profile
|
|
370
426
|
const toolNames = installed.map((t) => t.name);
|
|
371
|
-
// Observer installation for
|
|
427
|
+
// Observer installation for AI agents
|
|
372
428
|
const needsObserver = codingAgents.filter((t) => t.observerAvailable && !t.observerActive);
|
|
373
429
|
if (needsObserver.length > 0) {
|
|
374
430
|
console.log(`\n ${c.bold("Observers")}`);
|
|
375
|
-
console.log(` JobArbiter observes your
|
|
431
|
+
console.log(` JobArbiter observes your AI sessions to build your`);
|
|
376
432
|
console.log(` proficiency profile. ${c.bold("No code or prompts leave your machine")} ā`);
|
|
377
433
|
console.log(` only aggregate scores (tool usage, session counts, token volume).\n`);
|
|
378
434
|
console.log(c.dim(` Data stored locally: ~/.config/jobarbiter/observer/observations.json`));
|
|
@@ -423,8 +479,87 @@ async function runToolDetectionStep(prompt, config) {
|
|
|
423
479
|
}
|
|
424
480
|
return { tools: toolNames };
|
|
425
481
|
}
|
|
482
|
+
// āā Connect AI Accounts Step āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
|
|
483
|
+
async function runConnectAIAccountsStep(prompt) {
|
|
484
|
+
console.log(`${sym.link} ${c.bold("Step 3/7 ā Connect AI Accounts")} ${c.dim("(optional)")}\n`);
|
|
485
|
+
console.log(` Connecting your AI provider accounts lets us analyze usage depth,`);
|
|
486
|
+
console.log(` not just tool presence. The more we can see, the stronger your`);
|
|
487
|
+
console.log(` verified proficiency profile.\n`);
|
|
488
|
+
console.log(` ${c.bold("What we can pull:")}`);
|
|
489
|
+
console.log(` ${sym.bullet} Token usage volume and patterns ${c.dim("(how much you use AI)")}`);
|
|
490
|
+
console.log(` ${sym.bullet} Model preferences ${c.dim("(which models you reach for)")}`);
|
|
491
|
+
console.log(` ${sym.bullet} Session frequency and consistency over time`);
|
|
492
|
+
console.log(` ${sym.bullet} API spend patterns ${c.dim("(demonstrates serious usage)")}\n`);
|
|
493
|
+
console.log(` ${c.bold("What we NEVER access:")}`);
|
|
494
|
+
console.log(` ${sym.bullet} Your conversation content`);
|
|
495
|
+
console.log(` ${sym.bullet} Prompts or responses`);
|
|
496
|
+
console.log(` ${sym.bullet} Any proprietary or sensitive data\n`);
|
|
497
|
+
// Check for already connected providers
|
|
498
|
+
const existingProviders = loadProviderKeys();
|
|
499
|
+
if (existingProviders.length > 0) {
|
|
500
|
+
console.log(` ${c.bold("Already connected:")}`);
|
|
501
|
+
for (const p of existingProviders) {
|
|
502
|
+
console.log(` ${sym.check} ${p.provider}`);
|
|
503
|
+
}
|
|
504
|
+
console.log();
|
|
505
|
+
}
|
|
506
|
+
// Show available connections
|
|
507
|
+
let continueConnecting = true;
|
|
508
|
+
while (continueConnecting) {
|
|
509
|
+
console.log(` ${c.bold("Available connections:")}\n`);
|
|
510
|
+
console.log(` ${c.highlight("1.")} Anthropic API key ā Pull Claude usage stats`);
|
|
511
|
+
console.log(` ${c.highlight("2.")} OpenAI API key ā Pull GPT/ChatGPT usage stats`);
|
|
512
|
+
console.log(` ${c.highlight("3.")} Skip for now\n`);
|
|
513
|
+
console.log(c.dim(` You can connect accounts later with 'jobarbiter tokens connect'\n`));
|
|
514
|
+
const choice = await prompt.question(` Your choice ${c.dim("[1/2/3]")}: `);
|
|
515
|
+
if (choice === "3" || choice.toLowerCase() === "skip" || choice === "") {
|
|
516
|
+
console.log(`\n${c.dim(" Skipped ā you can connect providers later with 'jobarbiter tokens connect'")}\n`);
|
|
517
|
+
continueConnecting = false;
|
|
518
|
+
}
|
|
519
|
+
else if (choice === "1") {
|
|
520
|
+
await connectProvider(prompt, "anthropic", "Anthropic");
|
|
521
|
+
// Ask if they want to connect another
|
|
522
|
+
continueConnecting = await prompt.confirm(`\n Connect another provider?`, false);
|
|
523
|
+
console.log();
|
|
524
|
+
}
|
|
525
|
+
else if (choice === "2") {
|
|
526
|
+
await connectProvider(prompt, "openai", "OpenAI");
|
|
527
|
+
// Ask if they want to connect another
|
|
528
|
+
continueConnecting = await prompt.confirm(`\n Connect another provider?`, false);
|
|
529
|
+
console.log();
|
|
530
|
+
}
|
|
531
|
+
else {
|
|
532
|
+
console.log(c.error(" Please enter 1, 2, or 3"));
|
|
533
|
+
}
|
|
534
|
+
}
|
|
535
|
+
}
|
|
536
|
+
async function connectProvider(prompt, providerId, providerName) {
|
|
537
|
+
console.log(`\n ${sym.lock} ${c.bold("Privacy Notice")}`);
|
|
538
|
+
console.log(` ${c.dim("ā".repeat(50))}`);
|
|
539
|
+
console.log(` Your API key is stored ${c.bold("locally only")} at:`);
|
|
540
|
+
console.log(` ${c.dim("~/.config/jobarbiter/providers.json")}\n`);
|
|
541
|
+
console.log(` ${sym.bullet} Keys are ${c.bold("NEVER")} sent to JobArbiter's servers`);
|
|
542
|
+
console.log(` ${sym.bullet} Only aggregate stats (token counts, model usage) are submitted`);
|
|
543
|
+
console.log(` ${sym.bullet} Revoke anytime: ${c.highlight(`jobarbiter tokens disconnect ${providerId}`)}`);
|
|
544
|
+
console.log(` ${c.dim("ā".repeat(50))}\n`);
|
|
545
|
+
const apiKey = await prompt.question(` ${providerName} API key: `);
|
|
546
|
+
if (!apiKey || apiKey.trim() === "") {
|
|
547
|
+
console.log(` ${c.dim("Skipped")}`);
|
|
548
|
+
return;
|
|
549
|
+
}
|
|
550
|
+
console.log(c.dim("\n Validating API key..."));
|
|
551
|
+
const result = await validateProviderKey(providerId, apiKey.trim());
|
|
552
|
+
if (result.valid) {
|
|
553
|
+
saveProviderKey(providerId, apiKey.trim());
|
|
554
|
+
console.log(` ${sym.check} ${c.success(`${providerName} connected`)} ā ${result.summary}`);
|
|
555
|
+
}
|
|
556
|
+
else {
|
|
557
|
+
console.log(` ${sym.cross} ${c.error(`Invalid key: ${result.error}`)}`);
|
|
558
|
+
console.log(` ${c.dim("You can try again later with 'jobarbiter tokens connect'")}`);
|
|
559
|
+
}
|
|
560
|
+
}
|
|
426
561
|
function showWorkerCompletion(state) {
|
|
427
|
-
console.log(`${sym.done} ${c.bold("Step
|
|
562
|
+
console.log(`${sym.done} ${c.bold("Step 7/7 ā You're In!")}\n`);
|
|
428
563
|
console.log(`Your profile is live. Here's what happens next:\n`);
|
|
429
564
|
console.log(` š Your proficiency score builds automatically from:`);
|
|
430
565
|
console.log(` ${sym.bullet} Coding agent observation ${c.dim("(biggest factor ā 35%)")}`);
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Provider Key Management Module
|
|
3
|
+
*
|
|
4
|
+
* Securely manages API keys for AI providers (Anthropic, OpenAI, etc.)
|
|
5
|
+
* Keys are stored locally ONLY and never sent to JobArbiter's servers.
|
|
6
|
+
* Only aggregate usage stats are submitted for proficiency scoring.
|
|
7
|
+
*/
|
|
8
|
+
export interface ProviderConfig {
|
|
9
|
+
provider: string;
|
|
10
|
+
apiKey: string;
|
|
11
|
+
connectedAt: string;
|
|
12
|
+
lastSync?: string;
|
|
13
|
+
}
|
|
14
|
+
export interface ProvidersFile {
|
|
15
|
+
version: number;
|
|
16
|
+
providers: ProviderConfig[];
|
|
17
|
+
}
|
|
18
|
+
export interface ValidationResult {
|
|
19
|
+
valid: boolean;
|
|
20
|
+
error?: string;
|
|
21
|
+
summary?: string;
|
|
22
|
+
}
|
|
23
|
+
export declare function getProvidersPath(): string;
|
|
24
|
+
export declare function loadProviderKeys(): ProviderConfig[];
|
|
25
|
+
export declare function saveProviderKey(provider: string, apiKey: string): void;
|
|
26
|
+
export declare function removeProviderKey(provider: string): boolean;
|
|
27
|
+
export declare function getProviderKey(provider: string): ProviderConfig | null;
|
|
28
|
+
/**
|
|
29
|
+
* Validate an Anthropic API key by making a test API call.
|
|
30
|
+
* Returns validation result with usage summary if available.
|
|
31
|
+
*/
|
|
32
|
+
export declare function validateAnthropicKey(apiKey: string): Promise<ValidationResult>;
|
|
33
|
+
/**
|
|
34
|
+
* Validate an OpenAI API key by making a test API call.
|
|
35
|
+
* Returns validation result with usage summary if available.
|
|
36
|
+
*/
|
|
37
|
+
export declare function validateOpenAIKey(apiKey: string): Promise<ValidationResult>;
|
|
38
|
+
/**
|
|
39
|
+
* Get list of supported providers.
|
|
40
|
+
*/
|
|
41
|
+
export declare function getSupportedProviders(): Array<{
|
|
42
|
+
id: string;
|
|
43
|
+
name: string;
|
|
44
|
+
}>;
|
|
45
|
+
/**
|
|
46
|
+
* Validate a key for any supported provider.
|
|
47
|
+
*/
|
|
48
|
+
export declare function validateProviderKey(provider: string, apiKey: string): Promise<ValidationResult>;
|