opencara 0.25.1 → 0.100.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 OpenCara
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OF OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/dist/bin.js ADDED
@@ -0,0 +1,600 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/commands/register.ts
4
+ import { createHash, randomBytes } from "node:crypto";
5
+ import { spawn } from "node:child_process";
6
+
7
+ // ../shared/dist/events.js
8
+ import { z } from "zod";
9
+ var PlatformSchema = z.enum(["github"]);
10
+ var PlatformEventSchema = z.object({
11
+ id: z.string(),
12
+ platform: PlatformSchema,
13
+ type: z.string(),
14
+ receivedAt: z.string().datetime(),
15
+ payload: z.unknown()
16
+ });
17
+
18
+ // ../shared/dist/agent.js
19
+ import { z as z2 } from "zod";
20
+ var AgentRunStatusSchema = z2.enum([
21
+ "queued",
22
+ "assigned",
23
+ "running",
24
+ "succeeded",
25
+ "failed",
26
+ "cancelled"
27
+ ]);
28
+ var AgentSpecSchema = z2.object({
29
+ kind: z2.string(),
30
+ command: z2.string(),
31
+ args: z2.array(z2.string()).default([]),
32
+ env: z2.record(z2.string()).default({}),
33
+ cwd: z2.string().optional()
34
+ });
35
+ var AgentRunSchema = z2.object({
36
+ id: z2.string(),
37
+ spec: AgentSpecSchema,
38
+ triggerEventId: z2.string().optional(),
39
+ status: AgentRunStatusSchema,
40
+ hostId: z2.string().nullable(),
41
+ createdAt: z2.string().datetime(),
42
+ startedAt: z2.string().datetime().nullable(),
43
+ finishedAt: z2.string().datetime().nullable(),
44
+ exitCode: z2.number().int().nullable()
45
+ });
46
+
47
+ // ../shared/dist/host-protocol.js
48
+ import { z as z3 } from "zod";
49
+ var PairingCreateRequestSchema = z3.object({
50
+ device_secret_hash: z3.string()
51
+ });
52
+ var PairingCreateResponseSchema = z3.object({
53
+ code: z3.string(),
54
+ expires_at: z3.string().datetime()
55
+ });
56
+ var PairingStatusResponseSchema = z3.union([
57
+ z3.object({ status: z3.literal("pending") }),
58
+ z3.object({
59
+ status: z3.literal("confirmed"),
60
+ token: z3.string(),
61
+ agent_host_id: z3.string(),
62
+ device_name: z3.string()
63
+ }),
64
+ z3.object({ status: z3.literal("expired") })
65
+ ]);
66
+ var PairingConfirmRequestSchema = z3.object({
67
+ device_name: z3.string().min(1)
68
+ });
69
+ var SystemInfoSchema = z3.object({
70
+ os: z3.string(),
71
+ // os.platform()
72
+ release: z3.string(),
73
+ // os.release()
74
+ arch: z3.string(),
75
+ // os.arch()
76
+ hostname: z3.string(),
77
+ cpu: z3.object({
78
+ model: z3.string(),
79
+ cores: z3.number().int().nonnegative(),
80
+ speedMhz: z3.number().int().nonnegative()
81
+ }),
82
+ memory: z3.object({
83
+ totalBytes: z3.number().nonnegative(),
84
+ freeBytes: z3.number().nonnegative()
85
+ }),
86
+ disk: z3.object({
87
+ path: z3.string(),
88
+ totalBytes: z3.number().nonnegative(),
89
+ freeBytes: z3.number().nonnegative()
90
+ }).optional(),
91
+ ipAddrs: z3.array(z3.string()).default([]),
92
+ uptimeSec: z3.number().nonnegative()
93
+ });
94
+ var HelloMessageSchema = z3.object({
95
+ type: z3.literal("hello"),
96
+ platform: z3.string(),
97
+ version: z3.string(),
98
+ capabilities: z3.array(z3.string()).default([]),
99
+ systemInfo: SystemInfoSchema.optional()
100
+ });
101
+ var JobAssignmentSchema = z3.object({
102
+ type: z3.literal("job"),
103
+ run: AgentRunSchema,
104
+ spec: AgentSpecSchema,
105
+ stdinJson: z3.unknown().optional()
106
+ });
107
+ var LogFrameSchema = z3.object({
108
+ type: z3.literal("log"),
109
+ runId: z3.string(),
110
+ seq: z3.number().int().min(0),
111
+ stream: z3.enum(["stdout", "stderr"]),
112
+ chunk: z3.string()
113
+ });
114
+ var RunDoneSchema = z3.object({
115
+ type: z3.literal("done"),
116
+ runId: z3.string(),
117
+ status: z3.enum(["succeeded", "failed", "cancelled"]),
118
+ exitCode: z3.number().int().nullable().optional(),
119
+ errorMessage: z3.string().optional()
120
+ });
121
+ var HelloAckSchema = z3.object({
122
+ type: z3.literal("hello-ack"),
123
+ agentHostId: z3.string(),
124
+ deviceName: z3.string()
125
+ });
126
+ var PingSchema = z3.object({ type: z3.literal("ping") });
127
+ var PongSchema = z3.object({ type: z3.literal("pong") });
128
+ var ServerToDeviceMessageSchema = z3.discriminatedUnion("type", [
129
+ JobAssignmentSchema,
130
+ HelloAckSchema,
131
+ PingSchema
132
+ ]);
133
+ var DeviceToServerMessageSchema = z3.discriminatedUnion("type", [
134
+ HelloMessageSchema,
135
+ LogFrameSchema,
136
+ RunDoneSchema,
137
+ PongSchema
138
+ ]);
139
+ var HostRegisterRequestSchema = z3.object({
140
+ hostId: z3.string(),
141
+ hostName: z3.string(),
142
+ capabilities: z3.array(z3.string()).default([]),
143
+ token: z3.string()
144
+ });
145
+ var HostRegisterResponseSchema = z3.object({
146
+ ok: z3.literal(true),
147
+ pollIntervalMs: z3.number().int().positive()
148
+ });
149
+
150
+ // src/config/store.ts
151
+ import { readFileSync, writeFileSync, mkdirSync, existsSync, unlinkSync } from "node:fs";
152
+ import { z as z4 } from "zod";
153
+
154
+ // src/config/paths.ts
155
+ import { homedir } from "node:os";
156
+ import { join } from "node:path";
157
+ var CONFIG_DIR = join(homedir(), ".opencara");
158
+ var CONFIG_FILE = join(CONFIG_DIR, "config.json");
159
+ var DEFAULT_ORCHESTRATOR_URL = "https://opencara.com";
160
+
161
+ // src/config/store.ts
162
+ var ConfigSchema = z4.object({
163
+ orchestratorUrl: z4.string().url(),
164
+ token: z4.string(),
165
+ agentHostId: z4.string(),
166
+ deviceName: z4.string()
167
+ });
168
+ function readConfig() {
169
+ if (!existsSync(CONFIG_FILE)) return null;
170
+ try {
171
+ return ConfigSchema.parse(JSON.parse(readFileSync(CONFIG_FILE, "utf8")));
172
+ } catch {
173
+ return null;
174
+ }
175
+ }
176
+ function writeConfig(cfg) {
177
+ if (!existsSync(CONFIG_DIR)) mkdirSync(CONFIG_DIR, { recursive: true, mode: 448 });
178
+ writeFileSync(CONFIG_FILE, JSON.stringify(cfg, null, 2), { mode: 384 });
179
+ }
180
+ function clearConfig() {
181
+ if (existsSync(CONFIG_FILE)) unlinkSync(CONFIG_FILE);
182
+ }
183
+ function defaultOrchestratorUrl() {
184
+ return process.env["OPENCARA_URL"] ?? DEFAULT_ORCHESTRATOR_URL;
185
+ }
186
+
187
+ // src/commands/register.ts
188
+ var POLL_INTERVAL_MS = 2e3;
189
+ async function register(opts = {}) {
190
+ const orchestratorUrl = opts.url ?? defaultOrchestratorUrl();
191
+ if (!opts.force && readConfig()) {
192
+ console.log("Already paired. Use --force to re-pair.");
193
+ return;
194
+ }
195
+ const deviceSecret = randomBytes(32).toString("base64url");
196
+ const deviceSecretHash = createHash("sha256").update(deviceSecret).digest("hex");
197
+ const createRes = await fetch(`${orchestratorUrl}/api/devices/pairings`, {
198
+ method: "POST",
199
+ headers: { "content-type": "application/json", "x-requested-with": "fetch" },
200
+ body: JSON.stringify({ device_secret_hash: deviceSecretHash })
201
+ });
202
+ if (!createRes.ok) {
203
+ throw new Error(`pairing create failed: ${createRes.status} ${await createRes.text()}`);
204
+ }
205
+ const { code, expires_at } = PairingCreateResponseSchema.parse(await createRes.json());
206
+ const pairUrl = `${orchestratorUrl}/devices/pair?code=${encodeURIComponent(code)}`;
207
+ console.log(`
208
+ Pairing code: ${code}`);
209
+ console.log(` Open ${pairUrl} in your browser to confirm.`);
210
+ console.log(` Expires at ${expires_at}.
211
+ `);
212
+ openBrowser(pairUrl);
213
+ const expiry = new Date(expires_at).getTime();
214
+ while (Date.now() < expiry) {
215
+ await sleep(POLL_INTERVAL_MS);
216
+ const statusRes = await fetch(
217
+ `${orchestratorUrl}/api/devices/pairings/${encodeURIComponent(code)}/status?secret=${deviceSecret}`
218
+ );
219
+ if (!statusRes.ok) {
220
+ console.error(` status check failed: ${statusRes.status}`);
221
+ continue;
222
+ }
223
+ const result = PairingStatusResponseSchema.parse(await statusRes.json());
224
+ if (result.status === "pending") {
225
+ process.stdout.write(".");
226
+ continue;
227
+ }
228
+ if (result.status === "expired") {
229
+ throw new Error("pairing expired before confirmation");
230
+ }
231
+ writeConfig({
232
+ orchestratorUrl,
233
+ token: result.token,
234
+ agentHostId: result.agent_host_id,
235
+ deviceName: result.device_name
236
+ });
237
+ console.log(`
238
+
239
+ \u2713 Paired as "${result.device_name}".`);
240
+ console.log(` Run 'opencara run' to start accepting jobs.`);
241
+ return;
242
+ }
243
+ throw new Error("pairing timed out");
244
+ }
245
+ function openBrowser(url) {
246
+ const cmd = process.platform === "darwin" ? "open" : process.platform === "win32" ? "start" : "xdg-open";
247
+ try {
248
+ const child = spawn(cmd, [url], { detached: true, stdio: "ignore" });
249
+ child.on("error", () => void 0);
250
+ child.unref();
251
+ } catch {
252
+ }
253
+ }
254
+ function sleep(ms) {
255
+ return new Promise((r) => setTimeout(r, ms));
256
+ }
257
+
258
+ // src/commands/run.ts
259
+ import {
260
+ arch,
261
+ cpus,
262
+ freemem,
263
+ hostname,
264
+ networkInterfaces,
265
+ platform,
266
+ release,
267
+ totalmem,
268
+ uptime
269
+ } from "node:os";
270
+ import { readFileSync as readFileSync2, statfsSync } from "node:fs";
271
+ import { dirname, join as join2 } from "node:path";
272
+ import { fileURLToPath } from "node:url";
273
+
274
+ // src/transport/ws-client.ts
275
+ import WebSocket from "ws";
276
+ var HEARTBEAT_MS = 3e4;
277
+ var WsClient = class {
278
+ constructor(opts) {
279
+ this.opts = opts;
280
+ this.backoff = opts.initialBackoffMs ?? 1e3;
281
+ }
282
+ opts;
283
+ ws = null;
284
+ backoff;
285
+ heartbeat = null;
286
+ stopped = false;
287
+ start() {
288
+ this.connect();
289
+ }
290
+ stop() {
291
+ this.stopped = true;
292
+ if (this.heartbeat) clearInterval(this.heartbeat);
293
+ this.ws?.close();
294
+ }
295
+ send(msg) {
296
+ if (!this.ws || this.ws.readyState !== WebSocket.OPEN) return;
297
+ const parsed = DeviceToServerMessageSchema.parse(msg);
298
+ this.ws.send(JSON.stringify(parsed));
299
+ }
300
+ connect() {
301
+ if (this.stopped) return;
302
+ const ws = new WebSocket(this.opts.url, {
303
+ headers: { Authorization: `Bearer ${this.opts.token}` }
304
+ });
305
+ this.ws = ws;
306
+ ws.on("open", () => {
307
+ this.backoff = this.opts.initialBackoffMs ?? 1e3;
308
+ if (this.heartbeat) clearInterval(this.heartbeat);
309
+ this.heartbeat = setInterval(() => {
310
+ if (ws.readyState === WebSocket.OPEN) ws.ping();
311
+ }, HEARTBEAT_MS);
312
+ this.opts.onOpen?.();
313
+ });
314
+ ws.on("message", (raw) => {
315
+ let parsed;
316
+ try {
317
+ parsed = ServerToDeviceMessageSchema.parse(JSON.parse(raw.toString()));
318
+ } catch (err) {
319
+ console.error("[ws] invalid frame", err);
320
+ return;
321
+ }
322
+ this.opts.onMessage(parsed);
323
+ });
324
+ ws.on("close", (code, reasonBuf) => {
325
+ const reason = reasonBuf.toString();
326
+ if (this.heartbeat) clearInterval(this.heartbeat);
327
+ this.opts.onClose?.(code, reason);
328
+ if (!this.stopped) this.scheduleReconnect();
329
+ });
330
+ ws.on("error", (err) => {
331
+ console.error("[ws] error", err.message);
332
+ });
333
+ }
334
+ scheduleReconnect() {
335
+ const max = this.opts.maxBackoffMs ?? 3e4;
336
+ const jittered = Math.floor(this.backoff * (0.5 + Math.random()));
337
+ setTimeout(() => this.connect(), jittered);
338
+ this.backoff = Math.min(this.backoff * 2, max);
339
+ }
340
+ };
341
+
342
+ // src/runner/spawn.ts
343
+ import { spawn as spawn2 } from "node:child_process";
344
+ function runJob(spec, stdinJson, handlers) {
345
+ return new Promise((resolve, reject) => {
346
+ const child = spawn2(spec.command, spec.args ?? [], {
347
+ env: { ...process.env, ...spec.env ?? {} },
348
+ cwd: spec.cwd,
349
+ stdio: ["pipe", "pipe", "pipe"]
350
+ });
351
+ child.stdout.setEncoding("utf8");
352
+ child.stderr.setEncoding("utf8");
353
+ child.stdout.on("data", (c) => handlers.onLog("stdout", c));
354
+ child.stderr.on("data", (c) => handlers.onLog("stderr", c));
355
+ child.on("error", reject);
356
+ child.on("close", (code) => resolve({ exitCode: code ?? -1 }));
357
+ if (stdinJson !== void 0) {
358
+ try {
359
+ child.stdin.end(JSON.stringify(stdinJson));
360
+ } catch (err) {
361
+ child.kill();
362
+ reject(err);
363
+ return;
364
+ }
365
+ } else {
366
+ child.stdin.end();
367
+ }
368
+ });
369
+ }
370
+
371
+ // src/commands/run.ts
372
+ var __dirname = dirname(fileURLToPath(import.meta.url));
373
+ var PKG_VERSION = readPkgVersion();
374
+ var LOG_FLUSH_MS = 800;
375
+ var MAX_CHUNK_SIZE = 4 * 1024;
376
+ async function run() {
377
+ const cfg = readConfig();
378
+ if (!cfg) {
379
+ throw new Error("Not paired. Run 'opencara register' first.");
380
+ }
381
+ const wsUrl = cfg.orchestratorUrl.replace(/^http/, "ws") + "/api/devices/ws";
382
+ const client = new WsClient({
383
+ url: wsUrl,
384
+ token: cfg.token,
385
+ onOpen: () => {
386
+ console.log(`[opencara] connected to ${cfg.orchestratorUrl}`);
387
+ client.send({
388
+ type: "hello",
389
+ platform: platform(),
390
+ version: PKG_VERSION,
391
+ capabilities: [],
392
+ systemInfo: collectSystemInfo()
393
+ });
394
+ },
395
+ onMessage: (msg) => handleServerMessage(msg, client, cfg),
396
+ onClose: (code, reason) => {
397
+ console.log(`[opencara] disconnected (code=${code} reason="${reason}")`);
398
+ }
399
+ });
400
+ console.log(`[opencara] starting as ${cfg.deviceName} (${hostname()})`);
401
+ client.start();
402
+ }
403
+ function handleServerMessage(msg, client, _cfg) {
404
+ if (msg.type === "hello-ack") {
405
+ console.log(`[opencara] acked as ${msg.deviceName} (${msg.agentHostId})`);
406
+ return;
407
+ }
408
+ if (msg.type === "ping") return;
409
+ if (msg.type === "job") {
410
+ void executeJob(msg, client);
411
+ }
412
+ }
413
+ async function executeJob(job, client) {
414
+ const runId = job.run.id;
415
+ console.log(`[opencara] job ${runId.slice(-8)}: ${job.spec.command}`);
416
+ let seq = 0;
417
+ let pending = { stdout: "", stderr: "" };
418
+ let flushTimer = null;
419
+ const flush = () => {
420
+ for (const stream of ["stdout", "stderr"]) {
421
+ const chunk = pending[stream];
422
+ if (!chunk) continue;
423
+ let remaining = chunk;
424
+ while (remaining.length > 0) {
425
+ const take = remaining.slice(0, MAX_CHUNK_SIZE);
426
+ client.send({ type: "log", runId, seq: seq++, stream, chunk: take });
427
+ remaining = remaining.slice(MAX_CHUNK_SIZE);
428
+ }
429
+ pending[stream] = "";
430
+ }
431
+ flushTimer = null;
432
+ };
433
+ const scheduleFlush = () => {
434
+ if (flushTimer) return;
435
+ flushTimer = setTimeout(flush, LOG_FLUSH_MS);
436
+ };
437
+ try {
438
+ const result = await runJob(job.spec, job.stdinJson, {
439
+ onLog: (stream, chunk) => {
440
+ pending[stream] += chunk;
441
+ scheduleFlush();
442
+ }
443
+ });
444
+ flush();
445
+ client.send({
446
+ type: "done",
447
+ runId,
448
+ status: result.exitCode === 0 ? "succeeded" : "failed",
449
+ exitCode: result.exitCode
450
+ });
451
+ console.log(`[opencara] job ${runId.slice(-8)} \u2192 exit ${result.exitCode}`);
452
+ } catch (err) {
453
+ flush();
454
+ const message = err instanceof Error ? err.message : String(err);
455
+ client.send({ type: "done", runId, status: "failed", errorMessage: message });
456
+ console.error(`[opencara] job ${runId.slice(-8)} failed`, message);
457
+ }
458
+ }
459
+ function readPkgVersion() {
460
+ try {
461
+ const raw = readFileSync2(join2(__dirname, "..", "..", "package.json"), "utf8");
462
+ return JSON.parse(raw).version ?? "0.0.0";
463
+ } catch {
464
+ return "0.0.0";
465
+ }
466
+ }
467
+ function collectSystemInfo() {
468
+ try {
469
+ const cpuList = cpus();
470
+ const head = cpuList[0];
471
+ const ipAddrs = [];
472
+ const ifaces = networkInterfaces();
473
+ for (const list of Object.values(ifaces)) {
474
+ if (!list) continue;
475
+ for (const iface of list) {
476
+ if (!iface.internal && iface.family === "IPv4") ipAddrs.push(iface.address);
477
+ }
478
+ }
479
+ let disk;
480
+ try {
481
+ const stats = statfsSync("/");
482
+ disk = {
483
+ path: "/",
484
+ totalBytes: Number(stats.blocks) * Number(stats.bsize),
485
+ freeBytes: Number(stats.bavail) * Number(stats.bsize)
486
+ };
487
+ } catch {
488
+ disk = void 0;
489
+ }
490
+ return {
491
+ os: platform(),
492
+ release: release(),
493
+ arch: arch(),
494
+ hostname: hostname(),
495
+ cpu: {
496
+ model: head?.model.trim() ?? "unknown",
497
+ cores: cpuList.length,
498
+ speedMhz: head?.speed ?? 0
499
+ },
500
+ memory: { totalBytes: totalmem(), freeBytes: freemem() },
501
+ disk,
502
+ ipAddrs,
503
+ uptimeSec: Math.floor(uptime())
504
+ };
505
+ } catch (err) {
506
+ console.warn("[opencara] system info collection failed", err);
507
+ return void 0;
508
+ }
509
+ }
510
+
511
+ // src/commands/status.ts
512
+ async function status() {
513
+ const cfg = readConfig();
514
+ if (!cfg) {
515
+ console.log("Not paired. Run 'opencara register' to pair.");
516
+ return;
517
+ }
518
+ console.log(`Paired:`);
519
+ console.log(` Device: ${cfg.deviceName}`);
520
+ console.log(` Host ID: ${cfg.agentHostId}`);
521
+ console.log(` Server: ${cfg.orchestratorUrl}`);
522
+ }
523
+
524
+ // src/commands/logout.ts
525
+ async function logout() {
526
+ const cfg = readConfig();
527
+ if (!cfg) {
528
+ console.log("Not paired.");
529
+ return;
530
+ }
531
+ try {
532
+ const res = await fetch(
533
+ `${cfg.orchestratorUrl}/api/devices/${cfg.agentHostId}/revoke`,
534
+ {
535
+ method: "POST",
536
+ headers: {
537
+ authorization: `Bearer ${cfg.token}`,
538
+ "x-requested-with": "fetch"
539
+ }
540
+ }
541
+ );
542
+ if (!res.ok) {
543
+ console.warn(`server revoke responded ${res.status}; clearing local config anyway`);
544
+ }
545
+ } catch (err) {
546
+ console.warn(`server revoke failed: ${err.message}; clearing local config anyway`);
547
+ }
548
+ clearConfig();
549
+ console.log("Removed local credentials.");
550
+ }
551
+
552
+ // src/bin.ts
553
+ async function main() {
554
+ const [, , cmd, ...rest] = process.argv;
555
+ switch (cmd) {
556
+ case "register":
557
+ await register({
558
+ force: rest.includes("--force"),
559
+ url: pickFlag(rest, "--url")
560
+ });
561
+ return;
562
+ case "run":
563
+ await run();
564
+ return;
565
+ case "status":
566
+ await status();
567
+ return;
568
+ case "logout":
569
+ await logout();
570
+ return;
571
+ case "--help":
572
+ case "-h":
573
+ case void 0:
574
+ printHelp();
575
+ return;
576
+ default:
577
+ console.error(`unknown command: ${cmd}`);
578
+ printHelp();
579
+ process.exit(1);
580
+ }
581
+ }
582
+ function pickFlag(argv, name) {
583
+ const i = argv.indexOf(name);
584
+ if (i === -1) return void 0;
585
+ return argv[i + 1];
586
+ }
587
+ function printHelp() {
588
+ console.log(`opencara \u2014 agent host CLI
589
+
590
+ Usage:
591
+ opencara register [--url URL] [--force]
592
+ opencara run
593
+ opencara status
594
+ opencara logout
595
+ `);
596
+ }
597
+ main().catch((err) => {
598
+ console.error(err instanceof Error ? err.message : err);
599
+ process.exit(1);
600
+ });
package/package.json CHANGED
@@ -1,58 +1,37 @@
1
1
  {
2
2
  "name": "opencara",
3
- "version": "0.25.1",
4
- "description": "Distributed AI code review agent poll, review, and submit PR reviews using your own AI tools",
5
- "type": "module",
3
+ "version": "0.100.0",
4
+ "description": "OpenCara agent-host CLI: register a machine as an agent host and run dispatched agents.",
6
5
  "license": "MIT",
7
- "author": "OpenCara <https://github.com/OpenCara>",
8
- "homepage": "https://github.com/OpenCara/OpenCara#readme",
9
6
  "repository": {
10
7
  "type": "git",
11
8
  "url": "https://github.com/OpenCara/OpenCara.git",
12
9
  "directory": "packages/cli"
13
10
  },
14
- "bugs": {
15
- "url": "https://github.com/OpenCara/OpenCara/issues"
16
- },
17
- "keywords": [
18
- "ai",
19
- "code-review",
20
- "github",
21
- "pull-request",
22
- "cli",
23
- "agent",
24
- "review",
25
- "openai",
26
- "claude",
27
- "gemini"
28
- ],
29
- "engines": {
30
- "node": ">=20"
31
- },
11
+ "homepage": "https://opencara.com",
12
+ "type": "module",
32
13
  "bin": {
33
- "opencara": "dist/index.js"
14
+ "opencara": "./dist/bin.js"
34
15
  },
16
+ "main": "./dist/bin.js",
35
17
  "files": [
36
- "dist",
37
- "README.md"
18
+ "dist/bin.js"
38
19
  ],
39
- "scripts": {
40
- "build": "tsup",
41
- "dev": "tsx src/index.ts",
42
- "clean": "rm -rf dist *.tsbuildinfo"
43
- },
44
20
  "dependencies": {
45
- "commander": "^13.0.0",
46
- "picocolors": "^1.1.1",
47
- "smol-toml": "^1.6.1"
21
+ "ws": "^8.18.0",
22
+ "zod": "^3.24.1"
48
23
  },
49
24
  "devDependencies": {
50
- "@cloudflare/workers-types": "^4.20250214.0",
51
- "@opencara/server": "workspace:*",
52
- "@opencara/shared": "workspace:*",
53
- "@types/node": "^22.0.0",
54
- "hono": "^4.7.0",
55
- "tsup": "^8.5.1",
56
- "tsx": "^4.0.0"
25
+ "@types/ws": "^8.5.13",
26
+ "esbuild": "^0.27.3",
27
+ "tsx": "^4.19.2",
28
+ "@opencara/shared": "0.0.0"
29
+ },
30
+ "scripts": {
31
+ "build": "tsc -b && node build.mjs",
32
+ "dev": "tsx watch src/bin.ts",
33
+ "start": "node dist/bin.js",
34
+ "typecheck": "tsc -b",
35
+ "clean": "rm -rf dist *.tsbuildinfo"
57
36
  }
58
- }
37
+ }