openzoo 0.50.19 → 0.50.21

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/bin/openzoo.js CHANGED
@@ -89,6 +89,10 @@ const HELP = `openzoo — local x402-paying proxy + MCP server for openzoo.fun
89
89
  usage:
90
90
  npx openzoo start the proxy: http://localhost:8402/v1 (keyless) PLUS a
91
91
  public HTTPS url for cloud IDEs (key required, printed at start)
92
+ npx openzoo bot Grok Bot.app on the zoo. Starts :8402 + aiserver on
93
+ :8443, then launches the app with
94
+ CURSOR_API_BASE_URL=https://127.0.0.1:8443
95
+ (no sudo, no /etc/hosts). Leave it running.
92
96
  npx openzoo cursor [dir] start proxy+tunnel, write MCP config + every model
93
97
  into the picker, and LAUNCH Cursor on [dir]
94
98
  (defaults to the current directory; ~ works;
@@ -164,6 +168,9 @@ async function main() {
164
168
  case 'start':
165
169
  await (await import('../lib/proxy.js')).startProxy({ autoTunnel: true });
166
170
  break;
171
+ case 'bot':
172
+ await (await import('../lib/grokcli.js')).runBot(process.argv.slice(3));
173
+ break;
167
174
  case 'editor':
168
175
  case 'cursor':
169
176
  case 'vscode':
package/lib/cursorapi.js CHANGED
@@ -177,6 +177,11 @@ export function encodeForMethod(method, models) {
177
177
  return encodeEnsureSandBox(pod);
178
178
  } catch { return null; }
179
179
  }
180
+ case 'GetGrokBotSendStatus':
181
+ // Empty body decodes status=UNSPECIFIED → UI "Failed to send".
182
+ // ACCEPTED=2 (aiserver.v1.GrokBotSendStatus). echo_entry_id (field 2)
183
+ // must echo the request's message_id or the overlay never reconciles.
184
+ return encodeGetGrokBotSendStatus();
180
185
  case 'GetPlanInfo': return encodeGetPlanInfo();
181
186
  case 'GetMe': return encodeGetMe();
182
187
  case 'GetDefaultModel': return encodeGetDefaultModel(models);
@@ -204,10 +209,80 @@ export function encodeForMethod(method, models) {
204
209
  * those. This encoder is the redirect; whether the box satisfies the protocol
205
210
  * is a separate, unfinished problem — point it at a logging box first.
206
211
  */
207
- export function encodeEnsureSandBox({ region = 'us1', accountId, podId, token, agent, vnc, p1340, p6081 }) {
212
+ /** GetGrokBotSendStatusResponse { status=ACCEPTED, echo_entry_id, accepted_at_ms } */
213
+ export function encodeGetGrokBotSendStatus(echoId = `oz-${Date.now()}`) {
214
+ return new Buf()
215
+ .int(1, 2) // GROK_BOT_SEND_STATUS_ACCEPTED
216
+ .str(2, echoId)
217
+ .int(4, Date.now())
218
+ .done();
219
+ }
220
+
221
+ /** Strip Connect-RPC 5-byte envelope if present (flags=0, big-endian length). */
222
+ export function unwrapConnect(buf) {
223
+ const b = Buffer.isBuffer(buf) ? buf : Buffer.from(buf || []);
224
+ if (b.length >= 5 && (b[0] === 0 || b[0] === 2)) {
225
+ const n = b.readUInt32BE(1);
226
+ if (n > 0 && 5 + n <= b.length) return b.subarray(5, 5 + n);
227
+ }
228
+ return b;
229
+ }
230
+
231
+ /** Bare protobuf → {fieldNumber: string|number}. Enough for GetGrokBotSendStatusRequest
232
+ * {1 agent_id, 2 message_id} (measured body=76b = two uuid strings). */
233
+ export function decodeProtoFields(buf) {
234
+ const b = unwrapConnect(buf);
235
+ const out = {};
236
+ let i = 0;
237
+ while (i < b.length) {
238
+ let key = 0;
239
+ let shift = 0;
240
+ while (i < b.length) {
241
+ const v = b[i++];
242
+ key |= (v & 0x7f) << shift;
243
+ if (!(v & 0x80)) break;
244
+ shift += 7;
245
+ if (shift > 28) return out;
246
+ }
247
+ const field = key >>> 3;
248
+ const wire = key & 7;
249
+ if (wire === 0) {
250
+ let n = 0;
251
+ shift = 0;
252
+ while (i < b.length) {
253
+ const v = b[i++];
254
+ n |= (v & 0x7f) << shift;
255
+ if (!(v & 0x80)) break;
256
+ shift += 7;
257
+ }
258
+ out[field] = n;
259
+ } else if (wire === 2) {
260
+ let len = 0;
261
+ shift = 0;
262
+ while (i < b.length) {
263
+ const v = b[i++];
264
+ len |= (v & 0x7f) << shift;
265
+ if (!(v & 0x80)) break;
266
+ shift += 7;
267
+ }
268
+ if (i + len > b.length) break;
269
+ out[field] = b.subarray(i, i + len).toString('utf8');
270
+ i += len;
271
+ } else {
272
+ break;
273
+ }
274
+ }
275
+ return out;
276
+ }
277
+
278
+ export function encodeEnsureSandBox({ region = 'us1', accountId, podId, token, accessToken, agent, vnc, vncPath, p1340, p6081 }) {
208
279
  // per-port URLs — field 6 is the agent (1337), field 7 the VNC desktop
209
280
  // (6080), matching the live cursorvm response. RunPod fronts each port at
210
281
  // https://<id>-<port>.proxy.runpod.net, so agent !== vnc.
282
+ // Field 4 = network_token (nto-…); field 11 = agent HTTP bearer. Mixing
283
+ // them 401s /api/* on the real pod (measured sniff 2026-08-29).
284
+ const auth = accessToken || token;
285
+ const vncUrl = vncPath || `${vnc}/vnc.html?network_token=${token}&resume_lower_s=900&resume_upper_s=18000&path=websockify%3Fnetwork_token%3D${token}`;
211
286
  return new Buf()
212
287
  .str(1, region)
213
288
  .str(2, accountId)
@@ -215,11 +290,11 @@ export function encodeEnsureSandBox({ region = 'us1', accountId, podId, token, a
215
290
  .str(4, token)
216
291
  .str(5, 'local')
217
292
  .str(6, agent)
218
- .str(7, `${vnc}/vnc.html?network_token=${token}&resume_lower_s=900&resume_upper_s=18000&path=websockify%3Fnetwork_token%3D${token}`)
293
+ .str(7, vncUrl)
219
294
  .str(8, '/workspace/terminals')
220
295
  .int(9, 1)
221
296
  .str(10, p1340 || agent)
222
- .str(11, token)
297
+ .str(11, auth)
223
298
  .str(12, p6081 || vnc)
224
299
  .done();
225
300
  }