jobarbiter 0.3.0 → 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/src/index.ts CHANGED
@@ -4,6 +4,9 @@ import { Command } from "commander";
4
4
  import { loadConfig, saveConfig, requireConfig, getConfigPath, type Config } from "./lib/config.js";
5
5
  import { api, apiUnauthenticated, ApiError } from "./lib/api.js";
6
6
  import { output, outputList, success, error, setJsonMode } from "./lib/output.js";
7
+ import { runOnboardWizard } from "./lib/onboard.js";
8
+ import { detectAgents, installObservers, removeObservers, getObservationStatus } from "./lib/observe.js";
9
+ import { getObservableTools, formatToolDisplay } from "./lib/detect-tools.js";
7
10
 
8
11
  const program = new Command();
9
12
 
@@ -18,57 +21,19 @@ program
18
21
  });
19
22
 
20
23
  // ============================================================
21
- // register
24
+ // onboard — Interactive setup wizard (primary entry point)
22
25
  // ============================================================
23
26
 
24
27
  program
25
- .command("register")
26
- .description("Register a new account and save API key")
27
- .requiredOption("--email <email>", "Email address")
28
- .requiredOption("--type <type>", "Account type: worker or employer")
28
+ .command("onboard")
29
+ .description("Interactive setup wizard the only command you need to get started")
30
+ .option("--force", "Start fresh even if already configured")
29
31
  .option("--base-url <url>", "API base URL", "https://jobarbiter-api-production.up.railway.app")
30
32
  .action(async (opts) => {
31
- try {
32
- const userType = opts.type === "seeker" ? "worker" : opts.type;
33
- if (!["worker", "employer"].includes(userType)) {
34
- error("Type must be 'worker' or 'employer'");
35
- process.exit(1);
36
- }
37
-
38
- // Step 1: Register and request verification code
39
- const registerData = await apiUnauthenticated(opts.baseUrl, "POST", "/v1/auth/register", {
40
- email: opts.email,
41
- userType,
42
- });
43
-
44
- success(`Verification code sent to ${opts.email} (expires in 15 minutes)`);
45
-
46
- // Step 2: Prompt for verification code
47
- const code = await promptForCode();
48
- if (!code) {
49
- error("Verification cancelled.");
50
- process.exit(1);
51
- }
52
-
53
- // Step 3: Verify the code
54
- const verifyData = await apiUnauthenticated(opts.baseUrl, "POST", "/v1/auth/verify", {
55
- email: opts.email,
56
- code: code.trim(),
57
- });
58
-
59
- // Step 4: Save config with API key
60
- saveConfig({
61
- apiKey: verifyData.apiKey as string,
62
- baseUrl: opts.baseUrl,
63
- userType: userType as "worker" | "employer",
64
- });
65
-
66
- success(`Email verified! API key saved to ${getConfigPath()}`);
67
- console.log(` Key: ${(verifyData.apiKey as string).slice(0, 20)}... (save this — shown only once)`);
68
- output({ id: verifyData.id, email: opts.email, userType });
69
- } catch (e) {
70
- handleError(e);
71
- }
33
+ await runOnboardWizard({
34
+ force: opts.force,
35
+ baseUrl: opts.baseUrl,
36
+ });
72
37
  });
73
38
 
74
39
  // ============================================================
@@ -76,7 +41,7 @@ program
76
41
  // ============================================================
77
42
 
78
43
  program
79
- .command("verify")
44
+ .command("verify-email")
80
45
  .description("Verify email with code (if registration was interrupted)")
81
46
  .requiredOption("--email <email>", "Email address")
82
47
  .option("--code <code>", "6-digit verification code")
@@ -746,7 +711,7 @@ program
746
711
  // verify
747
712
  // ============================================================
748
713
 
749
- const verify = program.command("verify").description("Identity verification");
714
+ const verify = program.command("identity").description("Identity verification (GitHub, LinkedIn, domain)");
750
715
 
751
716
  verify
752
717
  .command("linkedin <url>")
@@ -815,6 +780,187 @@ program
815
780
  }
816
781
  });
817
782
 
783
+ // ============================================================
784
+ // observe (manage coding agent observers)
785
+ // ============================================================
786
+
787
+ const observe = program.command("observe").description("Manage coding agent proficiency observers");
788
+
789
+ observe
790
+ .command("status")
791
+ .description("Show observer status and accumulated data")
792
+ .action(async () => {
793
+ try {
794
+ const agents = detectAgents();
795
+ const status = getObservationStatus();
796
+
797
+ const detected = agents.filter((a) => a.installed);
798
+
799
+ console.log("\n🔍 Coding Agent Observers\n");
800
+ console.log(" Agents:");
801
+ for (const agent of agents) {
802
+ if (!agent.installed) {
803
+ console.log(` ⬚ ${agent.name} (not installed)`);
804
+ } else if (agent.hookInstalled) {
805
+ console.log(` ✅ ${agent.name} (observer active)`);
806
+ } else {
807
+ console.log(` ⚠️ ${agent.name} (detected, no observer)`);
808
+ }
809
+ }
810
+
811
+ console.log("\n Accumulated Data:");
812
+ console.log(` Sessions observed: ${status.totalSessions}`);
813
+ console.log(` Total tokens: ${status.totalTokens.toLocaleString()}`);
814
+ console.log(` Agents seen: ${status.agents.join(", ") || "none yet"}`);
815
+ console.log(` Last submitted: ${status.lastSubmitted || "never"}`);
816
+
817
+ if (status.topTools.length > 0) {
818
+ console.log("\n Top Tools:");
819
+ for (const { tool, count } of status.topTools.slice(0, 5)) {
820
+ console.log(` ${tool}: ${count} uses`);
821
+ }
822
+ }
823
+
824
+ console.log(`\n Data file: ~/.config/jobarbiter/observer/observations.json\n`);
825
+
826
+ output({
827
+ detectedAgents: agents.map((a) => ({
828
+ id: a.id,
829
+ name: a.name,
830
+ installed: a.installed,
831
+ observerActive: a.hookInstalled,
832
+ })),
833
+ ...status,
834
+ });
835
+ } catch (e) {
836
+ handleError(e);
837
+ }
838
+ });
839
+
840
+ observe
841
+ .command("install")
842
+ .description("Install observers for detected coding agents")
843
+ .option("--agent <id>", "Install for specific agent (claude-code, cursor, opencode, codex, gemini)")
844
+ .option("--all", "Install for all detected agents")
845
+ .action(async (opts) => {
846
+ try {
847
+ const agents = detectAgents();
848
+ const detected = agents.filter((a) => a.installed);
849
+
850
+ if (detected.length === 0) {
851
+ error("No coding agents detected on this system.");
852
+ console.log(" Supported: Claude Code, Cursor, OpenCode, Codex CLI, Gemini CLI");
853
+ process.exit(1);
854
+ }
855
+
856
+ let toInstall: string[];
857
+
858
+ if (opts.agent) {
859
+ const agent = agents.find((a) => a.id === opts.agent);
860
+ if (!agent) {
861
+ error(`Unknown agent: ${opts.agent}. Available: ${agents.map((a) => a.id).join(", ")}`);
862
+ process.exit(1);
863
+ }
864
+ if (!agent.installed) {
865
+ error(`${agent.name} is not installed on this system.`);
866
+ process.exit(1);
867
+ }
868
+ toInstall = [opts.agent];
869
+ } else {
870
+ toInstall = detected.map((a) => a.id);
871
+ }
872
+
873
+ const result = installObservers(toInstall);
874
+
875
+ for (const name of result.installed) {
876
+ success(`Installed observer for ${name}`);
877
+ }
878
+ for (const name of result.skipped) {
879
+ console.log(` — ${name} (already installed)`);
880
+ }
881
+ for (const { agent: a, error: errMsg } of result.errors) {
882
+ error(`${a}: ${errMsg}`);
883
+ }
884
+
885
+ output(result);
886
+ } catch (e) {
887
+ handleError(e);
888
+ }
889
+ });
890
+
891
+ observe
892
+ .command("remove")
893
+ .description("Remove observers from coding agents")
894
+ .option("--agent <id>", "Remove from specific agent")
895
+ .option("--all", "Remove from all agents")
896
+ .action(async (opts) => {
897
+ try {
898
+ const agents = detectAgents();
899
+ const withHooks = agents.filter((a) => a.hookInstalled);
900
+
901
+ if (withHooks.length === 0) {
902
+ console.log("No observers are currently installed.");
903
+ process.exit(0);
904
+ }
905
+
906
+ let toRemove: string[];
907
+
908
+ if (opts.agent) {
909
+ toRemove = [opts.agent];
910
+ } else {
911
+ toRemove = withHooks.map((a) => a.id);
912
+ }
913
+
914
+ const result = removeObservers(toRemove);
915
+
916
+ for (const name of result.removed) {
917
+ success(`Removed observer from ${name}`);
918
+ }
919
+ for (const name of result.notFound) {
920
+ console.log(` — ${name} (no observer found)`);
921
+ }
922
+
923
+ output(result);
924
+ } catch (e) {
925
+ handleError(e);
926
+ }
927
+ });
928
+
929
+ observe
930
+ .command("review")
931
+ .description("Review accumulated observation data before submission")
932
+ .action(async () => {
933
+ try {
934
+ const status = getObservationStatus();
935
+
936
+ if (!status.hasData) {
937
+ console.log("\nNo observation data collected yet.");
938
+ console.log("Use your coding agents normally — data accumulates automatically.\n");
939
+ process.exit(0);
940
+ }
941
+
942
+ console.log("\n📊 Observation Data Review\n");
943
+ console.log(` Sessions: ${status.totalSessions}`);
944
+ console.log(` Tokens: ${status.totalTokens.toLocaleString()}`);
945
+ console.log(` Agents: ${status.agents.join(", ")}`);
946
+
947
+ if (status.topTools.length > 0) {
948
+ console.log("\n Tool Usage:");
949
+ for (const { tool, count } of status.topTools) {
950
+ const bar = "█".repeat(Math.min(count, 30));
951
+ console.log(` ${tool.padEnd(25)} ${bar} ${count}`);
952
+ }
953
+ }
954
+
955
+ console.log(`\n This data will be submitted as an attestation when you run:`);
956
+ console.log(` jobarbiter attest --agent observer --capabilities <auto-generated>\n`);
957
+
958
+ output(status);
959
+ } catch (e) {
960
+ handleError(e);
961
+ }
962
+ });
963
+
818
964
  // ============================================================
819
965
  // Helpers
820
966
  // ============================================================
package/src/lib/config.ts CHANGED
@@ -47,7 +47,7 @@ export function saveConfig(config: Config): void {
47
47
  export function requireConfig(): Config {
48
48
  const config = loadConfig();
49
49
  if (!config) {
50
- console.error("Not configured. Run: jobarbiter register --email YOUR_EMAIL --type seeker");
50
+ console.error("Not configured. Run: jobarbiter onboard");
51
51
  console.error("Or set JOBARBITER_API_KEY environment variable.");
52
52
  process.exit(1);
53
53
  }