playclaw 1.0.0 → 1.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.
Files changed (2) hide show
  1. package/index.js +230 -136
  2. package/package.json +1 -1
package/index.js CHANGED
@@ -11,10 +11,8 @@
11
11
  * What it does:
12
12
  * 1. Reads your OpenClaw gateway token (auto-detects or you provide it)
13
13
  * 2. Connects to your local OpenClaw gateway (localhost:18789)
14
- * 3. Subscribes to PlayClaw's Supabase Realtime channel (bridge:<token>)
14
+ * 3. Opens a private bridge channel to PlayClaw's cloud
15
15
  * 4. Relays messages between the PlayClaw playground and your agent
16
- *
17
- * No separate bridge server needed — everything runs through Supabase.
18
16
  */
19
17
 
20
18
  "use strict";
@@ -26,7 +24,7 @@ const path = require("path");
26
24
  const os = require("os");
27
25
  const http = require("http");
28
26
 
29
- // ─── Parse args ───────────────────────────────────────────────────────────────
27
+ // ─── Parse args ────────────────────────────────────────────────────────────────
30
28
 
31
29
  const args = process.argv.slice(2);
32
30
 
@@ -37,12 +35,11 @@ if (args.length === 0 || args[0] === "--help" || args[0] === "-h") {
37
35
 
38
36
  const playclawToken = args[0];
39
37
  if (!playclawToken.startsWith("PC-")) {
40
- console.error("Invalid PlayClaw token. It should start with PC-");
41
- console.error(" Get your token from: https://playclaw.info/connect");
38
+ console.error("\n ✗ Invalid PlayClaw token it should start with PC-");
39
+ console.error(" Get your token at: https://playclaw.info/connect\n");
42
40
  process.exit(1);
43
41
  }
44
42
 
45
- // Optional flags
46
43
  const flagIndex = (flag) => args.findIndex((a) => a === flag);
47
44
  const flagValue = (flag) => {
48
45
  const i = flagIndex(flag);
@@ -52,13 +49,11 @@ const flagValue = (flag) => {
52
49
  const gatewayPortArg = parseInt(flagValue("--port") || flagValue("-p")) || null;
53
50
  const gatewayTokenArg = flagValue("--gateway-token") || flagValue("-g");
54
51
 
55
- // ─── Config ───────────────────────────────────────────────────────────────────
52
+ // ─── Config ────────────────────────────────────────────────────────────────────
56
53
 
57
- // PlayClaw's Supabase public credentials — safe to embed (anon key is public)
58
- // These are overridable via env vars for local development / self-hosting.
59
- const SUPABASE_URL =
54
+ const CLOUD_URL =
60
55
  process.env.PLAYCLAW_SUPABASE_URL || "https://bbplxuprwmbkbslvwjfv.supabase.co";
61
- const SUPABASE_ANON_KEY =
56
+ const CLOUD_KEY =
62
57
  process.env.PLAYCLAW_SUPABASE_ANON_KEY ||
63
58
  "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6ImJicGx4dXByd21ia2JzbHZ3amZ2Iiwicm9sZSI6ImFub24iLCJpYXQiOjE3NzIzMjY2OTIsImV4cCI6MjA4NzkwMjY5Mn0.Y_R6ozJmyCiu_oCKJYJ7Bt17xbztRlZEk7eAI2D81fM";
64
59
 
@@ -67,25 +62,20 @@ const OPENCLAW_PORT =
67
62
  parseInt(process.env.OPENCLAW_PORT || "") ||
68
63
  18789;
69
64
 
70
- // ─── Auto-detect OpenClaw gateway token ─────────────────────────────────────
65
+ // ─── Auto-detect OpenClaw gateway token ────────────────────────────────────────
71
66
 
72
67
  function detectGatewayToken() {
73
- // 1. CLI flag
74
68
  if (gatewayTokenArg) return gatewayTokenArg;
75
-
76
- // 2. Environment variable
77
69
  if (process.env.OPENCLAW_GATEWAY_TOKEN) return process.env.OPENCLAW_GATEWAY_TOKEN;
78
70
 
79
- // 3. Auto-detect from ~/.openclaw/openclaw.json
80
71
  const configPath = path.join(os.homedir(), ".openclaw", "openclaw.json");
81
72
  try {
82
73
  if (!fs.existsSync(configPath)) return null;
83
- // openclaw.json supports JSON5 (comments + trailing commas) — strip them
84
74
  const raw = fs.readFileSync(configPath, "utf-8");
85
75
  const cleaned = raw
86
- .replace(/\/\/[^\n]*/g, "") // strip // comments
87
- .replace(/\/\*[\s\S]*?\*\//g, "") // strip /* */ comments
88
- .replace(/,\s*([}\]])/g, "$1"); // strip trailing commas
76
+ .replace(/\/\/[^\n]*/g, "")
77
+ .replace(/\/\*[\s\S]*?\*\//g, "")
78
+ .replace(/,\s*([}\]])/g, "$1");
89
79
  const config = JSON.parse(cleaned);
90
80
  return config?.gateway?.auth?.token || null;
91
81
  } catch {
@@ -96,23 +86,18 @@ function detectGatewayToken() {
96
86
  const gatewayToken = detectGatewayToken();
97
87
 
98
88
  if (!gatewayToken) {
99
- console.error("");
100
- console.error(" Could not find your OpenClaw gateway token.");
101
- console.error("");
102
- console.error(" Option Aprovide it directly:");
103
- console.error(` npx playclaw ${playclawToken} --gateway-token <your-token>`);
104
- console.error("");
105
- console.error(" Option B set an env var:");
106
- console.error(` OPENCLAW_GATEWAY_TOKEN=<token> npx playclaw ${playclawToken}`);
107
- console.error("");
108
- console.error(" Option C — find it in your OpenClaw config:");
109
- console.error(" openclaw config get gateway.auth.token");
110
- console.error(" or look in: ~/.openclaw/openclaw.json → gateway.auth.token");
111
- console.error("");
89
+ console.error("\n ✗ Could not find your OpenClaw gateway token.\n");
90
+ console.error(" Option A provide it directly:");
91
+ console.error(` npx playclaw ${playclawToken} --gateway-token <your-token>\n`);
92
+ console.error(" Option Bset an env var:");
93
+ console.error(` OPENCLAW_GATEWAY_TOKEN=<token> npx playclaw ${playclawToken}\n`);
94
+ console.error(" Option C — auto-detect from config:");
95
+ console.error(" openclaw config get gateway.auth.token");
96
+ console.error(" (~/.openclaw/openclaw.json gateway.auth.token)\n");
112
97
  process.exit(1);
113
98
  }
114
99
 
115
- // ─── Logging ─────────────────────────────────────────────────────────────────
100
+ // ─── Colors ────────────────────────────────────────────────────────────────────
116
101
 
117
102
  const c = {
118
103
  reset: "\x1b[0m",
@@ -123,117 +108,201 @@ const c = {
123
108
  red: "\x1b[31m",
124
109
  cyan: "\x1b[36m",
125
110
  magenta: "\x1b[35m",
111
+ gray: "\x1b[90m",
126
112
  };
127
113
 
114
+ // ─── Spinner ───────────────────────────────────────────────────────────────────
115
+
116
+ class Spinner {
117
+ constructor() {
118
+ this.frames = ["⠋","⠙","⠹","⠸","⠼","⠴","⠦","⠧","⠇","⠏"];
119
+ this.i = 0;
120
+ this._iv = null;
121
+ this._active = false;
122
+ }
123
+
124
+ start(text) {
125
+ this._active = true;
126
+ this.i = 0;
127
+ process.stdout.write("\n");
128
+ this._iv = setInterval(() => {
129
+ if (!this._active) return;
130
+ const f = this.frames[this.i++ % this.frames.length];
131
+ process.stdout.write(`\r ${c.cyan}${f}${c.reset} ${c.dim}${text}${c.reset} `);
132
+ }, 80);
133
+ return this;
134
+ }
135
+
136
+ success(text) {
137
+ this._active = false;
138
+ clearInterval(this._iv);
139
+ process.stdout.write(`\r ${c.green}✓${c.reset} ${text}${" ".repeat(10)}\n`);
140
+ }
141
+
142
+ fail(text) {
143
+ this._active = false;
144
+ clearInterval(this._iv);
145
+ process.stdout.write(`\r ${c.red}✗${c.reset} ${c.red}${text}${c.reset}${" ".repeat(10)}\n\n`);
146
+ }
147
+ }
148
+
149
+ const spinner = new Spinner();
150
+
151
+ // ─── Logging ───────────────────────────────────────────────────────────────────
152
+
128
153
  function log(icon, msg, color = c.reset) {
129
154
  const time = new Date().toLocaleTimeString("en-US", { hour12: false });
130
- console.log(`${c.dim}${time}${c.reset} ${icon} ${color}${msg}${c.reset}`);
155
+ console.log(` ${c.gray}${time}${c.reset} ${icon} ${color}${msg}${c.reset}`);
131
156
  }
132
157
 
133
- // ─── State ────────────────────────────────────────────────────────────────────
158
+ // ─── State ─────────────────────────────────────────────────────────────────────
134
159
 
135
- let openclawWs = null;
136
- let bridgeChannel = null;
137
- let openclawReady = false;
138
- let pendingMessages = []; // queued while OpenClaw reconnects
160
+ let openclawWs = null;
161
+ let bridgeChannel = null;
162
+ let openclawReady = false;
163
+ let pendingMessages = [];
139
164
  let reconnectTimer = null;
165
+ let gatewayConnected = false;
166
+ let bridgeConnected = false;
140
167
 
141
- // ─── Supabase client ─────────────────────────────────────────────────────────
168
+ // ─── Cloud bridge client ───────────────────────────────────────────────────────
142
169
 
143
- const supabase = createClient(SUPABASE_URL, SUPABASE_ANON_KEY, {
170
+ const cloud = createClient(CLOUD_URL, CLOUD_KEY, {
144
171
  realtime: { params: { eventsPerSecond: 10 } },
145
172
  });
146
173
 
147
- // ─── OpenClaw Gateway connection ─────────────────────────────────────────────
174
+ // ─── OpenClaw Gateway connection ───────────────────────────────────────────────
148
175
 
149
176
  function connectToOpenClaw() {
150
177
  openclawReady = false;
151
178
 
179
+ if (!gatewayConnected) {
180
+ spinner.start("Connecting to agent gateway...");
181
+ }
182
+
152
183
  openclawWs = new WebSocket(`ws://localhost:${OPENCLAW_PORT}`, {
153
184
  headers: { Authorization: `Bearer ${gatewayToken}` },
154
185
  });
155
186
 
187
+ // OpenClaw uses a challenge-response handshake:
188
+ // 1. Server emits connect.challenge with a nonce
189
+ // 2. Client replies with connect req (correct params schema)
156
190
  openclawWs.on("open", () => {
157
- // Send OpenClaw handshake identifies us as an "operator" client
158
- openclawWs.send(
159
- JSON.stringify({
160
- type: "req",
161
- id: `playclaw-${Date.now()}`,
162
- method: "connect",
163
- params: {
164
- role: "operator",
165
- token: gatewayToken,
166
- client: "playclaw",
167
- version: "1.0.0",
168
- },
169
- })
170
- );
191
+ if (process.env.DEBUG) log("·", "[DEBUG] WS open waiting for challenge", c.dim);
171
192
  });
172
193
 
173
194
  openclawWs.on("message", (raw) => {
174
195
  let msg;
175
196
  try { msg = JSON.parse(raw.toString()); } catch { return; }
176
197
 
177
- // Handshake acceptedwe're in
198
+ // Step 1challenge received, send connect request
199
+ if (msg.type === "event" && msg.event === "connect.challenge") {
200
+ if (process.env.DEBUG) log("·", `[DEBUG] Challenge: nonce=${msg.payload?.nonce}`, c.dim);
201
+
202
+ openclawWs.send(JSON.stringify({
203
+ type: "req",
204
+ id: `playclaw-${Date.now()}`,
205
+ method: "connect",
206
+ params: {
207
+ minProtocol: 3,
208
+ maxProtocol: 3,
209
+ client: {
210
+ id: "cli",
211
+ version: "1.0.2",
212
+ platform: process.platform,
213
+ mode: "operator",
214
+ },
215
+ role: "operator",
216
+ scopes: ["operator.read", "operator.write"],
217
+ caps: [],
218
+ commands: [],
219
+ permissions: {},
220
+ auth: { token: gatewayToken },
221
+ locale: "en-US",
222
+ userAgent: "playclaw/1.0.2",
223
+ },
224
+ }));
225
+ return;
226
+ }
227
+
228
+ // Step 2 — handshake accepted
178
229
  if (msg.type === "res" && msg.ok && !openclawReady) {
179
230
  openclawReady = true;
180
- log("✅", `OpenClaw gateway connected (port ${OPENCLAW_PORT})`, c.green);
181
231
 
182
- // Flush messages that arrived while we were reconnecting
183
- while (pendingMessages.length > 0) {
184
- const queued = pendingMessages.shift();
185
- sendToOpenClaw(queued.content, queued.sessionId);
232
+ if (!gatewayConnected) {
233
+ gatewayConnected = true;
234
+ spinner.success(`Agent gateway connected ${c.gray}· port ${OPENCLAW_PORT}${c.reset}`);
235
+ if (!bridgeChannel) connectToBridge();
236
+ } else {
237
+ log("✓", "Gateway reconnected", c.green);
238
+ while (pendingMessages.length > 0) {
239
+ const q = pendingMessages.shift();
240
+ sendToOpenClaw(q.content, q.sessionId);
241
+ }
186
242
  }
187
-
188
- // Connect to PlayClaw via Supabase Realtime (only once)
189
- if (!bridgeChannel) connectToBridge();
190
243
  return;
191
244
  }
192
245
 
193
- // Auth failure — stop immediately
246
+ // Handshake rejected
194
247
  if (msg.type === "res" && !msg.ok) {
195
- log("❌", `OpenClaw auth failed: ${msg.error?.message || "Unknown error"}`, c.red);
196
- log(" ", "Check your gateway token and try again.", c.yellow);
197
- process.exit(1);
248
+ const reason = msg.error?.message || "invalid credentials";
249
+ if (!gatewayConnected) {
250
+ spinner.fail(`Gateway auth failed — ${reason}`);
251
+ process.exit(1);
252
+ } else {
253
+ log("✗", `Gateway auth error — ${reason}`, c.red);
254
+ }
255
+ return;
198
256
  }
199
257
 
200
- // Agent response events
258
+ // Agent events
201
259
  if (msg.type === "event") {
202
260
  handleOpenClawEvent(msg);
203
261
  }
204
262
  });
205
263
 
206
264
  openclawWs.on("error", (err) => {
207
- if (err.code === "ECONNREFUSED") {
208
- log("⚠️ ", `OpenClaw not found on port ${OPENCLAW_PORT}. Is it running?`, c.yellow);
209
- log(" ", "Start it with: openclaw gateway run", c.dim);
265
+ if (!gatewayConnected) {
266
+ if (err.code === "ECONNREFUSED") {
267
+ spinner.fail(`Gateway not found on port ${OPENCLAW_PORT} is OpenClaw running?`);
268
+ } else {
269
+ spinner.fail(`Gateway error: ${err.message}`);
270
+ }
271
+ process.exit(1);
210
272
  } else {
211
- log("", `OpenClaw error: ${err.message}`, c.red);
273
+ if (err.code === "ECONNREFUSED") {
274
+ log("⚠", `Gateway offline (port ${OPENCLAW_PORT}) — retrying in 5s`, c.yellow);
275
+ } else {
276
+ log("⚠", `Gateway error: ${err.message}`, c.yellow);
277
+ }
212
278
  }
213
279
  });
214
280
 
215
281
  openclawWs.on("close", () => {
216
282
  openclawReady = false;
217
- log("⚠️ ", "OpenClaw disconnected. Reconnecting in 5s...", c.yellow);
218
- clearTimeout(reconnectTimer);
219
- reconnectTimer = setTimeout(connectToOpenClaw, 5000);
283
+ if (gatewayConnected) {
284
+ log("⟳", "Gateway disconnected — reconnecting in 5s...", c.gray);
285
+ clearTimeout(reconnectTimer);
286
+ reconnectTimer = setTimeout(connectToOpenClaw, 5000);
287
+ }
220
288
  });
221
289
  }
222
290
 
223
- // ─── Handle events from OpenClaw ─────────────────────────────────────────────
291
+ // ─── Handle events from OpenClaw ───────────────────────────────────────────────
224
292
 
225
293
  function handleOpenClawEvent(msg) {
226
294
  const eventName = msg.event || "";
227
295
 
228
- // We listen for several possible response event names.
229
- // OpenClaw's exact event depends on version — DEBUG=1 logs all events.
296
+ if (process.env.DEBUG) {
297
+ log("·", `[DEBUG] Gateway event: ${eventName}`, c.dim);
298
+ if (process.env.DEBUG === "2") {
299
+ console.log(" payload:", JSON.stringify(msg.payload, null, 2));
300
+ }
301
+ }
302
+
230
303
  const responseEvents = [
231
- "agent.response",
232
- "message.sent",
233
- "turn.complete",
234
- "reply.sent",
235
- "assistant.message",
236
- "output.complete",
304
+ "agent.response", "message.sent", "turn.complete",
305
+ "reply.sent", "assistant.message", "output.complete",
237
306
  ];
238
307
 
239
308
  if (responseEvents.some((e) => eventName.includes(e))) {
@@ -246,18 +315,14 @@ function handleOpenClawEvent(msg) {
246
315
  JSON.stringify(msg.payload);
247
316
 
248
317
  if (content) {
249
- log("📨", `Agent responded: "${String(content).substring(0, 80)}..."`, c.cyan);
318
+ const preview = String(content);
319
+ log("←", `Agent: "${preview.length > 80 ? preview.substring(0, 80) + "…" : preview}"`, c.cyan);
250
320
  forwardResponseToPlayClaw(String(content));
251
321
  }
252
- } else if (process.env.DEBUG) {
253
- log("🔍", `[DEBUG] OpenClaw event: ${eventName}`, c.dim);
254
- if (process.env.DEBUG === "2") {
255
- console.log(" payload:", JSON.stringify(msg.payload, null, 2));
256
- }
257
322
  }
258
323
  }
259
324
 
260
- // ─── Send message to OpenClaw ─────────────────────────────────────────────────
325
+ // ─── Send message to OpenClaw ──────────────────────────────────────────────────
261
326
 
262
327
  function sendToOpenClaw(content, sessionId) {
263
328
  if (!openclawReady) {
@@ -265,8 +330,6 @@ function sendToOpenClaw(content, sessionId) {
265
330
  return;
266
331
  }
267
332
 
268
- // POST to /hooks/agent — OpenClaw's async webhook endpoint (returns 202)
269
- // The agent processes it and emits an event back on the WebSocket.
270
333
  const body = JSON.stringify({
271
334
  message: content,
272
335
  agentId: "hooks",
@@ -291,65 +354,74 @@ function sendToOpenClaw(content, sessionId) {
291
354
  },
292
355
  (res) => {
293
356
  if (res.statusCode === 202) {
294
- log("📤", `Message sent to agent (session: ${sessionId || "default"})`, c.magenta);
357
+ log("", `Forwarded to agent ${c.gray}(session: ${sessionId || "default"})${c.reset}`, c.reset);
295
358
  } else if (res.statusCode === 401) {
296
- log("", "OpenClaw auth failed — check your gateway token", c.red);
359
+ log("", "Gateway rejected — check your token", c.red);
297
360
  } else {
298
- log("⚠️ ", `Unexpected status from OpenClaw: ${res.statusCode}`, c.yellow);
361
+ log("", `Unexpected gateway response: ${res.statusCode}`, c.yellow);
299
362
  }
300
363
  }
301
364
  );
302
365
 
303
366
  req.on("error", (err) => {
304
- log("", `Failed to reach OpenClaw: ${err.message}`, c.red);
305
- forwardErrorToPlayClaw("Could not reach OpenClaw gateway. Is it running?");
367
+ log("", `Could not reach gateway: ${err.message}`, c.red);
368
+ forwardErrorToPlayClaw("Agent gateway unreachable. Is OpenClaw running?");
306
369
  });
307
370
 
308
371
  req.write(body);
309
372
  req.end();
310
373
  }
311
374
 
312
- // ─── PlayClaw Bridge via Supabase Realtime ────────────────────────────────────
375
+ // ─── PlayClaw Bridge ───────────────────────────────────────────────────────────
313
376
 
314
377
  function connectToBridge() {
315
- // Channel name = PC token → acts as a shared secret between CLI and playground
316
- bridgeChannel = supabase.channel(`bridge:${playclawToken}`, {
378
+ if (!bridgeConnected) {
379
+ spinner.start("Opening PlayClaw bridge...");
380
+ }
381
+
382
+ bridgeChannel = cloud.channel(`bridge:${playclawToken}`, {
317
383
  config: {
318
- broadcast: { ack: false }, // fire-and-forget is fine for low-latency relay
319
- presence: { key: "cli" }, // track CLI presence so playground knows we're live
384
+ broadcast: { ack: false },
385
+ presence: { key: "cli" },
320
386
  },
321
387
  });
322
388
 
323
389
  bridgeChannel
324
- // Listen for messages from the playground
325
390
  .on("broadcast", { event: "playground:message" }, ({ payload }) => {
326
- log("💬", `From playground: "${payload.content?.substring(0, 60)}"`, c.cyan);
391
+ const preview = payload.content || "";
392
+ log("↓", `Playground: "${preview.length > 60 ? preview.substring(0, 60) + "…" : preview}"`, c.magenta);
327
393
  sendToOpenClaw(payload.content, payload.session_id);
328
394
  })
329
395
  .subscribe(async (status) => {
330
396
  if (status === "SUBSCRIBED") {
331
- // Signal that the CLI is online (playground reads this via presence)
332
397
  await bridgeChannel.track({
333
398
  online_at: new Date().toISOString(),
334
399
  token: playclawToken,
335
400
  });
336
401
 
337
- log("✅", "PlayClaw Bridge connected (Supabase Realtime)", c.green);
338
- log("🎮", "Agent is ready to be tested!", c.bold);
339
- log(" ", "Open https://playclaw.info/playground to start.", c.dim);
340
- console.log("");
402
+ if (!bridgeConnected) {
403
+ bridgeConnected = true;
404
+ spinner.success("PlayClaw bridge established");
405
+ printReady();
406
+ } else {
407
+ log("✓", "Bridge reconnected", c.green);
408
+ }
341
409
 
342
410
  } else if (status === "CHANNEL_ERROR") {
343
- log("❌", "Bridge connection failed. Check your PC token.", c.red);
411
+ if (!bridgeConnected) {
412
+ spinner.fail("Bridge failed — check your PC token at playclaw.info/connect");
413
+ } else {
414
+ log("✗", "Bridge error — check your PC token", c.red);
415
+ }
344
416
 
345
417
  } else if (status === "TIMED_OUT") {
346
- log("⚠️ ", "Bridge timed out. Reconnecting in 5s...", c.yellow);
347
- supabase.removeChannel(bridgeChannel);
418
+ log("", "Bridge timed out reconnecting in 5s...", c.gray);
419
+ cloud.removeChannel(bridgeChannel);
348
420
  bridgeChannel = null;
349
421
  setTimeout(connectToBridge, 5000);
350
422
 
351
423
  } else if (status === "CLOSED") {
352
- log("⚠️ ", "Bridge disconnected. Reconnecting in 5s...", c.yellow);
424
+ log("", "Bridge closed reconnecting in 5s...", c.gray);
353
425
  bridgeChannel = null;
354
426
  setTimeout(connectToBridge, 5000);
355
427
  }
@@ -358,7 +430,7 @@ function connectToBridge() {
358
430
 
359
431
  function forwardResponseToPlayClaw(content, sessionId) {
360
432
  if (!bridgeChannel) {
361
- log("⚠️ ", "Bridge not ready — response dropped", c.yellow);
433
+ log("", "Bridge not ready — response dropped", c.yellow);
362
434
  return;
363
435
  }
364
436
  bridgeChannel.send({
@@ -377,52 +449,74 @@ function forwardErrorToPlayClaw(message) {
377
449
  });
378
450
  }
379
451
 
380
- // ─── Startup banner ───────────────────────────────────────────────────────────
452
+ // ─── Startup banner ────────────────────────────────────────────────────────────
381
453
 
382
454
  function printBanner() {
455
+ const g = c.gray, r = c.reset, b = c.bold, m = c.magenta, d = c.dim, cy = c.cyan;
456
+
457
+ // OSC 8 hyperlink — clickable in Windows Terminal, iTerm2, GNOME Terminal, etc.
458
+ const twitterLink = `\x1b]8;;https://x.com/uxKero\x1b\\${b}@KeroClow${r}\x1b]8;;\x1b\\`;
459
+
383
460
  console.log("");
384
- console.log(`${c.bold}${c.magenta} 🦞 PlayClaw Bridge${c.reset}`);
385
- console.log(`${c.dim} https://playclaw.info${c.reset}`);
461
+ console.log(` 🦞 ${b}${m}P L A Y C L A W${r}`);
462
+ console.log(` ${d}Agent Testing Playground · playclaw.info${r}`);
463
+ console.log(` ${d}by ${r}${twitterLink}${g} · ${d}x.com/uxKero${r}`);
464
+ console.log(` ${g}${"─".repeat(43)}${r}`);
386
465
  console.log("");
387
- console.log(`${c.dim} PC Token:${c.reset} ${playclawToken}`);
388
- console.log(`${c.dim} Gateway:${c.reset} localhost:${OPENCLAW_PORT}`);
389
- console.log(`${c.dim} Relay:${c.reset} Supabase Realtime`);
466
+ console.log(` ${g}Token ${r} ${b}${playclawToken}${r}`);
467
+ console.log(` ${g}Gateway${r} localhost:${OPENCLAW_PORT}`);
468
+ }
469
+
470
+ // ─── Ready message ─────────────────────────────────────────────────────────────
471
+
472
+ function printReady() {
473
+ const line = ` ${c.gray}${"─".repeat(43)}${c.reset}`;
474
+ console.log("");
475
+ console.log(line);
476
+ console.log(` 🎮 ${c.bold}Your agent is live and ready!${c.reset}`);
477
+ console.log(` ${c.gray} playclaw.info/playground${c.reset}`);
478
+ console.log(line);
390
479
  console.log("");
391
480
  }
392
481
 
482
+ // ─── Help ──────────────────────────────────────────────────────────────────────
483
+
393
484
  function printHelp() {
394
485
  console.log("");
395
- console.log(" Usage: npx playclaw <PC-TOKEN> [options]");
486
+ console.log(" 🦞 PlayClaw — Agent Testing Playground");
487
+ console.log(" playclaw.info · by @KeroClow");
488
+ console.log("");
489
+ console.log(" Usage:");
490
+ console.log(" npx playclaw <PC-TOKEN> [options]");
396
491
  console.log("");
397
492
  console.log(" Arguments:");
398
- console.log(" PC-TOKEN Your PlayClaw connection token (from playclaw.info/connect)");
493
+ console.log(" PC-TOKEN Your PlayClaw token (from playclaw.info/connect)");
399
494
  console.log("");
400
495
  console.log(" Options:");
401
496
  console.log(" --gateway-token, -g <token> OpenClaw gateway bearer token");
402
- console.log(" --port, -p <port> OpenClaw gateway port (default: 18789)");
497
+ console.log(" --port, -p <port> Gateway port (default: 18789)");
403
498
  console.log(" --help, -h Show this help");
404
499
  console.log("");
405
500
  console.log(" Examples:");
406
501
  console.log(" npx playclaw PC-XXXX-XXXX-XXXX");
407
- console.log(" npx playclaw PC-XXXX-XXXX-XXXX --gateway-token my-secret-token");
502
+ console.log(" npx playclaw PC-XXXX-XXXX-XXXX --gateway-token my-token");
408
503
  console.log(" OPENCLAW_GATEWAY_TOKEN=mytoken npx playclaw PC-XXXX-XXXX-XXXX");
409
504
  console.log("");
410
505
  console.log(" Auto-detection:");
411
- console.log(" The gateway token is auto-read from ~/.openclaw/openclaw.json");
412
- console.log(" if not provided. Run `openclaw config get gateway.auth.token`");
413
- console.log(" to find yours.");
506
+ console.log(" Gateway token is auto-read from ~/.openclaw/openclaw.json");
507
+ console.log(" Run `openclaw config get gateway.auth.token` to find yours.");
414
508
  console.log("");
415
509
  }
416
510
 
417
- // ─── Graceful shutdown ────────────────────────────────────────────────────────
511
+ // ─── Graceful shutdown ─────────────────────────────────────────────────────────
418
512
 
419
513
  async function shutdown() {
420
514
  console.log("");
421
- log("👋", "Shutting down PlayClaw bridge...", c.dim);
515
+ log("·", "Shutting down...", c.gray);
422
516
 
423
517
  if (bridgeChannel) {
424
518
  await bridgeChannel.untrack().catch(() => {});
425
- await supabase.removeChannel(bridgeChannel).catch(() => {});
519
+ await cloud.removeChannel(bridgeChannel).catch(() => {});
426
520
  }
427
521
 
428
522
  openclawWs?.close();
@@ -432,7 +526,7 @@ async function shutdown() {
432
526
  process.on("SIGINT", shutdown);
433
527
  process.on("SIGTERM", shutdown);
434
528
 
435
- // ─── Main ─────────────────────────────────────────────────────────────────────
529
+ // ─── Main ──────────────────────────────────────────────────────────────────────
436
530
 
437
531
  printBanner();
438
532
  connectToOpenClaw();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "playclaw",
3
- "version": "1.0.0",
3
+ "version": "1.0.2",
4
4
  "description": "Connect your OpenClaw agent to PlayClaw — the professional testing playground",
5
5
  "main": "index.js",
6
6
  "bin": {