jobarbiter 0.3.0
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.d.ts +2 -0
- package/dist/index.js +816 -0
- package/dist/lib/api.d.ts +8 -0
- package/dist/lib/api.js +40 -0
- package/dist/lib/config.d.ts +9 -0
- package/dist/lib/config.js +43 -0
- package/dist/lib/output.d.ts +8 -0
- package/dist/lib/output.js +85 -0
- package/package.json +24 -0
- package/src/index.ts +852 -0
- package/src/lib/api.ts +62 -0
- package/src/lib/config.ts +55 -0
- package/src/lib/output.ts +86 -0
- package/tsconfig.json +14 -0
package/src/lib/api.ts
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import type { Config } from "./config.js";
|
|
2
|
+
|
|
3
|
+
export class ApiError extends Error {
|
|
4
|
+
constructor(
|
|
5
|
+
public status: number,
|
|
6
|
+
public body: Record<string, unknown>,
|
|
7
|
+
) {
|
|
8
|
+
super(body.error as string || `HTTP ${status}`);
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export async function api(
|
|
13
|
+
config: Config,
|
|
14
|
+
method: string,
|
|
15
|
+
path: string,
|
|
16
|
+
body?: Record<string, unknown>,
|
|
17
|
+
): Promise<Record<string, unknown>> {
|
|
18
|
+
const url = `${config.baseUrl}${path}`;
|
|
19
|
+
|
|
20
|
+
const headers: Record<string, string> = {
|
|
21
|
+
"Authorization": `Bearer ${config.apiKey}`,
|
|
22
|
+
"Content-Type": "application/json",
|
|
23
|
+
"User-Agent": "jobarbiter-cli/0.3.0",
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
const response = await fetch(url, {
|
|
27
|
+
method,
|
|
28
|
+
headers,
|
|
29
|
+
body: body ? JSON.stringify(body) : undefined,
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
const data = await response.json() as Record<string, unknown>;
|
|
33
|
+
|
|
34
|
+
if (!response.ok) {
|
|
35
|
+
throw new ApiError(response.status, data);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
return data;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export async function apiUnauthenticated(
|
|
42
|
+
baseUrl: string,
|
|
43
|
+
method: string,
|
|
44
|
+
path: string,
|
|
45
|
+
body?: Record<string, unknown>,
|
|
46
|
+
): Promise<Record<string, unknown>> {
|
|
47
|
+
const url = `${baseUrl}${path}`;
|
|
48
|
+
|
|
49
|
+
const response = await fetch(url, {
|
|
50
|
+
method,
|
|
51
|
+
headers: { "Content-Type": "application/json" },
|
|
52
|
+
body: body ? JSON.stringify(body) : undefined,
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
const data = await response.json() as Record<string, unknown>;
|
|
56
|
+
|
|
57
|
+
if (!response.ok) {
|
|
58
|
+
throw new ApiError(response.status, data);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
return data;
|
|
62
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { readFileSync, writeFileSync, mkdirSync, existsSync } from "node:fs";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import { homedir } from "node:os";
|
|
4
|
+
|
|
5
|
+
export interface Config {
|
|
6
|
+
apiKey: string;
|
|
7
|
+
baseUrl: string;
|
|
8
|
+
userType: "worker" | "employer" | "seeker" | "poster";
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
const CONFIG_DIR = join(homedir(), ".config", "jobarbiter");
|
|
12
|
+
const CONFIG_FILE = join(CONFIG_DIR, "config.json");
|
|
13
|
+
|
|
14
|
+
export function getConfigPath(): string {
|
|
15
|
+
return CONFIG_FILE;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export function loadConfig(): Config | null {
|
|
19
|
+
// Environment variables override file config
|
|
20
|
+
const envKey = process.env.JOBARBITER_API_KEY;
|
|
21
|
+
const envUrl = process.env.JOBARBITER_BASE_URL;
|
|
22
|
+
const envType = process.env.JOBARBITER_USER_TYPE as "seeker" | "poster" | undefined;
|
|
23
|
+
|
|
24
|
+
if (envKey) {
|
|
25
|
+
return {
|
|
26
|
+
apiKey: envKey,
|
|
27
|
+
baseUrl: envUrl || "https://jobarbiter-api-production.up.railway.app",
|
|
28
|
+
userType: envType || "worker",
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
if (!existsSync(CONFIG_FILE)) return null;
|
|
33
|
+
|
|
34
|
+
try {
|
|
35
|
+
const raw = readFileSync(CONFIG_FILE, "utf-8");
|
|
36
|
+
return JSON.parse(raw) as Config;
|
|
37
|
+
} catch {
|
|
38
|
+
return null;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export function saveConfig(config: Config): void {
|
|
43
|
+
mkdirSync(CONFIG_DIR, { recursive: true });
|
|
44
|
+
writeFileSync(CONFIG_FILE, JSON.stringify(config, null, 2) + "\n");
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export function requireConfig(): Config {
|
|
48
|
+
const config = loadConfig();
|
|
49
|
+
if (!config) {
|
|
50
|
+
console.error("Not configured. Run: jobarbiter register --email YOUR_EMAIL --type seeker");
|
|
51
|
+
console.error("Or set JOBARBITER_API_KEY environment variable.");
|
|
52
|
+
process.exit(1);
|
|
53
|
+
}
|
|
54
|
+
return config;
|
|
55
|
+
}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Output formatting — JSON for agents, readable for humans
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
let jsonMode = false;
|
|
6
|
+
|
|
7
|
+
export function setJsonMode(enabled: boolean): void {
|
|
8
|
+
jsonMode = enabled;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function output(data: Record<string, unknown>): void {
|
|
12
|
+
if (jsonMode) {
|
|
13
|
+
console.log(JSON.stringify(data, null, 2));
|
|
14
|
+
} else {
|
|
15
|
+
printReadable(data);
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function outputList(items: Array<Record<string, unknown>>, title?: string): void {
|
|
20
|
+
if (jsonMode) {
|
|
21
|
+
console.log(JSON.stringify(items, null, 2));
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
if (title) console.log(`\n${title} (${items.length})\n${"─".repeat(40)}`);
|
|
26
|
+
|
|
27
|
+
if (items.length === 0) {
|
|
28
|
+
console.log(" (none)");
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
for (const item of items) {
|
|
33
|
+
printReadable(item);
|
|
34
|
+
console.log("");
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export function success(message: string): void {
|
|
39
|
+
if (jsonMode) {
|
|
40
|
+
console.log(JSON.stringify({ status: "ok", message }));
|
|
41
|
+
} else {
|
|
42
|
+
console.log(`✓ ${message}`);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export function error(message: string): void {
|
|
47
|
+
if (jsonMode) {
|
|
48
|
+
console.error(JSON.stringify({ status: "error", error: message }));
|
|
49
|
+
} else {
|
|
50
|
+
console.error(`✗ ${message}`);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function printReadable(data: Record<string, unknown>, indent = 0): void {
|
|
55
|
+
const pad = " ".repeat(indent);
|
|
56
|
+
for (const [key, value] of Object.entries(data)) {
|
|
57
|
+
if (key === "ai_disclosure") continue; // skip noise in human output
|
|
58
|
+
if (value === null || value === undefined) continue;
|
|
59
|
+
|
|
60
|
+
if (typeof value === "object" && !Array.isArray(value)) {
|
|
61
|
+
console.log(`${pad}${formatKey(key)}:`);
|
|
62
|
+
printReadable(value as Record<string, unknown>, indent + 1);
|
|
63
|
+
} else if (Array.isArray(value)) {
|
|
64
|
+
if (value.length === 0) continue;
|
|
65
|
+
if (typeof value[0] === "string") {
|
|
66
|
+
console.log(`${pad}${formatKey(key)}: ${value.join(", ")}`);
|
|
67
|
+
} else {
|
|
68
|
+
console.log(`${pad}${formatKey(key)}:`);
|
|
69
|
+
for (const item of value) {
|
|
70
|
+
if (typeof item === "object") {
|
|
71
|
+
printReadable(item as Record<string, unknown>, indent + 1);
|
|
72
|
+
console.log("");
|
|
73
|
+
} else {
|
|
74
|
+
console.log(`${pad} - ${item}`);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
} else {
|
|
79
|
+
console.log(`${pad}${formatKey(key)}: ${value}`);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function formatKey(key: string): string {
|
|
85
|
+
return key.replace(/([A-Z])/g, " $1").replace(/^./, (s) => s.toUpperCase()).trim();
|
|
86
|
+
}
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2022",
|
|
4
|
+
"module": "ESNext",
|
|
5
|
+
"moduleResolution": "bundler",
|
|
6
|
+
"outDir": "./dist",
|
|
7
|
+
"rootDir": "./src",
|
|
8
|
+
"strict": true,
|
|
9
|
+
"esModuleInterop": true,
|
|
10
|
+
"declaration": true,
|
|
11
|
+
"skipLibCheck": true
|
|
12
|
+
},
|
|
13
|
+
"include": ["src/**/*"]
|
|
14
|
+
}
|