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