jobarbiter 0.3.0 → 0.3.1
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 +172 -43
- package/dist/lib/config.js +1 -1
- package/dist/lib/observe.d.ts +48 -0
- package/dist/lib/observe.js +690 -0
- package/dist/lib/onboard.d.ts +13 -0
- package/dist/lib/onboard.js +546 -0
- package/package.json +15 -5
- package/src/index.ts +193 -48
- package/src/lib/config.ts +1 -1
- package/src/lib/observe.ts +768 -0
- package/src/lib/onboard.ts +658 -0
- package/test/smoke.test.ts +205 -0
- package/vitest.config.ts +8 -0
package/dist/index.js
CHANGED
|
@@ -3,6 +3,8 @@ import { Command } from "commander";
|
|
|
3
3
|
import { saveConfig, requireConfig, getConfigPath } from "./lib/config.js";
|
|
4
4
|
import { api, apiUnauthenticated, ApiError } from "./lib/api.js";
|
|
5
5
|
import { output, outputList, success, error, setJsonMode } from "./lib/output.js";
|
|
6
|
+
import { runOnboardWizard } from "./lib/onboard.js";
|
|
7
|
+
import { detectAgents, installObservers, removeObservers, getObservationStatus } from "./lib/observe.js";
|
|
6
8
|
const program = new Command();
|
|
7
9
|
program
|
|
8
10
|
.name("jobarbiter")
|
|
@@ -15,57 +17,24 @@ program
|
|
|
15
17
|
setJsonMode(true);
|
|
16
18
|
});
|
|
17
19
|
// ============================================================
|
|
18
|
-
//
|
|
20
|
+
// onboard — Interactive setup wizard (primary entry point)
|
|
19
21
|
// ============================================================
|
|
20
22
|
program
|
|
21
|
-
.command("
|
|
22
|
-
.description("
|
|
23
|
-
.
|
|
24
|
-
.requiredOption("--type <type>", "Account type: worker or employer")
|
|
23
|
+
.command("onboard")
|
|
24
|
+
.description("Interactive setup wizard — the only command you need to get started")
|
|
25
|
+
.option("--force", "Start fresh even if already configured")
|
|
25
26
|
.option("--base-url <url>", "API base URL", "https://jobarbiter-api-production.up.railway.app")
|
|
26
27
|
.action(async (opts) => {
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
process.exit(1);
|
|
32
|
-
}
|
|
33
|
-
// Step 1: Register and request verification code
|
|
34
|
-
const registerData = await apiUnauthenticated(opts.baseUrl, "POST", "/v1/auth/register", {
|
|
35
|
-
email: opts.email,
|
|
36
|
-
userType,
|
|
37
|
-
});
|
|
38
|
-
success(`Verification code sent to ${opts.email} (expires in 15 minutes)`);
|
|
39
|
-
// Step 2: Prompt for verification code
|
|
40
|
-
const code = await promptForCode();
|
|
41
|
-
if (!code) {
|
|
42
|
-
error("Verification cancelled.");
|
|
43
|
-
process.exit(1);
|
|
44
|
-
}
|
|
45
|
-
// Step 3: Verify the code
|
|
46
|
-
const verifyData = await apiUnauthenticated(opts.baseUrl, "POST", "/v1/auth/verify", {
|
|
47
|
-
email: opts.email,
|
|
48
|
-
code: code.trim(),
|
|
49
|
-
});
|
|
50
|
-
// Step 4: Save config with API key
|
|
51
|
-
saveConfig({
|
|
52
|
-
apiKey: verifyData.apiKey,
|
|
53
|
-
baseUrl: opts.baseUrl,
|
|
54
|
-
userType: userType,
|
|
55
|
-
});
|
|
56
|
-
success(`Email verified! API key saved to ${getConfigPath()}`);
|
|
57
|
-
console.log(` Key: ${verifyData.apiKey.slice(0, 20)}... (save this — shown only once)`);
|
|
58
|
-
output({ id: verifyData.id, email: opts.email, userType });
|
|
59
|
-
}
|
|
60
|
-
catch (e) {
|
|
61
|
-
handleError(e);
|
|
62
|
-
}
|
|
28
|
+
await runOnboardWizard({
|
|
29
|
+
force: opts.force,
|
|
30
|
+
baseUrl: opts.baseUrl,
|
|
31
|
+
});
|
|
63
32
|
});
|
|
64
33
|
// ============================================================
|
|
65
34
|
// verify (standalone verification for existing registrations)
|
|
66
35
|
// ============================================================
|
|
67
36
|
program
|
|
68
|
-
.command("verify")
|
|
37
|
+
.command("verify-email")
|
|
69
38
|
.description("Verify email with code (if registration was interrupted)")
|
|
70
39
|
.requiredOption("--email <email>", "Email address")
|
|
71
40
|
.option("--code <code>", "6-digit verification code")
|
|
@@ -706,7 +675,7 @@ program
|
|
|
706
675
|
// ============================================================
|
|
707
676
|
// verify
|
|
708
677
|
// ============================================================
|
|
709
|
-
const verify = program.command("
|
|
678
|
+
const verify = program.command("identity").description("Identity verification (GitHub, LinkedIn, domain)");
|
|
710
679
|
verify
|
|
711
680
|
.command("linkedin <url>")
|
|
712
681
|
.description("Submit LinkedIn profile for verification")
|
|
@@ -774,6 +743,166 @@ program
|
|
|
774
743
|
}
|
|
775
744
|
});
|
|
776
745
|
// ============================================================
|
|
746
|
+
// observe (manage coding agent observers)
|
|
747
|
+
// ============================================================
|
|
748
|
+
const observe = program.command("observe").description("Manage coding agent proficiency observers");
|
|
749
|
+
observe
|
|
750
|
+
.command("status")
|
|
751
|
+
.description("Show observer status and accumulated data")
|
|
752
|
+
.action(async () => {
|
|
753
|
+
try {
|
|
754
|
+
const agents = detectAgents();
|
|
755
|
+
const status = getObservationStatus();
|
|
756
|
+
const detected = agents.filter((a) => a.installed);
|
|
757
|
+
console.log("\n🔍 Coding Agent Observers\n");
|
|
758
|
+
console.log(" Agents:");
|
|
759
|
+
for (const agent of agents) {
|
|
760
|
+
if (!agent.installed) {
|
|
761
|
+
console.log(` ⬚ ${agent.name} (not installed)`);
|
|
762
|
+
}
|
|
763
|
+
else if (agent.hookInstalled) {
|
|
764
|
+
console.log(` ✅ ${agent.name} (observer active)`);
|
|
765
|
+
}
|
|
766
|
+
else {
|
|
767
|
+
console.log(` ⚠️ ${agent.name} (detected, no observer)`);
|
|
768
|
+
}
|
|
769
|
+
}
|
|
770
|
+
console.log("\n Accumulated Data:");
|
|
771
|
+
console.log(` Sessions observed: ${status.totalSessions}`);
|
|
772
|
+
console.log(` Total tokens: ${status.totalTokens.toLocaleString()}`);
|
|
773
|
+
console.log(` Agents seen: ${status.agents.join(", ") || "none yet"}`);
|
|
774
|
+
console.log(` Last submitted: ${status.lastSubmitted || "never"}`);
|
|
775
|
+
if (status.topTools.length > 0) {
|
|
776
|
+
console.log("\n Top Tools:");
|
|
777
|
+
for (const { tool, count } of status.topTools.slice(0, 5)) {
|
|
778
|
+
console.log(` ${tool}: ${count} uses`);
|
|
779
|
+
}
|
|
780
|
+
}
|
|
781
|
+
console.log(`\n Data file: ~/.config/jobarbiter/observer/observations.json\n`);
|
|
782
|
+
output({
|
|
783
|
+
detectedAgents: agents.map((a) => ({
|
|
784
|
+
id: a.id,
|
|
785
|
+
name: a.name,
|
|
786
|
+
installed: a.installed,
|
|
787
|
+
observerActive: a.hookInstalled,
|
|
788
|
+
})),
|
|
789
|
+
...status,
|
|
790
|
+
});
|
|
791
|
+
}
|
|
792
|
+
catch (e) {
|
|
793
|
+
handleError(e);
|
|
794
|
+
}
|
|
795
|
+
});
|
|
796
|
+
observe
|
|
797
|
+
.command("install")
|
|
798
|
+
.description("Install observers for detected coding agents")
|
|
799
|
+
.option("--agent <id>", "Install for specific agent (claude-code, cursor, opencode, codex, gemini)")
|
|
800
|
+
.option("--all", "Install for all detected agents")
|
|
801
|
+
.action(async (opts) => {
|
|
802
|
+
try {
|
|
803
|
+
const agents = detectAgents();
|
|
804
|
+
const detected = agents.filter((a) => a.installed);
|
|
805
|
+
if (detected.length === 0) {
|
|
806
|
+
error("No coding agents detected on this system.");
|
|
807
|
+
console.log(" Supported: Claude Code, Cursor, OpenCode, Codex CLI, Gemini CLI");
|
|
808
|
+
process.exit(1);
|
|
809
|
+
}
|
|
810
|
+
let toInstall;
|
|
811
|
+
if (opts.agent) {
|
|
812
|
+
const agent = agents.find((a) => a.id === opts.agent);
|
|
813
|
+
if (!agent) {
|
|
814
|
+
error(`Unknown agent: ${opts.agent}. Available: ${agents.map((a) => a.id).join(", ")}`);
|
|
815
|
+
process.exit(1);
|
|
816
|
+
}
|
|
817
|
+
if (!agent.installed) {
|
|
818
|
+
error(`${agent.name} is not installed on this system.`);
|
|
819
|
+
process.exit(1);
|
|
820
|
+
}
|
|
821
|
+
toInstall = [opts.agent];
|
|
822
|
+
}
|
|
823
|
+
else {
|
|
824
|
+
toInstall = detected.map((a) => a.id);
|
|
825
|
+
}
|
|
826
|
+
const result = installObservers(toInstall);
|
|
827
|
+
for (const name of result.installed) {
|
|
828
|
+
success(`Installed observer for ${name}`);
|
|
829
|
+
}
|
|
830
|
+
for (const name of result.skipped) {
|
|
831
|
+
console.log(` — ${name} (already installed)`);
|
|
832
|
+
}
|
|
833
|
+
for (const { agent: a, error: errMsg } of result.errors) {
|
|
834
|
+
error(`${a}: ${errMsg}`);
|
|
835
|
+
}
|
|
836
|
+
output(result);
|
|
837
|
+
}
|
|
838
|
+
catch (e) {
|
|
839
|
+
handleError(e);
|
|
840
|
+
}
|
|
841
|
+
});
|
|
842
|
+
observe
|
|
843
|
+
.command("remove")
|
|
844
|
+
.description("Remove observers from coding agents")
|
|
845
|
+
.option("--agent <id>", "Remove from specific agent")
|
|
846
|
+
.option("--all", "Remove from all agents")
|
|
847
|
+
.action(async (opts) => {
|
|
848
|
+
try {
|
|
849
|
+
const agents = detectAgents();
|
|
850
|
+
const withHooks = agents.filter((a) => a.hookInstalled);
|
|
851
|
+
if (withHooks.length === 0) {
|
|
852
|
+
console.log("No observers are currently installed.");
|
|
853
|
+
process.exit(0);
|
|
854
|
+
}
|
|
855
|
+
let toRemove;
|
|
856
|
+
if (opts.agent) {
|
|
857
|
+
toRemove = [opts.agent];
|
|
858
|
+
}
|
|
859
|
+
else {
|
|
860
|
+
toRemove = withHooks.map((a) => a.id);
|
|
861
|
+
}
|
|
862
|
+
const result = removeObservers(toRemove);
|
|
863
|
+
for (const name of result.removed) {
|
|
864
|
+
success(`Removed observer from ${name}`);
|
|
865
|
+
}
|
|
866
|
+
for (const name of result.notFound) {
|
|
867
|
+
console.log(` — ${name} (no observer found)`);
|
|
868
|
+
}
|
|
869
|
+
output(result);
|
|
870
|
+
}
|
|
871
|
+
catch (e) {
|
|
872
|
+
handleError(e);
|
|
873
|
+
}
|
|
874
|
+
});
|
|
875
|
+
observe
|
|
876
|
+
.command("review")
|
|
877
|
+
.description("Review accumulated observation data before submission")
|
|
878
|
+
.action(async () => {
|
|
879
|
+
try {
|
|
880
|
+
const status = getObservationStatus();
|
|
881
|
+
if (!status.hasData) {
|
|
882
|
+
console.log("\nNo observation data collected yet.");
|
|
883
|
+
console.log("Use your coding agents normally — data accumulates automatically.\n");
|
|
884
|
+
process.exit(0);
|
|
885
|
+
}
|
|
886
|
+
console.log("\n📊 Observation Data Review\n");
|
|
887
|
+
console.log(` Sessions: ${status.totalSessions}`);
|
|
888
|
+
console.log(` Tokens: ${status.totalTokens.toLocaleString()}`);
|
|
889
|
+
console.log(` Agents: ${status.agents.join(", ")}`);
|
|
890
|
+
if (status.topTools.length > 0) {
|
|
891
|
+
console.log("\n Tool Usage:");
|
|
892
|
+
for (const { tool, count } of status.topTools) {
|
|
893
|
+
const bar = "█".repeat(Math.min(count, 30));
|
|
894
|
+
console.log(` ${tool.padEnd(25)} ${bar} ${count}`);
|
|
895
|
+
}
|
|
896
|
+
}
|
|
897
|
+
console.log(`\n This data will be submitted as an attestation when you run:`);
|
|
898
|
+
console.log(` jobarbiter attest --agent observer --capabilities <auto-generated>\n`);
|
|
899
|
+
output(status);
|
|
900
|
+
}
|
|
901
|
+
catch (e) {
|
|
902
|
+
handleError(e);
|
|
903
|
+
}
|
|
904
|
+
});
|
|
905
|
+
// ============================================================
|
|
777
906
|
// Helpers
|
|
778
907
|
// ============================================================
|
|
779
908
|
function handleError(e) {
|
package/dist/lib/config.js
CHANGED
|
@@ -35,7 +35,7 @@ export function saveConfig(config) {
|
|
|
35
35
|
export function requireConfig() {
|
|
36
36
|
const config = loadConfig();
|
|
37
37
|
if (!config) {
|
|
38
|
-
console.error("Not configured. Run: jobarbiter
|
|
38
|
+
console.error("Not configured. Run: jobarbiter onboard");
|
|
39
39
|
console.error("Or set JOBARBITER_API_KEY environment variable.");
|
|
40
40
|
process.exit(1);
|
|
41
41
|
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* JobArbiter Observer — Hook installer for coding agent CLIs
|
|
3
|
+
*
|
|
4
|
+
* Detects installed coding agents, installs observation hooks that
|
|
5
|
+
* extract proficiency signals from session transcripts.
|
|
6
|
+
*/
|
|
7
|
+
export interface DetectedAgent {
|
|
8
|
+
id: string;
|
|
9
|
+
name: string;
|
|
10
|
+
configDir: string;
|
|
11
|
+
hookFormat: "claude" | "cursor" | "opencode" | "codex" | "gemini";
|
|
12
|
+
installed: boolean;
|
|
13
|
+
hookInstalled: boolean;
|
|
14
|
+
}
|
|
15
|
+
export declare function detectAgents(): DetectedAgent[];
|
|
16
|
+
/**
|
|
17
|
+
* Install observer hooks for the specified agents.
|
|
18
|
+
* Returns a summary of what was installed.
|
|
19
|
+
*/
|
|
20
|
+
export declare function installObservers(agentIds: string[]): {
|
|
21
|
+
installed: string[];
|
|
22
|
+
skipped: string[];
|
|
23
|
+
errors: Array<{
|
|
24
|
+
agent: string;
|
|
25
|
+
error: string;
|
|
26
|
+
}>;
|
|
27
|
+
};
|
|
28
|
+
/**
|
|
29
|
+
* Remove observer hooks for the specified agents.
|
|
30
|
+
*/
|
|
31
|
+
export declare function removeObservers(agentIds: string[]): {
|
|
32
|
+
removed: string[];
|
|
33
|
+
notFound: string[];
|
|
34
|
+
};
|
|
35
|
+
/**
|
|
36
|
+
* Get observation status — what's been accumulated locally.
|
|
37
|
+
*/
|
|
38
|
+
export declare function getObservationStatus(): {
|
|
39
|
+
hasData: boolean;
|
|
40
|
+
totalSessions: number;
|
|
41
|
+
totalTokens: number;
|
|
42
|
+
topTools: Array<{
|
|
43
|
+
tool: string;
|
|
44
|
+
count: number;
|
|
45
|
+
}>;
|
|
46
|
+
agents: string[];
|
|
47
|
+
lastSubmitted: string | null;
|
|
48
|
+
};
|