@clawrent/cli 0.1.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.js ADDED
@@ -0,0 +1,1164 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/index.ts
4
+ import { Command } from "commander";
5
+
6
+ // src/commands/auth.ts
7
+ import { createInterface } from "readline";
8
+
9
+ // src/config.ts
10
+ import { readFileSync, writeFileSync, mkdirSync, unlinkSync, existsSync } from "fs";
11
+ import { join } from "path";
12
+ import { homedir } from "os";
13
+ var DEFAULT_CONFIG = {
14
+ apiUrl: "http://localhost:3001",
15
+ wsUrl: "ws://localhost:3001"
16
+ };
17
+ function getConfigDir() {
18
+ return join(homedir(), ".clawrent");
19
+ }
20
+ function getConfigFilePath() {
21
+ return join(getConfigDir(), "config.json");
22
+ }
23
+ function getConfigPath() {
24
+ return getConfigFilePath();
25
+ }
26
+ function loadConfig() {
27
+ let fileConfig = {};
28
+ const configPath = getConfigFilePath();
29
+ if (existsSync(configPath)) {
30
+ try {
31
+ const raw = readFileSync(configPath, "utf-8");
32
+ fileConfig = JSON.parse(raw);
33
+ } catch {
34
+ }
35
+ }
36
+ const config = {
37
+ ...DEFAULT_CONFIG,
38
+ ...fileConfig
39
+ };
40
+ const envApiUrl = process.env["CLAWRENT_API_URL"];
41
+ const envWsUrl = process.env["CLAWRENT_WS_URL"];
42
+ const envToken = process.env["CLAWRENT_TOKEN"];
43
+ const envApiKey = process.env["CLAWRENT_API_KEY"];
44
+ const envUserId = process.env["CLAWRENT_USER_ID"];
45
+ if (envApiUrl) config.apiUrl = envApiUrl;
46
+ if (envWsUrl) config.wsUrl = envWsUrl;
47
+ if (envToken) config.token = envToken;
48
+ if (envApiKey) config.apiKey = envApiKey;
49
+ if (envUserId) config.userId = envUserId;
50
+ return config;
51
+ }
52
+ function saveConfig(partial) {
53
+ const configDir = getConfigDir();
54
+ if (!existsSync(configDir)) {
55
+ mkdirSync(configDir, { recursive: true });
56
+ }
57
+ let existing = {};
58
+ const configPath = getConfigFilePath();
59
+ if (existsSync(configPath)) {
60
+ try {
61
+ existing = JSON.parse(readFileSync(configPath, "utf-8"));
62
+ } catch {
63
+ }
64
+ }
65
+ const merged = { ...existing, ...partial };
66
+ writeFileSync(configPath, JSON.stringify(merged, null, 2), "utf-8");
67
+ }
68
+ function clearConfig() {
69
+ const configPath = getConfigFilePath();
70
+ if (existsSync(configPath)) {
71
+ unlinkSync(configPath);
72
+ }
73
+ }
74
+
75
+ // src/api-client.ts
76
+ var ApiClient = class {
77
+ config;
78
+ constructor(config) {
79
+ this.config = config;
80
+ }
81
+ get apiUrl() {
82
+ return this.config.apiUrl;
83
+ }
84
+ get wsUrl() {
85
+ return this.config.wsUrl;
86
+ }
87
+ get userId() {
88
+ return this.config.userId;
89
+ }
90
+ // --- Auth (no auth needed) ---
91
+ async login(email, password) {
92
+ return this.request("POST", "/api/auth/login", { email, password }, false);
93
+ }
94
+ async getMe() {
95
+ return this.request("GET", "/api/auth/me");
96
+ }
97
+ // --- Marketplace ---
98
+ async browse(query) {
99
+ const params = new URLSearchParams();
100
+ if (query?.search) params.set("search", query.search);
101
+ if (query?.category) params.set("category", query.category);
102
+ if (query?.page) params.set("page", String(query.page));
103
+ if (query?.limit) params.set("limit", String(query.limit));
104
+ const qs = params.toString();
105
+ return this.request("GET", `/api/marketplace/browse${qs ? `?${qs}` : ""}`);
106
+ }
107
+ async getAgent(slug) {
108
+ return this.request("GET", `/api/marketplace/agents/${encodeURIComponent(slug)}`);
109
+ }
110
+ // --- Sessions ---
111
+ async rent(options) {
112
+ return this.request("POST", "/api/sessions", {
113
+ agentId: options.agentId,
114
+ taskDescription: options.taskDescription,
115
+ grantedPermissions: options.grantedPermissions ?? {}
116
+ });
117
+ }
118
+ async getSessions(query) {
119
+ const params = new URLSearchParams();
120
+ if (query?.role) params.set("role", query.role);
121
+ if (query?.status) params.set("status", query.status);
122
+ if (query?.page) params.set("page", String(query.page));
123
+ if (query?.limit) params.set("limit", String(query.limit));
124
+ const qs = params.toString();
125
+ return this.request("GET", `/api/sessions${qs ? `?${qs}` : ""}`);
126
+ }
127
+ async getSession(sessionId) {
128
+ return this.request("GET", `/api/sessions/${encodeURIComponent(sessionId)}`);
129
+ }
130
+ async getSessionMessages(sessionId) {
131
+ return this.request("GET", `/api/sessions/${encodeURIComponent(sessionId)}/messages`);
132
+ }
133
+ async endSession(sessionId) {
134
+ return this.request("POST", `/api/sessions/${encodeURIComponent(sessionId)}/end`);
135
+ }
136
+ async approveSession(sessionId) {
137
+ return this.request("POST", `/api/sessions/${encodeURIComponent(sessionId)}/approve`);
138
+ }
139
+ // --- Billing ---
140
+ async getBalance() {
141
+ return this.request("GET", "/api/billing/wallet");
142
+ }
143
+ async topUp(amount) {
144
+ return this.request("POST", "/api/billing/wallet/topup", { amount });
145
+ }
146
+ // --- Provider: Agents ---
147
+ async registerAgent(data) {
148
+ return this.request("POST", "/api/agents", data);
149
+ }
150
+ async getMyAgents(query) {
151
+ const params = new URLSearchParams();
152
+ if (query?.page) params.set("page", String(query.page));
153
+ if (query?.limit) params.set("limit", String(query.limit));
154
+ if (query?.status) params.set("status", query.status);
155
+ const qs = params.toString();
156
+ return this.request("GET", `/api/agents/my${qs ? `?${qs}` : ""}`);
157
+ }
158
+ async publishAgent(agentId) {
159
+ return this.request("POST", `/api/agents/${encodeURIComponent(agentId)}/publish`);
160
+ }
161
+ async activateAgent(agentId) {
162
+ return this.request("POST", `/api/agents/${encodeURIComponent(agentId)}/activate`);
163
+ }
164
+ async setOnlineStatus(agentId, onlineStatus) {
165
+ return this.request("PATCH", `/api/agents/${encodeURIComponent(agentId)}/status`, { onlineStatus });
166
+ }
167
+ async generateAgentToken(agentId) {
168
+ return this.request("POST", `/api/agents/${encodeURIComponent(agentId)}/token`);
169
+ }
170
+ async revokeAgentToken(agentId) {
171
+ return this.request("DELETE", `/api/agents/${encodeURIComponent(agentId)}/token`);
172
+ }
173
+ // --- Orders ---
174
+ async createOrder(data) {
175
+ return this.request("POST", "/api/orders", data);
176
+ }
177
+ async getOrders(query) {
178
+ const params = new URLSearchParams();
179
+ if (query?.status) params.set("status", query.status);
180
+ if (query?.page) params.set("page", String(query.page));
181
+ if (query?.limit) params.set("limit", String(query.limit));
182
+ const qs = params.toString();
183
+ return this.request("GET", `/api/orders${qs ? `?${qs}` : ""}`);
184
+ }
185
+ async getOrder(orderId) {
186
+ return this.request("GET", `/api/orders/${encodeURIComponent(orderId)}`);
187
+ }
188
+ async cancelOrder(orderId) {
189
+ return this.request("POST", `/api/orders/${encodeURIComponent(orderId)}/cancel`);
190
+ }
191
+ // --- Cart ---
192
+ async getCart() {
193
+ return this.request("GET", "/api/cart");
194
+ }
195
+ async addToCart(data) {
196
+ return this.request("POST", "/api/cart", data);
197
+ }
198
+ async updateCartItem(itemId, data) {
199
+ return this.request("PATCH", `/api/cart/${encodeURIComponent(itemId)}`, data);
200
+ }
201
+ async removeFromCart(itemId) {
202
+ return this.request("DELETE", `/api/cart/${encodeURIComponent(itemId)}`);
203
+ }
204
+ async clearCart() {
205
+ return this.request("DELETE", "/api/cart");
206
+ }
207
+ // --- Favorites ---
208
+ async listFavorites(query) {
209
+ const params = new URLSearchParams();
210
+ if (query?.page) params.set("page", String(query.page));
211
+ if (query?.limit) params.set("limit", String(query.limit));
212
+ const qs = params.toString();
213
+ return this.request("GET", `/api/favorites${qs ? `?${qs}` : ""}`);
214
+ }
215
+ async addFavorite(agentId) {
216
+ return this.request("POST", `/api/favorites/${encodeURIComponent(agentId)}`);
217
+ }
218
+ async removeFavorite(agentId) {
219
+ return this.request("DELETE", `/api/favorites/${encodeURIComponent(agentId)}`);
220
+ }
221
+ // --- Health ---
222
+ async health() {
223
+ return this.request("GET", "/api/health", void 0, false);
224
+ }
225
+ // --- Private ---
226
+ async request(method, path, body, requireAuth = true) {
227
+ const url = `${this.config.apiUrl}${path}`;
228
+ const headers = {};
229
+ if (body !== void 0) {
230
+ headers["Content-Type"] = "application/json";
231
+ }
232
+ if (requireAuth) {
233
+ if (this.config.token) {
234
+ headers["Authorization"] = `Bearer ${this.config.token}`;
235
+ } else if (this.config.apiKey) {
236
+ headers["x-api-key"] = this.config.apiKey;
237
+ } else {
238
+ throw new Error("Not authenticated. Run `clawrent auth login` first.");
239
+ }
240
+ }
241
+ const response = await fetch(url, {
242
+ method,
243
+ headers,
244
+ body: body ? JSON.stringify(body) : void 0
245
+ });
246
+ if (!response.ok) {
247
+ const errBody = await response.json().catch(() => ({ message: response.statusText }));
248
+ throw new Error(`API error ${response.status}: ${errBody["message"] ?? JSON.stringify(errBody)}`);
249
+ }
250
+ return response.json();
251
+ }
252
+ };
253
+
254
+ // src/output.ts
255
+ function printJson(data) {
256
+ process.stdout.write(JSON.stringify(data, null, 2) + "\n");
257
+ }
258
+ function printError(message) {
259
+ process.stderr.write(`Error: ${message}
260
+ `);
261
+ }
262
+ function printSuccess(message) {
263
+ process.stdout.write(`${message}
264
+ `);
265
+ }
266
+
267
+ // src/commands/auth.ts
268
+ function deriveWsUrl(apiUrl) {
269
+ return apiUrl.replace(/^http/, "ws");
270
+ }
271
+ async function promptInput(prompt, hidden = false) {
272
+ const rl = createInterface({ input: process.stdin, output: process.stderr });
273
+ return new Promise((resolve) => {
274
+ if (hidden) {
275
+ process.stderr.write(prompt);
276
+ const stdin = process.stdin;
277
+ const wasRaw = stdin.isRaw;
278
+ if (stdin.isTTY) stdin.setRawMode(true);
279
+ let input = "";
280
+ const onData = (ch) => {
281
+ const c = ch.toString("utf-8");
282
+ if (c === "\n" || c === "\r") {
283
+ stdin.removeListener("data", onData);
284
+ if (stdin.isTTY && wasRaw !== void 0) stdin.setRawMode(wasRaw);
285
+ process.stderr.write("\n");
286
+ rl.close();
287
+ resolve(input);
288
+ } else if (c === "") {
289
+ rl.close();
290
+ process.exit(1);
291
+ } else if (c === "\x7F" || c === "\b") {
292
+ input = input.slice(0, -1);
293
+ } else {
294
+ input += c;
295
+ }
296
+ };
297
+ stdin.on("data", onData);
298
+ } else {
299
+ rl.question(prompt, (answer) => {
300
+ rl.close();
301
+ resolve(answer);
302
+ });
303
+ }
304
+ });
305
+ }
306
+ function registerAuthCommand(program2) {
307
+ const auth = program2.command("auth").description("Authentication management");
308
+ auth.command("login").description("Login to ClawRent platform").option("-e, --email <email>", "Email address").option("-p, --password <password>", "Password").option("--api-url <url>", "Platform API URL").action(async (opts) => {
309
+ try {
310
+ const currentConfig = loadConfig();
311
+ const apiUrl = opts.apiUrl ?? currentConfig.apiUrl;
312
+ const wsUrl = deriveWsUrl(apiUrl);
313
+ const email = opts.email ?? await promptInput("Email: ");
314
+ const password = opts.password ?? await promptInput("Password: ", true);
315
+ if (!email || !password) {
316
+ printError("Email and password are required.");
317
+ process.exit(1);
318
+ }
319
+ const client = new ApiClient({ ...currentConfig, apiUrl, wsUrl });
320
+ const result = await client.login(email, password);
321
+ saveConfig({
322
+ apiUrl,
323
+ wsUrl,
324
+ token: result.token,
325
+ userId: result.user.id,
326
+ email: result.user.email,
327
+ name: result.user.name
328
+ });
329
+ printSuccess(`Logged in as ${result.user.name} (${result.user.email})`);
330
+ printSuccess(`Config saved to ${getConfigPath()}`);
331
+ } catch (err) {
332
+ printError(err instanceof Error ? err.message : String(err));
333
+ process.exit(1);
334
+ }
335
+ });
336
+ auth.command("logout").description("Logout and clear saved credentials").action(() => {
337
+ try {
338
+ clearConfig();
339
+ printSuccess("Logged out. Config cleared.");
340
+ } catch (err) {
341
+ printError(err instanceof Error ? err.message : String(err));
342
+ process.exit(1);
343
+ }
344
+ });
345
+ auth.command("whoami").description("Show current user info").action(async () => {
346
+ try {
347
+ const config = loadConfig();
348
+ const client = new ApiClient(config);
349
+ const me = await client.getMe();
350
+ printJson(me);
351
+ } catch (err) {
352
+ printError(err instanceof Error ? err.message : String(err));
353
+ process.exit(1);
354
+ }
355
+ });
356
+ }
357
+
358
+ // src/commands/browse.ts
359
+ function registerBrowseCommand(program2) {
360
+ program2.command("browse").description("Browse available agents on the marketplace").option("-s, --search <query>", "Search by name").option("-c, --category <category>", "Filter by category").option("-p, --page <number>", "Page number", "1").option("-l, --limit <number>", "Results per page", "20").action(async (opts) => {
361
+ try {
362
+ const client = new ApiClient(loadConfig());
363
+ const result = await client.browse({
364
+ search: opts.search,
365
+ category: opts.category,
366
+ page: parseInt(opts.page, 10),
367
+ limit: parseInt(opts.limit, 10)
368
+ });
369
+ printJson(result);
370
+ } catch (err) {
371
+ printError(err instanceof Error ? err.message : String(err));
372
+ process.exit(1);
373
+ }
374
+ });
375
+ }
376
+
377
+ // src/commands/agent.ts
378
+ function registerAgentCommand(program2) {
379
+ program2.command("agent <slug>").description("Get agent details by slug").action(async (slug) => {
380
+ try {
381
+ const client = new ApiClient(loadConfig());
382
+ const result = await client.getAgent(slug);
383
+ printJson(result);
384
+ } catch (err) {
385
+ printError(err instanceof Error ? err.message : String(err));
386
+ process.exit(1);
387
+ }
388
+ });
389
+ }
390
+
391
+ // src/commands/sessions.ts
392
+ function registerSessionsCommand(program2) {
393
+ program2.command("sessions").description("List your sessions").option("-r, --role <role>", "Filter by role (consumer/provider)").option("-s, --status <status>", "Filter by status").option("-p, --page <number>", "Page number", "1").option("-l, --limit <number>", "Results per page", "20").action(async (opts) => {
394
+ try {
395
+ const client = new ApiClient(loadConfig());
396
+ const result = await client.getSessions({
397
+ role: opts.role,
398
+ status: opts.status,
399
+ page: parseInt(opts.page, 10),
400
+ limit: parseInt(opts.limit, 10)
401
+ });
402
+ printJson(result);
403
+ } catch (err) {
404
+ printError(err instanceof Error ? err.message : String(err));
405
+ process.exit(1);
406
+ }
407
+ });
408
+ }
409
+
410
+ // src/commands/balance.ts
411
+ function registerBalanceCommand(program2) {
412
+ program2.command("balance").description("Check wallet balance").action(async () => {
413
+ try {
414
+ const client = new ApiClient(loadConfig());
415
+ const result = await client.getBalance();
416
+ printSuccess(`Balance: ${result.balance}`);
417
+ } catch (err) {
418
+ printError(err instanceof Error ? err.message : String(err));
419
+ process.exit(1);
420
+ }
421
+ });
422
+ }
423
+
424
+ // src/commands/topup.ts
425
+ function registerTopupCommand(program2) {
426
+ program2.command("topup <amount>").description("Top up wallet balance").action(async (amount) => {
427
+ try {
428
+ const client = new ApiClient(loadConfig());
429
+ const result = await client.topUp(amount);
430
+ printSuccess(`New balance: ${result.balance}`);
431
+ } catch (err) {
432
+ printError(err instanceof Error ? err.message : String(err));
433
+ process.exit(1);
434
+ }
435
+ });
436
+ }
437
+
438
+ // src/commands/health.ts
439
+ function registerHealthCommand(program2) {
440
+ program2.command("health").description("Check platform health").action(async () => {
441
+ try {
442
+ const client = new ApiClient(loadConfig());
443
+ const result = await client.health();
444
+ printJson(result);
445
+ } catch (err) {
446
+ printError(err instanceof Error ? err.message : String(err));
447
+ process.exit(1);
448
+ }
449
+ });
450
+ }
451
+
452
+ // src/commands/rent.ts
453
+ function registerRentCommand(program2) {
454
+ program2.command("rent").description("Rent an agent (create a session)").requiredOption("--agent-id <id>", "Agent ID to rent").requiredOption("--task <description>", "Task description").option("--permissions <json>", "Granted permissions as JSON string", "{}").action(async (opts) => {
455
+ try {
456
+ let grantedPermissions = {};
457
+ try {
458
+ grantedPermissions = JSON.parse(opts.permissions);
459
+ } catch {
460
+ printError("Invalid JSON for --permissions");
461
+ process.exit(1);
462
+ }
463
+ const client = new ApiClient(loadConfig());
464
+ const result = await client.rent({
465
+ agentId: opts.agentId,
466
+ taskDescription: opts.task,
467
+ grantedPermissions
468
+ });
469
+ printJson(result);
470
+ } catch (err) {
471
+ printError(err instanceof Error ? err.message : String(err));
472
+ process.exit(1);
473
+ }
474
+ });
475
+ }
476
+
477
+ // src/commands/end.ts
478
+ function registerEndCommand(program2) {
479
+ program2.command("end <sessionId>").description("End a session").action(async (sessionId) => {
480
+ try {
481
+ const client = new ApiClient(loadConfig());
482
+ const result = await client.endSession(sessionId);
483
+ printJson(result);
484
+ } catch (err) {
485
+ printError(err instanceof Error ? err.message : String(err));
486
+ process.exit(1);
487
+ }
488
+ });
489
+ }
490
+
491
+ // src/commands/send.ts
492
+ import WebSocket from "ws";
493
+ function registerSendCommand(program2) {
494
+ program2.command("send <sessionId>").description("Send a message to a session via WebSocket").requiredOption("--content <text>", "Message content").option("--type <messageType>", "Message type", "dialogue.message").option("--token <sessionToken>", "Session token (auto-fetched if not provided)").option("--wait <ms>", "Wait for response (milliseconds)").action(async (sessionId, opts) => {
495
+ try {
496
+ const config = loadConfig();
497
+ const client = new ApiClient(config);
498
+ let sessionToken = opts.token;
499
+ let role = "consumer";
500
+ if (!sessionToken) {
501
+ const session = await client.getSession(sessionId);
502
+ sessionToken = session["sessionToken"];
503
+ if (config.userId && session["providerUserId"] === config.userId) {
504
+ role = "provider";
505
+ }
506
+ }
507
+ if (!sessionToken) {
508
+ printError("Could not determine session token. Use --token flag.");
509
+ process.exit(1);
510
+ }
511
+ const wsUrl = `${config.wsUrl}/ws/session?sessionId=${sessionId}&token=${sessionToken}&role=${role}`;
512
+ await new Promise((resolve, reject) => {
513
+ const ws = new WebSocket(wsUrl);
514
+ let responded = false;
515
+ ws.on("open", () => {
516
+ const message = {
517
+ id: `msg_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 8)}`,
518
+ sessionId,
519
+ timestamp: Date.now(),
520
+ sender: { role, agentId: config.userId ?? "unknown" },
521
+ type: opts.type,
522
+ payload: { content: opts.content, dialogueType: "message" }
523
+ };
524
+ ws.send(JSON.stringify(message));
525
+ if (opts.wait) {
526
+ setTimeout(() => {
527
+ if (!responded) {
528
+ ws.close(1e3);
529
+ printSuccess("Message sent (no response within timeout).");
530
+ resolve();
531
+ }
532
+ }, parseInt(opts.wait, 10));
533
+ } else {
534
+ setTimeout(() => {
535
+ ws.close(1e3);
536
+ printSuccess("Message sent.");
537
+ resolve();
538
+ }, 500);
539
+ }
540
+ });
541
+ ws.on("message", (raw) => {
542
+ try {
543
+ const data = JSON.parse(raw.toString());
544
+ if (data.type !== "system.heartbeat_ack" && data.type !== "system.peer_connected") {
545
+ responded = true;
546
+ printJson(data);
547
+ if (opts.wait) {
548
+ ws.close(1e3);
549
+ resolve();
550
+ }
551
+ }
552
+ } catch {
553
+ }
554
+ });
555
+ ws.on("error", (err) => {
556
+ reject(err);
557
+ });
558
+ ws.on("close", () => {
559
+ if (!responded && opts.wait) {
560
+ resolve();
561
+ }
562
+ });
563
+ });
564
+ } catch (err) {
565
+ printError(err instanceof Error ? err.message : String(err));
566
+ process.exit(1);
567
+ }
568
+ });
569
+ }
570
+
571
+ // src/commands/provider/index.ts
572
+ function registerProviderCommands(program2) {
573
+ const provider = program2.command("provider").description("Provider operations");
574
+ const agent = provider.command("agent").description("Manage your agents");
575
+ agent.command("create").description("Register a new agent").requiredOption("--name <name>", "Agent name").requiredOption("--slug <slug>", "Agent slug (URL-friendly)").requiredOption("--description <desc>", "Agent description (10-500 chars)").option("--long-description <text>", "Detailed description").option("--pricing-model <model>", "Pricing model (per_token/per_session/per_minute)", "per_session").option("--price <amount>", "Price amount", "1.00").option("--currency <cur>", "Currency (CNY/USD)", "CNY").option("--hosting-type <type>", "Hosting type (self_hosted/platform_hosted)", "self_hosted").option("--approval-mode <mode>", "Approval mode (manual/auto)", "manual").option("--max-concurrent <n>", "Max concurrent sessions", "5").option("--capabilities <json>", "Capabilities as JSON array").action(async (opts) => {
576
+ try {
577
+ const data = {
578
+ name: opts.name,
579
+ slug: opts.slug,
580
+ description: opts.description,
581
+ pricingModel: opts.pricingModel,
582
+ priceAmount: opts.price,
583
+ currency: opts.currency,
584
+ hostingType: opts.hostingType,
585
+ approvalMode: opts.approvalMode,
586
+ maxConcurrentSessions: parseInt(opts.maxConcurrent, 10)
587
+ };
588
+ if (opts.longDescription) data["longDescription"] = opts.longDescription;
589
+ if (opts.capabilities) {
590
+ try {
591
+ data["capabilities"] = JSON.parse(opts.capabilities);
592
+ } catch {
593
+ printError("Invalid JSON for --capabilities");
594
+ process.exit(1);
595
+ }
596
+ }
597
+ const client = new ApiClient(loadConfig());
598
+ const result = await client.registerAgent(data);
599
+ printJson(result);
600
+ } catch (err) {
601
+ printError(err instanceof Error ? err.message : String(err));
602
+ process.exit(1);
603
+ }
604
+ });
605
+ agent.command("list").description("List your agents").option("-s, --status <status>", "Filter by status").option("-p, --page <number>", "Page number", "1").option("-l, --limit <number>", "Results per page", "20").action(async (opts) => {
606
+ try {
607
+ const client = new ApiClient(loadConfig());
608
+ const result = await client.getMyAgents({
609
+ status: opts.status,
610
+ page: parseInt(opts.page, 10),
611
+ limit: parseInt(opts.limit, 10)
612
+ });
613
+ printJson(result);
614
+ } catch (err) {
615
+ printError(err instanceof Error ? err.message : String(err));
616
+ process.exit(1);
617
+ }
618
+ });
619
+ agent.command("publish <agentId>").description("Publish an agent (draft -> pending_review)").action(async (agentId) => {
620
+ try {
621
+ const client = new ApiClient(loadConfig());
622
+ const result = await client.publishAgent(agentId);
623
+ printSuccess("Agent published successfully.");
624
+ printJson(result);
625
+ } catch (err) {
626
+ printError(err instanceof Error ? err.message : String(err));
627
+ process.exit(1);
628
+ }
629
+ });
630
+ agent.command("activate <agentId>").description("Activate an agent (pending_review -> active)").action(async (agentId) => {
631
+ try {
632
+ const client = new ApiClient(loadConfig());
633
+ const result = await client.activateAgent(agentId);
634
+ printSuccess("Agent activated successfully.");
635
+ printJson(result);
636
+ } catch (err) {
637
+ printError(err instanceof Error ? err.message : String(err));
638
+ process.exit(1);
639
+ }
640
+ });
641
+ agent.command("status <agentId>").description("Set agent online status").requiredOption("--status <status>", "Online status (online/offline/busy)").action(async (agentId, opts) => {
642
+ try {
643
+ const client = new ApiClient(loadConfig());
644
+ const result = await client.setOnlineStatus(agentId, opts.status);
645
+ printSuccess(`Agent status set to ${opts.status}.`);
646
+ printJson(result);
647
+ } catch (err) {
648
+ printError(err instanceof Error ? err.message : String(err));
649
+ process.exit(1);
650
+ }
651
+ });
652
+ provider.command("sessions").description("List provider sessions").option("-s, --status <status>", "Filter by status").option("-p, --page <number>", "Page number", "1").option("-l, --limit <number>", "Results per page", "20").action(async (opts) => {
653
+ try {
654
+ const client = new ApiClient(loadConfig());
655
+ const result = await client.getSessions({
656
+ role: "provider",
657
+ status: opts.status,
658
+ page: parseInt(opts.page, 10),
659
+ limit: parseInt(opts.limit, 10)
660
+ });
661
+ printJson(result);
662
+ } catch (err) {
663
+ printError(err instanceof Error ? err.message : String(err));
664
+ process.exit(1);
665
+ }
666
+ });
667
+ provider.command("approve <sessionId>").description("Approve a pending session").action(async (sessionId) => {
668
+ try {
669
+ const client = new ApiClient(loadConfig());
670
+ const result = await client.approveSession(sessionId);
671
+ printSuccess("Session approved.");
672
+ printJson(result);
673
+ } catch (err) {
674
+ printError(err instanceof Error ? err.message : String(err));
675
+ process.exit(1);
676
+ }
677
+ });
678
+ }
679
+
680
+ // src/serve/index.ts
681
+ import WebSocket3 from "ws";
682
+
683
+ // src/serve/stdio-bridge.ts
684
+ import { createInterface as createInterface2 } from "readline";
685
+ var StdioBridge = class {
686
+ handler = null;
687
+ rl = null;
688
+ /** Start reading from stdin */
689
+ start(handler) {
690
+ this.handler = handler;
691
+ this.rl = createInterface2({ input: process.stdin, crlfDelay: Infinity });
692
+ this.rl.on("line", (line) => {
693
+ const trimmed = line.trim();
694
+ if (!trimmed) return;
695
+ try {
696
+ const msg = JSON.parse(trimmed);
697
+ if (msg.jsonrpc !== "2.0") {
698
+ this.writeError("Invalid JSON-RPC: missing jsonrpc field");
699
+ return;
700
+ }
701
+ this.handler?.(msg);
702
+ } catch {
703
+ this.writeError(`Failed to parse JSON: ${trimmed.slice(0, 100)}`);
704
+ }
705
+ });
706
+ this.rl.on("close", () => {
707
+ });
708
+ }
709
+ /** Stop reading */
710
+ stop() {
711
+ this.rl?.close();
712
+ this.rl = null;
713
+ }
714
+ /** Write a JSON-RPC message to stdout */
715
+ write(msg) {
716
+ process.stdout.write(JSON.stringify(msg) + "\n");
717
+ }
718
+ /** Write a notification */
719
+ writeNotification(method, params) {
720
+ const msg = { jsonrpc: "2.0", method, params };
721
+ this.write(msg);
722
+ }
723
+ /** Write a request (expects response) */
724
+ writeRequest(method, id, params) {
725
+ const msg = { jsonrpc: "2.0", method, id, params };
726
+ this.write(msg);
727
+ }
728
+ /** Write a response */
729
+ writeResponse(id, result) {
730
+ const msg = { jsonrpc: "2.0", id, result };
731
+ this.write(msg);
732
+ }
733
+ /** Write an error notification to stdout */
734
+ writeError(message) {
735
+ this.writeNotification("error", { message });
736
+ }
737
+ };
738
+
739
+ // src/serve/session-manager.ts
740
+ import { EventEmitter } from "events";
741
+ import WebSocket2 from "ws";
742
+ var SessionManager = class extends EventEmitter {
743
+ sessions = /* @__PURE__ */ new Map();
744
+ wsUrl;
745
+ heartbeatInterval;
746
+ maxReconnectDelay;
747
+ agentId;
748
+ constructor(wsUrl, heartbeatInterval = 25e3, maxReconnectDelay = 3e4) {
749
+ super();
750
+ this.wsUrl = wsUrl;
751
+ this.heartbeatInterval = heartbeatInterval;
752
+ this.maxReconnectDelay = maxReconnectDelay;
753
+ }
754
+ /** Connect to a session via WebSocket as provider */
755
+ connect(sessionId, sessionToken) {
756
+ if (this.sessions.has(sessionId)) {
757
+ return;
758
+ }
759
+ const url = `${this.wsUrl}/ws/session?sessionId=${sessionId}&token=${sessionToken}&role=provider`;
760
+ const ws = new WebSocket2(url);
761
+ const conn = {
762
+ sessionId,
763
+ sessionToken,
764
+ ws,
765
+ heartbeatTimer: null,
766
+ reconnectAttempts: 0
767
+ };
768
+ this.sessions.set(sessionId, conn);
769
+ ws.on("open", () => {
770
+ conn.reconnectAttempts = 0;
771
+ conn.heartbeatTimer = setInterval(() => {
772
+ if (ws.readyState === WebSocket2.OPEN) {
773
+ ws.send(JSON.stringify({ type: "system.heartbeat", payload: {} }));
774
+ }
775
+ }, this.heartbeatInterval);
776
+ this.emit("session:connected", sessionId);
777
+ });
778
+ ws.on("message", (raw) => {
779
+ try {
780
+ const message = JSON.parse(raw.toString());
781
+ if (message["type"] === "system.heartbeat_ack") return;
782
+ this.emit("session:message", sessionId, message);
783
+ } catch {
784
+ }
785
+ });
786
+ ws.on("close", (code, reason) => {
787
+ this.clearHeartbeat(conn);
788
+ if (code !== 1e3) {
789
+ const delay = Math.min(
790
+ 1e3 * Math.pow(2, conn.reconnectAttempts),
791
+ this.maxReconnectDelay
792
+ );
793
+ conn.reconnectAttempts++;
794
+ this.emit("session:reconnecting", sessionId, delay);
795
+ setTimeout(() => {
796
+ this.sessions.delete(sessionId);
797
+ this.connect(sessionId, sessionToken);
798
+ }, delay);
799
+ } else {
800
+ this.sessions.delete(sessionId);
801
+ this.emit("session:disconnected", sessionId, reason.toString());
802
+ }
803
+ });
804
+ ws.on("error", (err) => {
805
+ this.emit("session:error", sessionId, err);
806
+ });
807
+ }
808
+ /** Send a message to a specific session, auto-wrapping with protocol envelope */
809
+ send(sessionId, message) {
810
+ const conn = this.sessions.get(sessionId);
811
+ if (!conn || conn.ws.readyState !== WebSocket2.OPEN) {
812
+ return false;
813
+ }
814
+ const envelope = {
815
+ id: message["id"] ?? `msg_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 8)}`,
816
+ sessionId: message["sessionId"] ?? sessionId,
817
+ timestamp: message["timestamp"] ?? Date.now(),
818
+ sender: message["sender"] ?? { role: "provider", agentId: this.agentId ?? "unknown" },
819
+ type: message["type"] ?? "result.success",
820
+ payload: message["payload"] ?? {}
821
+ };
822
+ conn.ws.send(JSON.stringify(envelope));
823
+ return true;
824
+ }
825
+ /** Disconnect a specific session */
826
+ disconnect(sessionId) {
827
+ const conn = this.sessions.get(sessionId);
828
+ if (!conn) return;
829
+ this.clearHeartbeat(conn);
830
+ if (conn.ws.readyState === WebSocket2.OPEN || conn.ws.readyState === WebSocket2.CONNECTING) {
831
+ conn.ws.close(1e3, "Provider disconnecting");
832
+ }
833
+ this.sessions.delete(sessionId);
834
+ }
835
+ /** Disconnect all sessions */
836
+ disconnectAll() {
837
+ for (const sessionId of this.sessions.keys()) {
838
+ this.disconnect(sessionId);
839
+ }
840
+ }
841
+ /** Get active session count */
842
+ get activeCount() {
843
+ return this.sessions.size;
844
+ }
845
+ /** Check if a session is connected */
846
+ isConnected(sessionId) {
847
+ const conn = this.sessions.get(sessionId);
848
+ return conn?.ws.readyState === WebSocket2.OPEN;
849
+ }
850
+ clearHeartbeat(conn) {
851
+ if (conn.heartbeatTimer) {
852
+ clearInterval(conn.heartbeatTimer);
853
+ conn.heartbeatTimer = null;
854
+ }
855
+ }
856
+ };
857
+
858
+ // src/serve/protocol.ts
859
+ var correlationCounter = 0;
860
+ function createCorrelationId() {
861
+ return `corr_${Date.now().toString(36)}_${(correlationCounter++).toString(36)}`;
862
+ }
863
+ function isResponse(msg) {
864
+ return "id" in msg && ("result" in msg || "error" in msg) && !("method" in msg);
865
+ }
866
+ function isRequest(msg) {
867
+ return "method" in msg && "id" in msg;
868
+ }
869
+
870
+ // src/serve/index.ts
871
+ var pendingInstructions = /* @__PURE__ */ new Map();
872
+ var knownPendingSessions = /* @__PURE__ */ new Set();
873
+ var activeSessions = /* @__PURE__ */ new Set();
874
+ function registerServeCommand(program2) {
875
+ program2.command("serve").description("Start serve daemon - bridge WebSocket sessions to stdin/stdout").requiredOption("--agent-id <id>", "Agent ID to serve").option("--agent-token <token>", "Agent token for /ws/agent control channel").option("--auto-approve", "Automatically approve incoming sessions", false).option("--poll-interval <ms>", "Polling interval for pending sessions (ms)", "5000").action(async (opts) => {
876
+ try {
877
+ await runDaemon(opts);
878
+ } catch (err) {
879
+ printError(err instanceof Error ? err.message : String(err));
880
+ process.exit(1);
881
+ }
882
+ });
883
+ }
884
+ async function runDaemon(opts) {
885
+ const config = loadConfig();
886
+ const client = new ApiClient(config);
887
+ const bridge = new StdioBridge();
888
+ const sessionManager = new SessionManager(config.wsUrl);
889
+ sessionManager.agentId = opts.agentId;
890
+ const pollInterval = parseInt(opts.pollInterval, 10);
891
+ let agentWs = null;
892
+ let agentHeartbeatTimer = null;
893
+ let agentName = opts.agentId;
894
+ try {
895
+ const agents = await client.getMyAgents();
896
+ const agent = agents.data.find((a) => a["id"] === opts.agentId);
897
+ if (agent) {
898
+ agentName = agent["name"] ?? opts.agentId;
899
+ if (!opts.agentToken) {
900
+ const status = agent["status"];
901
+ if (status === "draft") {
902
+ await client.publishAgent(opts.agentId);
903
+ await client.activateAgent(opts.agentId);
904
+ } else if (status === "pending_review") {
905
+ await client.activateAgent(opts.agentId);
906
+ }
907
+ await client.setOnlineStatus(opts.agentId, "online");
908
+ }
909
+ }
910
+ } catch {
911
+ }
912
+ bridge.writeNotification("ready", {
913
+ agentId: opts.agentId,
914
+ agentName
915
+ });
916
+ sessionManager.on("session:connected", (sessionId) => {
917
+ bridge.writeNotification("session.connected", { sessionId });
918
+ });
919
+ sessionManager.on("session:message", (sessionId, message) => {
920
+ const type = message["type"];
921
+ if (type?.startsWith("instruction.")) {
922
+ const corrId = createCorrelationId();
923
+ pendingInstructions.set(corrId, sessionId);
924
+ bridge.writeRequest("instruction", corrId, {
925
+ sessionId,
926
+ messageId: message["id"] ?? corrId,
927
+ type,
928
+ payload: message["payload"] ?? {},
929
+ sender: message["sender"]
930
+ });
931
+ } else if (type?.startsWith("dialogue.")) {
932
+ const payload = message["payload"] ?? {};
933
+ bridge.writeNotification("dialogue", {
934
+ sessionId,
935
+ content: payload["content"] ?? "",
936
+ dialogueType: payload["dialogueType"] ?? "message"
937
+ });
938
+ } else if (type?.startsWith("result.")) {
939
+ bridge.writeNotification("result", {
940
+ sessionId,
941
+ type,
942
+ payload: message["payload"] ?? {}
943
+ });
944
+ } else if (type === "system.session_ended" || type === "system.peer_disconnected") {
945
+ activeSessions.delete(sessionId);
946
+ bridge.writeNotification("session.ended", {
947
+ sessionId,
948
+ reason: type
949
+ });
950
+ } else if (type === "system.peer_connected") {
951
+ bridge.writeNotification("session.peer_connected", { sessionId });
952
+ } else if (type !== "system.heartbeat_ack") {
953
+ bridge.writeNotification("message", { sessionId, ...message });
954
+ }
955
+ });
956
+ sessionManager.on("session:disconnected", (sessionId, reason) => {
957
+ activeSessions.delete(sessionId);
958
+ bridge.writeNotification("session.disconnected", { sessionId, reason });
959
+ });
960
+ sessionManager.on("session:reconnecting", (sessionId, delay) => {
961
+ bridge.writeNotification("session.reconnecting", { sessionId, delay });
962
+ });
963
+ sessionManager.on("session:error", (sessionId, err) => {
964
+ bridge.writeNotification("session.error", { sessionId, message: err.message });
965
+ });
966
+ bridge.start((msg) => {
967
+ if (isResponse(msg)) {
968
+ const sessionId = pendingInstructions.get(msg.id);
969
+ if (sessionId && msg.result) {
970
+ pendingInstructions.delete(msg.id);
971
+ const result = msg.result;
972
+ sessionManager.send(sessionId, {
973
+ type: result.type ?? "result.success",
974
+ payload: result.payload ?? result
975
+ });
976
+ }
977
+ } else if (isRequest(msg)) {
978
+ if (msg.method === "send") {
979
+ const params = msg.params;
980
+ if (params?.["sessionId"]) {
981
+ const sent = sessionManager.send(params["sessionId"], {
982
+ type: params["type"] ?? "dialogue.message",
983
+ payload: params["payload"] ?? { content: "" }
984
+ });
985
+ bridge.writeResponse(msg.id, { success: sent });
986
+ }
987
+ } else if (msg.method === "approve") {
988
+ const params = msg.params;
989
+ const sid = params?.["sessionId"];
990
+ if (sid) {
991
+ approveAndConnect(client, sessionManager, bridge, sid).catch(() => {
992
+ bridge.writeResponse(msg.id, { success: false, error: "Failed to approve" });
993
+ }).then(() => {
994
+ bridge.writeResponse(msg.id, { success: true });
995
+ });
996
+ }
997
+ }
998
+ }
999
+ });
1000
+ let pollTimer = null;
1001
+ function connectAgentWs(token) {
1002
+ const wsBase = config.wsUrl ?? config.apiUrl.replace(/^http/, "ws");
1003
+ const url = `${wsBase}/ws/agent?token=${token}`;
1004
+ agentWs = new WebSocket3(url);
1005
+ agentWs.on("open", () => {
1006
+ bridge.writeNotification("agent.connected", { agentId: opts.agentId });
1007
+ agentHeartbeatTimer = setInterval(() => {
1008
+ if (agentWs && agentWs.readyState === WebSocket3.OPEN) {
1009
+ agentWs.send(JSON.stringify({ type: "system.heartbeat", payload: {} }));
1010
+ }
1011
+ }, 25e3);
1012
+ if (pollTimer) {
1013
+ clearInterval(pollTimer);
1014
+ pollTimer = null;
1015
+ }
1016
+ });
1017
+ agentWs.on("message", (raw) => {
1018
+ try {
1019
+ const message = JSON.parse(raw.toString());
1020
+ const type = message.type;
1021
+ if (type === "session.new" || type === "session.approved") {
1022
+ const payload = message.payload;
1023
+ const sid = payload["sessionId"];
1024
+ const sessionToken = payload["sessionToken"];
1025
+ if (knownPendingSessions.has(sid) || activeSessions.has(sid)) return;
1026
+ knownPendingSessions.add(sid);
1027
+ if (opts.autoApprove && type === "session.new") {
1028
+ approveAndConnect(client, sessionManager, bridge, sid);
1029
+ } else if (type === "session.approved" && sessionToken) {
1030
+ activeSessions.add(sid);
1031
+ bridge.writeNotification("session.new", {
1032
+ sessionId: sid,
1033
+ sessionToken,
1034
+ taskDescription: payload["taskDescription"] ?? "",
1035
+ consumerUserId: payload["consumerUserId"] ?? ""
1036
+ });
1037
+ sessionManager.connect(sid, sessionToken);
1038
+ } else {
1039
+ bridge.writeNotification("session.pending", {
1040
+ sessionId: sid,
1041
+ taskDescription: payload["taskDescription"] ?? "",
1042
+ consumerUserId: payload["consumerUserId"] ?? ""
1043
+ });
1044
+ }
1045
+ }
1046
+ } catch {
1047
+ }
1048
+ });
1049
+ agentWs.on("close", (code, reason) => {
1050
+ if (agentHeartbeatTimer) {
1051
+ clearInterval(agentHeartbeatTimer);
1052
+ agentHeartbeatTimer = null;
1053
+ }
1054
+ bridge.writeNotification("agent.disconnected", {
1055
+ agentId: opts.agentId,
1056
+ code,
1057
+ reason: reason.toString()
1058
+ });
1059
+ if (!pollTimer) {
1060
+ startPollLoop();
1061
+ }
1062
+ if (code !== 1e3 && code !== 4009) {
1063
+ setTimeout(() => connectAgentWs(token), 5e3);
1064
+ }
1065
+ });
1066
+ agentWs.on("error", () => {
1067
+ });
1068
+ }
1069
+ function startPollLoop() {
1070
+ pollTimer = setInterval(async () => {
1071
+ try {
1072
+ const sessions = await client.getSessions({
1073
+ role: "provider",
1074
+ status: "pending_approval"
1075
+ });
1076
+ for (const session of sessions.data ?? []) {
1077
+ const sid = session["id"];
1078
+ if (knownPendingSessions.has(sid) || activeSessions.has(sid)) continue;
1079
+ knownPendingSessions.add(sid);
1080
+ if (opts.autoApprove) {
1081
+ await approveAndConnect(client, sessionManager, bridge, sid);
1082
+ } else {
1083
+ bridge.writeNotification("session.pending", {
1084
+ sessionId: sid,
1085
+ taskDescription: session["taskDescription"] ?? "",
1086
+ consumerUserId: session["consumerUserId"] ?? ""
1087
+ });
1088
+ }
1089
+ }
1090
+ } catch {
1091
+ }
1092
+ }, pollInterval);
1093
+ }
1094
+ if (opts.agentToken) {
1095
+ connectAgentWs(opts.agentToken);
1096
+ } else {
1097
+ startPollLoop();
1098
+ }
1099
+ const shutdown = async () => {
1100
+ if (pollTimer) clearInterval(pollTimer);
1101
+ if (agentHeartbeatTimer) clearInterval(agentHeartbeatTimer);
1102
+ bridge.stop();
1103
+ if (agentWs) {
1104
+ try {
1105
+ agentWs.close(1e3, "Shutting down");
1106
+ } catch {
1107
+ }
1108
+ agentWs = null;
1109
+ }
1110
+ if (!opts.agentToken) {
1111
+ try {
1112
+ await client.setOnlineStatus(opts.agentId, "offline");
1113
+ } catch {
1114
+ }
1115
+ }
1116
+ sessionManager.disconnectAll();
1117
+ bridge.writeNotification("shutdown", { reason: "signal" });
1118
+ setTimeout(() => process.exit(0), 500);
1119
+ };
1120
+ process.on("SIGINT", () => {
1121
+ void shutdown();
1122
+ });
1123
+ process.on("SIGTERM", () => {
1124
+ void shutdown();
1125
+ });
1126
+ }
1127
+ async function approveAndConnect(client, sessionManager, bridge, sessionId) {
1128
+ try {
1129
+ const approved = await client.approveSession(sessionId);
1130
+ const sessionToken = approved["sessionToken"];
1131
+ const taskDescription = approved["taskDescription"];
1132
+ activeSessions.add(sessionId);
1133
+ bridge.writeNotification("session.new", {
1134
+ sessionId,
1135
+ sessionToken,
1136
+ taskDescription: taskDescription ?? "",
1137
+ consumerUserId: approved["consumerUserId"] ?? ""
1138
+ });
1139
+ sessionManager.connect(sessionId, sessionToken);
1140
+ } catch (err) {
1141
+ bridge.writeNotification("session.approve_failed", {
1142
+ sessionId,
1143
+ error: err instanceof Error ? err.message : String(err)
1144
+ });
1145
+ }
1146
+ }
1147
+
1148
+ // src/index.ts
1149
+ var program = new Command();
1150
+ program.name("clawrent").description("ClawRent CLI - Agent capability rental platform").version("0.1.0");
1151
+ registerAuthCommand(program);
1152
+ registerBrowseCommand(program);
1153
+ registerAgentCommand(program);
1154
+ registerSessionsCommand(program);
1155
+ registerBalanceCommand(program);
1156
+ registerTopupCommand(program);
1157
+ registerRentCommand(program);
1158
+ registerEndCommand(program);
1159
+ registerSendCommand(program);
1160
+ registerHealthCommand(program);
1161
+ registerProviderCommands(program);
1162
+ registerServeCommand(program);
1163
+ program.parse();
1164
+ //# sourceMappingURL=index.js.map