clawrk 0.0.1 → 0.0.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/dist/index.js +216 -12
- package/package.json +5 -5
package/dist/index.js
CHANGED
|
@@ -3,12 +3,87 @@
|
|
|
3
3
|
// src/index.ts
|
|
4
4
|
import { Command } from "commander";
|
|
5
5
|
|
|
6
|
+
// src/auth.ts
|
|
7
|
+
import fs from "fs";
|
|
8
|
+
import { mkdir, writeFile } from "fs/promises";
|
|
9
|
+
|
|
10
|
+
// src/constants.ts
|
|
11
|
+
import os from "os";
|
|
12
|
+
import path from "path";
|
|
13
|
+
var USER_DATA_DIR = path.join(os.homedir(), ".clawrk");
|
|
14
|
+
var CLI_CREDENTIALS_PATH = path.join(USER_DATA_DIR, "credentials.json");
|
|
15
|
+
var CLI_LOGIN_PATH = "/cli-login";
|
|
16
|
+
var CLI_LOGIN_EXCHANGE_PATH = "/api/cli-login/consume";
|
|
17
|
+
var CLI_WHOAMI_PATH = "/api/whoami";
|
|
18
|
+
var DEV_BASE_URL = "http://localhost:3000";
|
|
19
|
+
var PROD_BASE_URL = "https://clawrk.sh";
|
|
20
|
+
function getBaseUrl() {
|
|
21
|
+
if (process.env.CLAWRK_API_URL) {
|
|
22
|
+
return process.env.CLAWRK_API_URL.trim();
|
|
23
|
+
}
|
|
24
|
+
if (process.env.__CLAWRK_DEV === "1") {
|
|
25
|
+
return DEV_BASE_URL;
|
|
26
|
+
}
|
|
27
|
+
return process.env.API_URL || PROD_BASE_URL;
|
|
28
|
+
}
|
|
29
|
+
function getCliLoginUrl() {
|
|
30
|
+
return `${getBaseUrl()}${CLI_LOGIN_PATH}`;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// src/auth.ts
|
|
34
|
+
function getEnvApiKey() {
|
|
35
|
+
const value = process.env.CLAWRK_API_KEY;
|
|
36
|
+
return value && value.trim() ? value.trim() : void 0;
|
|
37
|
+
}
|
|
38
|
+
function loadStoredApiKey() {
|
|
39
|
+
try {
|
|
40
|
+
if (!fs.existsSync(CLI_CREDENTIALS_PATH)) return void 0;
|
|
41
|
+
const raw = fs.readFileSync(CLI_CREDENTIALS_PATH, "utf8");
|
|
42
|
+
const parsed = JSON.parse(raw);
|
|
43
|
+
if (parsed && typeof parsed.apiKey === "string" && parsed.apiKey.trim()) {
|
|
44
|
+
return parsed.apiKey.trim();
|
|
45
|
+
}
|
|
46
|
+
return void 0;
|
|
47
|
+
} catch {
|
|
48
|
+
return void 0;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
async function saveApiKey(apiKey) {
|
|
52
|
+
await mkdir(USER_DATA_DIR, { recursive: true });
|
|
53
|
+
await writeFile(
|
|
54
|
+
CLI_CREDENTIALS_PATH,
|
|
55
|
+
JSON.stringify({ apiKey, createdAt: (/* @__PURE__ */ new Date()).toISOString() }, null, 2),
|
|
56
|
+
{ mode: 384 }
|
|
57
|
+
);
|
|
58
|
+
}
|
|
59
|
+
function clearStoredApiKey() {
|
|
60
|
+
try {
|
|
61
|
+
if (fs.existsSync(CLI_CREDENTIALS_PATH)) {
|
|
62
|
+
fs.unlinkSync(CLI_CREDENTIALS_PATH);
|
|
63
|
+
}
|
|
64
|
+
} catch {
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
function getEffectiveApiKey() {
|
|
68
|
+
return getEnvApiKey() ?? loadStoredApiKey();
|
|
69
|
+
}
|
|
70
|
+
function isLoggedIn() {
|
|
71
|
+
return Boolean(getEffectiveApiKey());
|
|
72
|
+
}
|
|
73
|
+
|
|
6
74
|
// src/api.ts
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
const
|
|
75
|
+
async function api(path2, opts) {
|
|
76
|
+
const baseUrl = getBaseUrl();
|
|
77
|
+
const apiKey = getEffectiveApiKey();
|
|
78
|
+
const headers = {
|
|
79
|
+
"Content-Type": "application/json"
|
|
80
|
+
};
|
|
81
|
+
if (apiKey) {
|
|
82
|
+
headers["Authorization"] = `Bearer ${apiKey}`;
|
|
83
|
+
}
|
|
84
|
+
const res = await fetch(`${baseUrl}${path2}`, {
|
|
10
85
|
method: opts?.method ?? "GET",
|
|
11
|
-
headers
|
|
86
|
+
headers,
|
|
12
87
|
...opts?.body ? { body: JSON.stringify(opts.body) } : {}
|
|
13
88
|
});
|
|
14
89
|
if (!res.ok) {
|
|
@@ -20,10 +95,10 @@ async function api(path, opts) {
|
|
|
20
95
|
|
|
21
96
|
// src/commands/create.ts
|
|
22
97
|
function registerCreate(program2) {
|
|
23
|
-
program2.command("create").description("Create a new job from a prompt").argument("<prompt>", "The job prompt").
|
|
98
|
+
program2.command("create").description("Create a new job from a prompt").argument("<prompt>", "The job prompt").action(async (prompt2) => {
|
|
24
99
|
const job = await api("/api/jobs", {
|
|
25
100
|
method: "POST",
|
|
26
|
-
body: { prompt
|
|
101
|
+
body: { prompt: prompt2 }
|
|
27
102
|
});
|
|
28
103
|
console.log("Job created:");
|
|
29
104
|
console.log(JSON.stringify(job, null, 2));
|
|
@@ -32,11 +107,9 @@ function registerCreate(program2) {
|
|
|
32
107
|
|
|
33
108
|
// src/commands/list.ts
|
|
34
109
|
function registerList(program2) {
|
|
35
|
-
program2.command("list").description("List jobs").option("--status <status>", "Filter by status").
|
|
110
|
+
program2.command("list").description("List jobs").option("--status <status>", "Filter by status").action(async (opts) => {
|
|
36
111
|
const params = new URLSearchParams();
|
|
37
112
|
if (opts.status) params.set("status", opts.status);
|
|
38
|
-
if (opts.sender) params.set("sender", opts.sender);
|
|
39
|
-
if (opts.receiver) params.set("receiver", opts.receiver);
|
|
40
113
|
const qs = params.toString();
|
|
41
114
|
const jobs = await api(`/api/jobs${qs ? `?${qs}` : ""}`);
|
|
42
115
|
if (jobs.length === 0) {
|
|
@@ -51,10 +124,9 @@ function registerList(program2) {
|
|
|
51
124
|
|
|
52
125
|
// src/commands/accept.ts
|
|
53
126
|
function registerAccept(program2) {
|
|
54
|
-
program2.command("accept").description("Accept a job").argument("<id>", "Job ID").
|
|
127
|
+
program2.command("accept").description("Accept a job").argument("<id>", "Job ID").action(async (id) => {
|
|
55
128
|
const job = await api(`/api/jobs/${id}/accept`, {
|
|
56
|
-
method: "POST"
|
|
57
|
-
body: { receiver: opts.receiver }
|
|
129
|
+
method: "POST"
|
|
58
130
|
});
|
|
59
131
|
console.log("Job accepted:");
|
|
60
132
|
console.log(JSON.stringify(job, null, 2));
|
|
@@ -165,9 +237,141 @@ function registerVerify(program2) {
|
|
|
165
237
|
});
|
|
166
238
|
}
|
|
167
239
|
|
|
240
|
+
// src/commands/login.ts
|
|
241
|
+
import { createInterface } from "readline";
|
|
242
|
+
|
|
243
|
+
// src/login-flow.ts
|
|
244
|
+
import { exec } from "child_process";
|
|
245
|
+
import { promisify } from "util";
|
|
246
|
+
var execAsync = promisify(exec);
|
|
247
|
+
async function openLoginBrowser(loginUrl) {
|
|
248
|
+
const platform = process.platform;
|
|
249
|
+
const command = platform === "win32" ? `cmd /c start "" "${loginUrl}"` : platform === "darwin" ? `open "${loginUrl}"` : `xdg-open "${loginUrl}"`;
|
|
250
|
+
await execAsync(command);
|
|
251
|
+
}
|
|
252
|
+
async function exchangeLoginCode(baseUrl, code) {
|
|
253
|
+
const exchangeRes = await fetch(`${baseUrl}${CLI_LOGIN_EXCHANGE_PATH}`, {
|
|
254
|
+
method: "POST",
|
|
255
|
+
headers: { "Content-Type": "application/json" },
|
|
256
|
+
body: JSON.stringify({ code })
|
|
257
|
+
});
|
|
258
|
+
const exchangeBody = await exchangeRes.json().catch(() => ({}));
|
|
259
|
+
if (!exchangeRes.ok) {
|
|
260
|
+
throw new Error(
|
|
261
|
+
exchangeBody?.error || `Login failed: ${exchangeRes.status}`
|
|
262
|
+
);
|
|
263
|
+
}
|
|
264
|
+
const apiKey = exchangeBody?.apiKey;
|
|
265
|
+
if (!apiKey) {
|
|
266
|
+
throw new Error("Login failed: missing API key");
|
|
267
|
+
}
|
|
268
|
+
return apiKey;
|
|
269
|
+
}
|
|
270
|
+
async function verifyApiKey(baseUrl, apiKey) {
|
|
271
|
+
const verifyRes = await fetch(`${baseUrl}${CLI_WHOAMI_PATH}`, {
|
|
272
|
+
method: "POST",
|
|
273
|
+
headers: { Authorization: `Bearer ${apiKey}` }
|
|
274
|
+
});
|
|
275
|
+
const verifyBody = await verifyRes.json().catch(() => ({}));
|
|
276
|
+
if (!verifyRes.ok) {
|
|
277
|
+
throw new Error(
|
|
278
|
+
verifyBody?.error || `Verification failed: ${verifyRes.status}`
|
|
279
|
+
);
|
|
280
|
+
}
|
|
281
|
+
return verifyBody;
|
|
282
|
+
}
|
|
283
|
+
async function loginWithCode(baseUrl, code) {
|
|
284
|
+
const apiKey = await exchangeLoginCode(baseUrl, code);
|
|
285
|
+
await saveApiKey(apiKey);
|
|
286
|
+
return apiKey;
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
// src/commands/login.ts
|
|
290
|
+
function prompt(question) {
|
|
291
|
+
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
292
|
+
return new Promise((resolve) => {
|
|
293
|
+
rl.question(question, (answer) => {
|
|
294
|
+
rl.close();
|
|
295
|
+
resolve(answer.trim());
|
|
296
|
+
});
|
|
297
|
+
});
|
|
298
|
+
}
|
|
299
|
+
function registerLogin(program2) {
|
|
300
|
+
program2.command("login").description("Authenticate with clawrk").action(async () => {
|
|
301
|
+
if (isLoggedIn()) {
|
|
302
|
+
console.log("Already logged in. Use 'clawrk logout' first to re-authenticate.");
|
|
303
|
+
return;
|
|
304
|
+
}
|
|
305
|
+
const baseUrl = getBaseUrl();
|
|
306
|
+
const loginUrl = getCliLoginUrl();
|
|
307
|
+
console.log(`Opening browser to: ${loginUrl}`);
|
|
308
|
+
console.log("(If it doesn't open, visit the URL manually)\n");
|
|
309
|
+
try {
|
|
310
|
+
await openLoginBrowser(loginUrl);
|
|
311
|
+
} catch {
|
|
312
|
+
}
|
|
313
|
+
const code = await prompt("Paste the login code here: ");
|
|
314
|
+
if (!code) {
|
|
315
|
+
console.error("No code provided.");
|
|
316
|
+
process.exit(1);
|
|
317
|
+
}
|
|
318
|
+
try {
|
|
319
|
+
await loginWithCode(baseUrl, code);
|
|
320
|
+
console.log("Logged in successfully!");
|
|
321
|
+
} catch (err) {
|
|
322
|
+
console.error(
|
|
323
|
+
"Login failed:",
|
|
324
|
+
err instanceof Error ? err.message : err
|
|
325
|
+
);
|
|
326
|
+
process.exit(1);
|
|
327
|
+
}
|
|
328
|
+
});
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
// src/commands/logout.ts
|
|
332
|
+
function registerLogout(program2) {
|
|
333
|
+
program2.command("logout").description("Clear stored credentials").action(() => {
|
|
334
|
+
clearStoredApiKey();
|
|
335
|
+
if (getEnvApiKey()) {
|
|
336
|
+
console.log(
|
|
337
|
+
"Cleared local credentials. CLAWRK_API_KEY is set in your shell; unset it to fully log out."
|
|
338
|
+
);
|
|
339
|
+
} else {
|
|
340
|
+
console.log("Logged out and cleared local credentials.");
|
|
341
|
+
}
|
|
342
|
+
});
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
// src/commands/whoami.ts
|
|
346
|
+
function registerWhoami(program2) {
|
|
347
|
+
program2.command("whoami").description("Show current authenticated user").action(async () => {
|
|
348
|
+
const apiKey = getEffectiveApiKey();
|
|
349
|
+
if (!apiKey) {
|
|
350
|
+
console.log("Not logged in. Use 'clawrk login' to authenticate.");
|
|
351
|
+
return;
|
|
352
|
+
}
|
|
353
|
+
try {
|
|
354
|
+
const baseUrl = getBaseUrl();
|
|
355
|
+
const whoami = await verifyApiKey(baseUrl, apiKey);
|
|
356
|
+
console.log(`Logged in as user ${whoami.user?.id ?? "unknown"}`);
|
|
357
|
+
if (whoami.user?.credits !== void 0) {
|
|
358
|
+
console.log(`Credits: ${whoami.user.credits}`);
|
|
359
|
+
}
|
|
360
|
+
} catch (err) {
|
|
361
|
+
console.error(
|
|
362
|
+
"Login check failed:",
|
|
363
|
+
err instanceof Error ? err.message : err
|
|
364
|
+
);
|
|
365
|
+
}
|
|
366
|
+
});
|
|
367
|
+
}
|
|
368
|
+
|
|
168
369
|
// src/index.ts
|
|
169
370
|
var program = new Command();
|
|
170
371
|
program.name("clawrk").description("Agent Job Exchange CLI").version("0.0.1");
|
|
372
|
+
registerLogin(program);
|
|
373
|
+
registerLogout(program);
|
|
374
|
+
registerWhoami(program);
|
|
171
375
|
registerCreate(program);
|
|
172
376
|
registerList(program);
|
|
173
377
|
registerAccept(program);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "clawrk",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.2",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"bin": {
|
|
6
6
|
"clawrk": "./dist/index.js"
|
|
@@ -10,14 +10,14 @@
|
|
|
10
10
|
],
|
|
11
11
|
"dependencies": {
|
|
12
12
|
"commander": "^13",
|
|
13
|
-
"openai": "^4"
|
|
14
|
-
"@clawrk/openai": "0.0.1",
|
|
15
|
-
"@clawrk/skills": "0.0.1"
|
|
13
|
+
"openai": "^4"
|
|
16
14
|
},
|
|
17
15
|
"devDependencies": {
|
|
18
16
|
"tsup": "^8",
|
|
19
17
|
"tsx": "^4",
|
|
20
|
-
"typescript": "^5"
|
|
18
|
+
"typescript": "^5",
|
|
19
|
+
"@clawrk/openai": "0.0.1",
|
|
20
|
+
"@clawrk/skills": "0.0.1"
|
|
21
21
|
},
|
|
22
22
|
"scripts": {
|
|
23
23
|
"build": "tsup",
|