opencara 0.100.0 → 0.100.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.
Files changed (2) hide show
  1. package/dist/bin.js +180 -175
  2. package/package.json +1 -1
package/dist/bin.js CHANGED
@@ -1,23 +1,77 @@
1
1
  #!/usr/bin/env node
2
2
 
3
+ // src/commands/run.ts
4
+ import {
5
+ arch,
6
+ cpus,
7
+ freemem,
8
+ hostname as hostname2,
9
+ networkInterfaces,
10
+ platform,
11
+ release,
12
+ totalmem,
13
+ uptime
14
+ } from "node:os";
15
+ import { readFileSync as readFileSync2, statfsSync } from "node:fs";
16
+ import { dirname, join as join2 } from "node:path";
17
+ import { fileURLToPath } from "node:url";
18
+
19
+ // src/config/store.ts
20
+ import { readFileSync, writeFileSync, mkdirSync, existsSync, unlinkSync } from "node:fs";
21
+ import { z } from "zod";
22
+
23
+ // src/config/paths.ts
24
+ import { homedir } from "node:os";
25
+ import { join } from "node:path";
26
+ var CONFIG_DIR = join(homedir(), ".opencara");
27
+ var CONFIG_FILE = join(CONFIG_DIR, "config.json");
28
+ var DEFAULT_ORCHESTRATOR_URL = "https://opencara.com";
29
+
30
+ // src/config/store.ts
31
+ var ConfigSchema = z.object({
32
+ orchestratorUrl: z.string().url(),
33
+ token: z.string(),
34
+ agentHostId: z.string(),
35
+ deviceName: z.string()
36
+ });
37
+ function readConfig() {
38
+ if (!existsSync(CONFIG_FILE)) return null;
39
+ try {
40
+ return ConfigSchema.parse(JSON.parse(readFileSync(CONFIG_FILE, "utf8")));
41
+ } catch {
42
+ return null;
43
+ }
44
+ }
45
+ function writeConfig(cfg) {
46
+ if (!existsSync(CONFIG_DIR)) mkdirSync(CONFIG_DIR, { recursive: true, mode: 448 });
47
+ writeFileSync(CONFIG_FILE, JSON.stringify(cfg, null, 2), { mode: 384 });
48
+ }
49
+ function clearConfig() {
50
+ if (existsSync(CONFIG_FILE)) unlinkSync(CONFIG_FILE);
51
+ }
52
+ function defaultOrchestratorUrl() {
53
+ return process.env["OPENCARA_URL"] ?? DEFAULT_ORCHESTRATOR_URL;
54
+ }
55
+
3
56
  // src/commands/register.ts
4
57
  import { createHash, randomBytes } from "node:crypto";
5
58
  import { spawn } from "node:child_process";
59
+ import { hostname } from "node:os";
6
60
 
7
61
  // ../shared/dist/events.js
8
- import { z } from "zod";
9
- var PlatformSchema = z.enum(["github"]);
10
- var PlatformEventSchema = z.object({
11
- id: z.string(),
62
+ import { z as z2 } from "zod";
63
+ var PlatformSchema = z2.enum(["github"]);
64
+ var PlatformEventSchema = z2.object({
65
+ id: z2.string(),
12
66
  platform: PlatformSchema,
13
- type: z.string(),
14
- receivedAt: z.string().datetime(),
15
- payload: z.unknown()
67
+ type: z2.string(),
68
+ receivedAt: z2.string().datetime(),
69
+ payload: z2.unknown()
16
70
  });
17
71
 
18
72
  // ../shared/dist/agent.js
19
- import { z as z2 } from "zod";
20
- var AgentRunStatusSchema = z2.enum([
73
+ import { z as z3 } from "zod";
74
+ var AgentRunStatusSchema = z3.enum([
21
75
  "queued",
22
76
  "assigned",
23
77
  "running",
@@ -25,171 +79,134 @@ var AgentRunStatusSchema = z2.enum([
25
79
  "failed",
26
80
  "cancelled"
27
81
  ]);
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()
82
+ var AgentSpecSchema = z3.object({
83
+ kind: z3.string(),
84
+ command: z3.string(),
85
+ args: z3.array(z3.string()).default([]),
86
+ env: z3.record(z3.string()).default({}),
87
+ cwd: z3.string().optional()
34
88
  });
35
- var AgentRunSchema = z2.object({
36
- id: z2.string(),
89
+ var AgentRunSchema = z3.object({
90
+ id: z3.string(),
37
91
  spec: AgentSpecSchema,
38
- triggerEventId: z2.string().optional(),
92
+ triggerEventId: z3.string().optional(),
39
93
  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()
94
+ hostId: z3.string().nullable(),
95
+ createdAt: z3.string().datetime(),
96
+ startedAt: z3.string().datetime().nullable(),
97
+ finishedAt: z3.string().datetime().nullable(),
98
+ exitCode: z3.number().int().nullable()
45
99
  });
46
100
 
47
101
  // ../shared/dist/host-protocol.js
48
- import { z as z3 } from "zod";
49
- var PairingCreateRequestSchema = z3.object({
50
- device_secret_hash: z3.string()
102
+ import { z as z4 } from "zod";
103
+ var PairingCreateRequestSchema = z4.object({
104
+ device_secret_hash: z4.string()
51
105
  });
52
- var PairingCreateResponseSchema = z3.object({
53
- code: z3.string(),
54
- expires_at: z3.string().datetime()
106
+ var PairingCreateResponseSchema = z4.object({
107
+ code: z4.string(),
108
+ expires_at: z4.string().datetime()
55
109
  });
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()
110
+ var PairingStatusResponseSchema = z4.union([
111
+ z4.object({ status: z4.literal("pending") }),
112
+ z4.object({
113
+ status: z4.literal("confirmed"),
114
+ token: z4.string(),
115
+ agent_host_id: z4.string(),
116
+ device_name: z4.string()
63
117
  }),
64
- z3.object({ status: z3.literal("expired") })
118
+ z4.object({ status: z4.literal("expired") })
65
119
  ]);
66
- var PairingConfirmRequestSchema = z3.object({
67
- device_name: z3.string().min(1)
120
+ var PairingConfirmRequestSchema = z4.object({
121
+ device_name: z4.string().min(1)
68
122
  });
69
- var SystemInfoSchema = z3.object({
70
- os: z3.string(),
123
+ var SystemInfoSchema = z4.object({
124
+ os: z4.string(),
71
125
  // os.platform()
72
- release: z3.string(),
126
+ release: z4.string(),
73
127
  // os.release()
74
- arch: z3.string(),
128
+ arch: z4.string(),
75
129
  // 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()
130
+ hostname: z4.string(),
131
+ cpu: z4.object({
132
+ model: z4.string(),
133
+ cores: z4.number().int().nonnegative(),
134
+ speedMhz: z4.number().int().nonnegative()
81
135
  }),
82
- memory: z3.object({
83
- totalBytes: z3.number().nonnegative(),
84
- freeBytes: z3.number().nonnegative()
136
+ memory: z4.object({
137
+ totalBytes: z4.number().nonnegative(),
138
+ freeBytes: z4.number().nonnegative()
85
139
  }),
86
- disk: z3.object({
87
- path: z3.string(),
88
- totalBytes: z3.number().nonnegative(),
89
- freeBytes: z3.number().nonnegative()
140
+ disk: z4.object({
141
+ path: z4.string(),
142
+ totalBytes: z4.number().nonnegative(),
143
+ freeBytes: z4.number().nonnegative()
90
144
  }).optional(),
91
- ipAddrs: z3.array(z3.string()).default([]),
92
- uptimeSec: z3.number().nonnegative()
145
+ ipAddrs: z4.array(z4.string()).default([]),
146
+ uptimeSec: z4.number().nonnegative()
93
147
  });
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([]),
148
+ var HelloMessageSchema = z4.object({
149
+ type: z4.literal("hello"),
150
+ platform: z4.string(),
151
+ version: z4.string(),
152
+ capabilities: z4.array(z4.string()).default([]),
99
153
  systemInfo: SystemInfoSchema.optional()
100
154
  });
101
- var JobAssignmentSchema = z3.object({
102
- type: z3.literal("job"),
155
+ var JobAssignmentSchema = z4.object({
156
+ type: z4.literal("job"),
103
157
  run: AgentRunSchema,
104
158
  spec: AgentSpecSchema,
105
- stdinJson: z3.unknown().optional()
159
+ stdinJson: z4.unknown().optional()
106
160
  });
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()
161
+ var LogFrameSchema = z4.object({
162
+ type: z4.literal("log"),
163
+ runId: z4.string(),
164
+ seq: z4.number().int().min(0),
165
+ stream: z4.enum(["stdout", "stderr"]),
166
+ chunk: z4.string()
113
167
  });
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()
168
+ var RunDoneSchema = z4.object({
169
+ type: z4.literal("done"),
170
+ runId: z4.string(),
171
+ status: z4.enum(["succeeded", "failed", "cancelled"]),
172
+ exitCode: z4.number().int().nullable().optional(),
173
+ errorMessage: z4.string().optional()
120
174
  });
121
- var HelloAckSchema = z3.object({
122
- type: z3.literal("hello-ack"),
123
- agentHostId: z3.string(),
124
- deviceName: z3.string()
175
+ var HelloAckSchema = z4.object({
176
+ type: z4.literal("hello-ack"),
177
+ agentHostId: z4.string(),
178
+ deviceName: z4.string()
125
179
  });
126
- var PingSchema = z3.object({ type: z3.literal("ping") });
127
- var PongSchema = z3.object({ type: z3.literal("pong") });
128
- var ServerToDeviceMessageSchema = z3.discriminatedUnion("type", [
180
+ var PingSchema = z4.object({ type: z4.literal("ping") });
181
+ var PongSchema = z4.object({ type: z4.literal("pong") });
182
+ var ServerToDeviceMessageSchema = z4.discriminatedUnion("type", [
129
183
  JobAssignmentSchema,
130
184
  HelloAckSchema,
131
185
  PingSchema
132
186
  ]);
133
- var DeviceToServerMessageSchema = z3.discriminatedUnion("type", [
187
+ var DeviceToServerMessageSchema = z4.discriminatedUnion("type", [
134
188
  HelloMessageSchema,
135
189
  LogFrameSchema,
136
190
  RunDoneSchema,
137
191
  PongSchema
138
192
  ]);
139
- var HostRegisterRequestSchema = z3.object({
140
- hostId: z3.string(),
141
- hostName: z3.string(),
142
- capabilities: z3.array(z3.string()).default([]),
143
- token: z3.string()
193
+ var HostRegisterRequestSchema = z4.object({
194
+ hostId: z4.string(),
195
+ hostName: z4.string(),
196
+ capabilities: z4.array(z4.string()).default([]),
197
+ token: z4.string()
144
198
  });
145
- var HostRegisterResponseSchema = z3.object({
146
- ok: z3.literal(true),
147
- pollIntervalMs: z3.number().int().positive()
199
+ var HostRegisterResponseSchema = z4.object({
200
+ ok: z4.literal(true),
201
+ pollIntervalMs: z4.number().int().positive()
148
202
  });
149
203
 
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
204
  // src/commands/register.ts
188
205
  var POLL_INTERVAL_MS = 2e3;
189
206
  async function register(opts = {}) {
190
207
  const orchestratorUrl = opts.url ?? defaultOrchestratorUrl();
191
- if (!opts.force && readConfig()) {
192
- console.log("Already paired. Use --force to re-pair.");
208
+ if (!opts.forcePair && readConfig()) {
209
+ console.log("Already paired. Use --force-pair to re-pair.");
193
210
  return;
194
211
  }
195
212
  const deviceSecret = randomBytes(32).toString("base64url");
@@ -203,7 +220,7 @@ async function register(opts = {}) {
203
220
  throw new Error(`pairing create failed: ${createRes.status} ${await createRes.text()}`);
204
221
  }
205
222
  const { code, expires_at } = PairingCreateResponseSchema.parse(await createRes.json());
206
- const pairUrl = `${orchestratorUrl}/devices/pair?code=${encodeURIComponent(code)}`;
223
+ const pairUrl = `${orchestratorUrl}/devices/pair?code=${encodeURIComponent(code)}&hostname=${encodeURIComponent(hostname())}`;
207
224
  console.log(`
208
225
  Pairing code: ${code}`);
209
226
  console.log(` Open ${pairUrl} in your browser to confirm.`);
@@ -237,7 +254,6 @@ async function register(opts = {}) {
237
254
  console.log(`
238
255
 
239
256
  \u2713 Paired as "${result.device_name}".`);
240
- console.log(` Run 'opencara run' to start accepting jobs.`);
241
257
  return;
242
258
  }
243
259
  throw new Error("pairing timed out");
@@ -255,22 +271,6 @@ function sleep(ms) {
255
271
  return new Promise((r) => setTimeout(r, ms));
256
272
  }
257
273
 
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
274
  // src/transport/ws-client.ts
275
275
  import WebSocket from "ws";
276
276
  var HEARTBEAT_MS = 3e4;
@@ -373,10 +373,12 @@ var __dirname = dirname(fileURLToPath(import.meta.url));
373
373
  var PKG_VERSION = readPkgVersion();
374
374
  var LOG_FLUSH_MS = 800;
375
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.");
376
+ async function run(opts = {}) {
377
+ let cfg = readConfig();
378
+ if (!cfg || opts.forcePair) {
379
+ await register({ url: opts.url, forcePair: opts.forcePair });
380
+ cfg = readConfig();
381
+ if (!cfg) throw new Error("pairing did not save a config");
380
382
  }
381
383
  const wsUrl = cfg.orchestratorUrl.replace(/^http/, "ws") + "/api/devices/ws";
382
384
  const client = new WsClient({
@@ -397,7 +399,7 @@ async function run() {
397
399
  console.log(`[opencara] disconnected (code=${code} reason="${reason}")`);
398
400
  }
399
401
  });
400
- console.log(`[opencara] starting as ${cfg.deviceName} (${hostname()})`);
402
+ console.log(`[opencara] starting as ${cfg.deviceName} (${hostname2()})`);
401
403
  client.start();
402
404
  }
403
405
  function handleServerMessage(msg, client, _cfg) {
@@ -491,7 +493,7 @@ function collectSystemInfo() {
491
493
  os: platform(),
492
494
  release: release(),
493
495
  arch: arch(),
494
- hostname: hostname(),
496
+ hostname: hostname2(),
495
497
  cpu: {
496
498
  model: head?.model.trim() ?? "unknown",
497
499
  cores: cpuList.length,
@@ -512,7 +514,7 @@ function collectSystemInfo() {
512
514
  async function status() {
513
515
  const cfg = readConfig();
514
516
  if (!cfg) {
515
- console.log("Not paired. Run 'opencara register' to pair.");
517
+ console.log("Not paired. Run 'opencara' to pair and start accepting jobs.");
516
518
  return;
517
519
  }
518
520
  console.log(`Paired:`);
@@ -551,17 +553,11 @@ async function logout() {
551
553
 
552
554
  // src/bin.ts
553
555
  async function main() {
554
- const [, , cmd, ...rest] = process.argv;
556
+ const argv = process.argv.slice(2);
557
+ const cmd = argv[0];
555
558
  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;
559
+ case void 0:
560
+ break;
565
561
  case "status":
566
562
  await status();
567
563
  return;
@@ -570,14 +566,19 @@ async function main() {
570
566
  return;
571
567
  case "--help":
572
568
  case "-h":
573
- case void 0:
574
569
  printHelp();
575
570
  return;
576
571
  default:
577
- console.error(`unknown command: ${cmd}`);
578
- printHelp();
579
- process.exit(1);
572
+ if (!cmd.startsWith("-")) {
573
+ console.error(`unknown command: ${cmd}`);
574
+ printHelp();
575
+ process.exit(1);
576
+ }
580
577
  }
578
+ await run({
579
+ forcePair: argv.includes("--force-pair"),
580
+ url: pickFlag(argv, "--url")
581
+ });
581
582
  }
582
583
  function pickFlag(argv, name) {
583
584
  const i = argv.indexOf(name);
@@ -588,10 +589,14 @@ function printHelp() {
588
589
  console.log(`opencara \u2014 agent host CLI
589
590
 
590
591
  Usage:
591
- opencara register [--url URL] [--force]
592
- opencara run
593
- opencara status
594
- opencara logout
592
+ opencara [--url URL] [--force-pair] Pair (if needed) and start accepting jobs.
593
+ opencara status Show pairing state.
594
+ opencara logout Forget the saved pairing.
595
+
596
+ Options:
597
+ --url URL Orchestrator URL (default: https://opencara.com,
598
+ or $OPENCARA_URL).
599
+ --force-pair Re-pair even if already paired.
595
600
  `);
596
601
  }
597
602
  main().catch((err) => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "opencara",
3
- "version": "0.100.0",
3
+ "version": "0.100.2",
4
4
  "description": "OpenCara agent-host CLI: register a machine as an agent host and run dispatched agents.",
5
5
  "license": "MIT",
6
6
  "repository": {