grok-bot-cli 0.10.2 → 0.11.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "grok-bot-cli",
3
- "version": "0.10.2",
3
+ "version": "0.11.0",
4
4
  "description": "CLI and Agent Bundle plugin for Grok Bot agents and groups: create, update, message, inspect threads, and automate cleanup",
5
5
  "type": "module",
6
6
  "engines": {
package/dist/plugin.json CHANGED
@@ -1 +1 @@
1
- {"$schema":"https://agent-plugins.org/schemas/1.0.0/plugin.schema.json","author":{"name":"Zack Jackson","url":"https://github.com/ScriptedAlchemy"},"description":"Message Grok Bot from Codex, Claude Code, and Cursor. Codex/Claude tools use local sockets on the user's registered machines only (no remote transport); from the Grok Bot box, run gbot via Grok Bot Shell with a machineId.","homepage":"https://github.com/ScriptedAlchemy/grok-bot-cli#readme","keywords":["grok","grok-bot","grokbot","gbot","cursor","ai-agents","automation","terminal","cli","agent-bundle"],"license":"MIT","name":"gbot","repository":"https://github.com/ScriptedAlchemy/grok-bot-cli","version":"0.10.2"}
1
+ {"$schema":"https://agent-plugins.org/schemas/1.0.0/plugin.schema.json","author":{"name":"Zack Jackson","url":"https://github.com/ScriptedAlchemy"},"description":"Message Grok Bot from Codex, Claude Code, and Cursor. Codex/Claude tools use local sockets on the user's registered machines only (no remote transport); from the Grok Bot box, run gbot via Grok Bot Shell with a machineId.","homepage":"https://github.com/ScriptedAlchemy/grok-bot-cli#readme","keywords":["grok","grok-bot","grokbot","gbot","cursor","ai-agents","automation","terminal","cli","agent-bundle"],"license":"MIT","name":"gbot","repository":"https://github.com/ScriptedAlchemy/grok-bot-cli","version":"0.11.0"}
@@ -22133,8 +22133,10 @@ var __webpack_modules__ = {
22133
22133
  var node_child_process__rspack_import_5 = __webpack_require__("node:child_process");
22134
22134
  var _package_json__rspack_import_6 = __webpack_require__("./package.json");
22135
22135
  var _codex_contract_js__rspack_import_7 = __webpack_require__("./src/core/codex/contract.js");
22136
- var _desktop_shim_js__rspack_import_8 = __webpack_require__("./src/core/desktop-shim.js");
22137
- var _user_machine_guidance_js__rspack_import_9 = __webpack_require__("./src/core/user-machine-guidance.js");
22136
+ var _codex_remote_control_js__rspack_import_8 = __webpack_require__("./src/core/codex/remote-control.js");
22137
+ var _codex_thread_id_js__rspack_import_11 = __webpack_require__("./src/core/codex/thread-id.js");
22138
+ var _desktop_shim_js__rspack_import_9 = __webpack_require__("./src/core/desktop-shim.js");
22139
+ var _user_machine_guidance_js__rspack_import_10 = __webpack_require__("./src/core/user-machine-guidance.js");
22138
22140
  const PINNED_CODEX_VERSION = "0.158.0";
22139
22141
  const UPSTREAM_DESKTOP_ISSUES = [
22140
22142
  "https://github.com/openai/codex/issues/41014",
@@ -22142,8 +22144,9 @@ var __webpack_modules__ = {
22142
22144
  ];
22143
22145
  const WS_GUID = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
22144
22146
  const WS_MAX_HEADER_BYTES = 16 * 1024;
22145
- const WS_MAX_MESSAGE_BYTES = 4 * 1024 * 1024;
22146
- const WS_MAX_BUFFER_BYTES = 8 * 1024 * 1024;
22147
+ const WS_MAX_MESSAGE_BYTES = 16 * 1024 * 1024;
22148
+ const WS_MAX_BUFFER_BYTES = 32 * 1024 * 1024;
22149
+ const CODEX_SEND_MAX_BYTES = 4 * 1024 * 1024;
22147
22150
  const WS_MAX_WRITE_BYTES = 8 * 1024 * 1024;
22148
22151
  const CODEX_MAX_REQUESTS = 128;
22149
22152
  const CODEX_MAX_LISTENERS = 128;
@@ -22171,11 +22174,11 @@ var __webpack_modules__ = {
22171
22174
  function boxUnreachableMessage(path) {
22172
22175
  return [
22173
22176
  "No Codex app-server control socket at " + path + ".",
22174
- _user_machine_guidance_js__rspack_import_9.M
22177
+ _user_machine_guidance_js__rspack_import_10.M
22175
22178
  ].join("\n");
22176
22179
  }
22177
22180
  function unreachableMessage(path, desktopAttached = "unknown", env = process.env) {
22178
- if ((0, _user_machine_guidance_js__rspack_import_9.R)({
22181
+ if ((0, _user_machine_guidance_js__rspack_import_10.R)({
22179
22182
  path,
22180
22183
  env,
22181
22184
  home: env.HOME || (0, node_os__rspack_import_3.homedir)()
@@ -23014,6 +23017,13 @@ var __webpack_modules__ = {
23014
23017
  if (value == null) return null;
23015
23018
  return typeof value === "string" ? singleLine(value) : value;
23016
23019
  }
23020
+ function summarizeSection(section) {
23021
+ if (!isObject(section) || typeof section.id !== "string" || !section.id) return null;
23022
+ return {
23023
+ id: singleLine(section.id),
23024
+ name: lineField(section.name)
23025
+ };
23026
+ }
23017
23027
  function summarizeThread(t) {
23018
23028
  if (!isObject(t) || typeof t.id !== "string" || !t.id) throw new CodexProtocolError("thread/list", "entry without a string `id`");
23019
23029
  const type = isObject(t.status) && typeof t.status.type === "string" ? t.status.type : "unknown";
@@ -23025,43 +23035,72 @@ var __webpack_modules__ = {
23025
23035
  preview: typeof t.preview === "string" ? stripTerminalControls(t.preview) : "",
23026
23036
  cwd: lineField(t.cwd),
23027
23037
  source: sourceField(t.source),
23028
- updatedAt: typeof t.updatedAt === "number" ? t.updatedAt : null
23038
+ createdAt: typeof t.createdAt === "number" ? t.createdAt : null,
23039
+ updatedAt: typeof t.updatedAt === "number" ? t.updatedAt : null,
23040
+ section: summarizeSection(t.section),
23041
+ projectId: lineField(t.projectId),
23042
+ modelProvider: lineField(t.modelProvider),
23043
+ model: lineField(t.model),
23044
+ originator: lineField(t.originator)
23029
23045
  };
23030
23046
  }
23031
23047
  const THREAD_LIST_MAX_LIMIT = 200;
23032
- async function listCodexThreads({ limit = 20, cursor, env = process.env } = {}) {
23048
+ async function listCodexThreads({ limit = 20, cursor, modelProviders = [], env = process.env } = {}) {
23033
23049
  if (!Number.isInteger(limit) || limit < 1 || limit > THREAD_LIST_MAX_LIMIT) {
23034
23050
  throw new RangeError("--limit must be an integer 1-" + THREAD_LIST_MAX_LIMIT);
23035
23051
  }
23036
23052
  if (cursor !== undefined && (typeof cursor !== "string" || !cursor)) throw new RangeError("--cursor must be a non-empty string");
23053
+ if (!Array.isArray(modelProviders) || modelProviders.some((p)=>typeof p !== "string")) {
23054
+ throw new RangeError("modelProviders must be an array of strings (empty = all providers)");
23055
+ }
23037
23056
  const { client } = await openSession(env);
23038
23057
  try {
23039
- const params = {
23058
+ const baseParams = {
23040
23059
  limit,
23041
- useStateDbOnly: true,
23060
+ modelProviders,
23042
23061
  ...cursor !== undefined ? {
23043
23062
  cursor
23044
23063
  } : {}
23045
23064
  };
23046
23065
  let out;
23066
+ let usedStateDbOnly = true;
23047
23067
  try {
23048
- out = await client.request("thread/list", params);
23068
+ out = await client.request("thread/list", {
23069
+ ...baseParams,
23070
+ useStateDbOnly: true
23071
+ });
23049
23072
  } catch (err) {
23050
23073
  throw transportError(err);
23051
23074
  }
23052
23075
  if (!isObject(out) || !Array.isArray(out.data)) throw new CodexProtocolError("thread/list", "missing `data` array");
23076
+ if (out.data.length === 0 && cursor === undefined && (out.nextCursor == null || out.nextCursor === "")) {
23077
+ try {
23078
+ out = await client.request("thread/list", {
23079
+ ...baseParams,
23080
+ useStateDbOnly: false
23081
+ });
23082
+ usedStateDbOnly = false;
23083
+ } catch (err) {
23084
+ throw transportError(err);
23085
+ }
23086
+ if (!isObject(out) || !Array.isArray(out.data)) throw new CodexProtocolError("thread/list", "missing `data` array");
23087
+ }
23053
23088
  if (out.nextCursor != null && typeof out.nextCursor !== "string") throw new CodexProtocolError("thread/list", "`nextCursor` is not a string");
23054
23089
  const threads = out.data.map(summarizeThread);
23055
23090
  return {
23056
23091
  threads,
23057
23092
  nextCursor: out.nextCursor ?? null,
23058
- limit
23093
+ limit,
23094
+ useStateDbOnly: usedStateDbOnly
23059
23095
  };
23060
23096
  } finally{
23061
23097
  client.close();
23062
23098
  }
23063
23099
  }
23064
- function explainSendError(err, threadId) {
23100
+ function explainSendError(err, threadId, env = process.env) {
23101
+ if (err instanceof _codex_remote_control_js__rspack_import_8.Rg) return err;
23102
+ const mapped = (0, _codex_remote_control_js__rspack_import_8.R3)(err, threadId, env);
23103
+ if (mapped instanceof _codex_remote_control_js__rspack_import_8.Rg) return mapped;
23065
23104
  if (!(err instanceof CodexRpcError)) return err;
23066
23105
  const msg = String(err.rpc && err.rpc.message || "");
23067
23106
  if (/no rollout found|thread not found/i.test(msg)) {
@@ -23136,12 +23175,19 @@ var __webpack_modules__ = {
23136
23175
  function assertThreadAllowed(threadId, env = process.env) {
23137
23176
  const raw = env.GROK_BOT_CODEX_THREADS;
23138
23177
  if (raw == null || raw.trim() === "") return;
23139
- const allowed = raw.split(",").map((s)=>s.trim()).filter(Boolean);
23140
- if (!allowed.includes(threadId)) {
23141
- throw new CodexSendError("Codex thread " + threadId + " is not in GROK_BOT_CODEX_THREADS; the operator allows only: " + allowed.join(", "), {
23178
+ const bare = (0, _codex_thread_id_js__rspack_import_11.SX)(threadId);
23179
+ const allowed = raw.split(",").map((s)=>{
23180
+ try {
23181
+ return (0, _codex_thread_id_js__rspack_import_11.SX)(s.trim());
23182
+ } catch {
23183
+ return s.trim();
23184
+ }
23185
+ }).filter(Boolean);
23186
+ if (!allowed.includes(bare)) {
23187
+ throw new CodexSendError("Codex thread " + bare + " is not in GROK_BOT_CODEX_THREADS; the operator allows only: " + allowed.join(", "), {
23142
23188
  delivery: "rejected",
23143
23189
  reason: "route-not-allowed",
23144
- threadId
23190
+ threadId: bare
23145
23191
  });
23146
23192
  }
23147
23193
  }
@@ -23211,6 +23257,7 @@ var __webpack_modules__ = {
23211
23257
  }
23212
23258
  async function listCodexQueue(threadId, { env = process.env, limit = 50, cursor } = {}) {
23213
23259
  requireExperimental(env, "gbot codex queue");
23260
+ threadId = normalizeCodexThreadId(threadId);
23214
23261
  const { client } = await openSession(env, {
23215
23262
  experimental: true
23216
23263
  });
@@ -23241,6 +23288,64 @@ var __webpack_modules__ = {
23241
23288
  client.close();
23242
23289
  }
23243
23290
  }
23291
+ async function readCodexThread(threadId, { limit = 100, env = process.env } = {}) {
23292
+ if (!Number.isInteger(limit) || limit < 1 || limit > 2000) {
23293
+ throw new RangeError("limit must be an integer 1-2000");
23294
+ }
23295
+ const bare = normalizeCodexThreadId(threadId);
23296
+ const { client } = await openSession(env);
23297
+ try {
23298
+ let meta;
23299
+ try {
23300
+ meta = await client.request("thread/read", {
23301
+ threadId: bare
23302
+ });
23303
+ } catch (err) {
23304
+ const mapped = explainSendError(err, bare, env);
23305
+ if (mapped instanceof RemoteThreadNotLoadedError) throw mapped;
23306
+ throw transportError(err);
23307
+ }
23308
+ const turns = [];
23309
+ let cursor;
23310
+ const seen = new Set();
23311
+ for(let page = 0; page < 40 && turns.length < limit; page++){
23312
+ let out;
23313
+ try {
23314
+ out = await client.request("thread/turns/list", {
23315
+ threadId: bare,
23316
+ limit: Math.min(100, limit - turns.length),
23317
+ itemsView: "full",
23318
+ sortDirection: "desc",
23319
+ ...cursor !== undefined ? {
23320
+ cursor
23321
+ } : {}
23322
+ });
23323
+ } catch (err) {
23324
+ const mapped = explainSendError(err, bare, env);
23325
+ if (mapped instanceof RemoteThreadNotLoadedError) throw mapped;
23326
+ throw transportError(err);
23327
+ }
23328
+ if (!isObject(out) || !Array.isArray(out.data)) {
23329
+ throw new CodexProtocolError("thread/turns/list", "missing `data` array");
23330
+ }
23331
+ for (const row of out.data)turns.push(row);
23332
+ cursor = out.nextCursor ?? null;
23333
+ if (cursor === null) break;
23334
+ if (typeof cursor !== "string" || !cursor || seen.has(cursor)) {
23335
+ throw new CodexProtocolError("thread/turns/list", "invalid or repeated cursor");
23336
+ }
23337
+ seen.add(cursor);
23338
+ }
23339
+ return {
23340
+ threadId: bare,
23341
+ thread: isObject(meta) && isObject(meta.thread) ? meta.thread : null,
23342
+ turns: turns.slice(0, limit),
23343
+ nextCursor: cursor ?? null
23344
+ };
23345
+ } finally{
23346
+ client.close();
23347
+ }
23348
+ }
23244
23349
  async function sendToCodexThread(threadId, text, { env = process.env, envelope = buildEnvelope({
23245
23350
  env
23246
23351
  }), whenBusy = "reject", session, expectedTurnId, expectedCwd, model, effort, signal } = {}) {
@@ -23286,8 +23391,8 @@ var __webpack_modules__ = {
23286
23391
  ] : []
23287
23392
  ].includes(whenBusy)) throw new RangeError("--when-busy must be reject, queue, or persistent steer");
23288
23393
  if (whenBusy === "steer" && (typeof expectedTurnId !== "string" || !ID_PATTERN.test(expectedTurnId))) throw new RangeError("steer requires expectedTurnId");
23289
- if (typeof threadId !== "string" || !ID_PATTERN.test(threadId)) throw new RangeError("Invalid threadId");
23290
- if (typeof text !== "string" || !text.trim() || Buffer.byteLength(text) > WS_MAX_MESSAGE_BYTES) throw new RangeError("Message must contain text within 4 MiB");
23394
+ threadId = (0, _codex_thread_id_js__rspack_import_11.SX)(threadId);
23395
+ if (typeof text !== "string" || !text.trim() || Buffer.byteLength(text) > CODEX_SEND_MAX_BYTES) throw new RangeError("Message must contain text within 4 MiB");
23291
23396
  if (!envelope || typeof envelope.messageId !== "string" || !ID_PATTERN.test(envelope.messageId)) throw new RangeError("Invalid envelope messageId");
23292
23397
  const validated = buildEnvelope({
23293
23398
  correlationId: envelope.correlationId,
@@ -23335,8 +23440,10 @@ var __webpack_modules__ = {
23335
23440
  });
23336
23441
  } catch (err) {
23337
23442
  assertNotCancelled();
23338
- if (err instanceof CodexSendError) throw err;
23339
- throw new CodexSendError(explainSendError(err, threadId).message, {
23443
+ if (err instanceof CodexSendError || err instanceof _codex_remote_control_js__rspack_import_8.Rg) throw err;
23444
+ const explained = explainSendError(err, threadId, env);
23445
+ if (explained instanceof _codex_remote_control_js__rspack_import_8.Rg) throw explained;
23446
+ throw new CodexSendError(explained.message, {
23340
23447
  delivery: err instanceof CodexRpcError ? "rejected" : err && err.delivery || "unknown",
23341
23448
  reason: err instanceof CodexRpcError ? /no rollout found|thread not found/i.test(String(err.rpc && err.rpc.message)) ? "unknown-thread" : /active writer/i.test(String(err.rpc && err.rpc.message)) ? "external-owner" : "rejected" : "transport",
23342
23449
  threadId,
@@ -23567,7 +23674,11 @@ var __webpack_modules__ = {
23567
23674
  "messageId",
23568
23675
  "correlationId",
23569
23676
  "refused",
23570
- "hop"
23677
+ "hop",
23678
+ "hostId",
23679
+ "hostName",
23680
+ "hint",
23681
+ "code"
23571
23682
  ]){
23572
23683
  if (error[key] !== undefined) out[key] = error[key];
23573
23684
  }
@@ -23602,9 +23713,12 @@ var __webpack_modules__ = {
23602
23713
  var _contract_js__rspack_import_0 = __webpack_require__("./src/core/codex/contract.js");
23603
23714
  var node_fs__rspack_import_1 = __webpack_require__("node:fs");
23604
23715
  var _codex_bridge_js__rspack_import_2 = __webpack_require__("./src/core/codex-bridge.js");
23716
+ var _remote_control_js__rspack_import_3 = __webpack_require__("./src/core/codex/remote-control.js");
23717
+ var _thread_id_js__rspack_import_4 = __webpack_require__("./src/core/codex/thread-id.js");
23605
23718
  const ID = /^[A-Za-z0-9_.:-]{1,128}$/;
23606
23719
  const BUDGET = 4 * 1024 * 1024;
23607
23720
  function conversationId(value, name = 'threadId') {
23721
+ if (name === 'threadId') return (0, _thread_id_js__rspack_import_4.SX)(value);
23608
23722
  if (typeof value !== 'string' || !ID.test(value)) throw new RangeError(`${name} must be 1-128 identifier characters`);
23609
23723
  return value;
23610
23724
  }
@@ -23729,7 +23843,8 @@ var __webpack_modules__ = {
23729
23843
  if (expected !== undefined && (typeof cwd !== 'string' || (0, node_fs__rspack_import_1.realpathSync)(cwd) !== expected)) throw new Error('Codex thread cwd does not match expectedCwd');
23730
23844
  } catch (error) {
23731
23845
  await close();
23732
- throw error;
23846
+ if (error instanceof _remote_control_js__rspack_import_3.Rg) throw error;
23847
+ throw (0, _remote_control_js__rspack_import_3.R3)(error, threadId, env);
23733
23848
  }
23734
23849
  async function wait({ turnId, messageId, afterMessageId, timeoutMs = 120000, signal: waitSignal, maxOutputBytes = 1048576 }) {
23735
23850
  conversationId(turnId, 'turnId');
@@ -24080,6 +24195,264 @@ var __webpack_modules__ = {
24080
24195
  j: ()=>openCodexConversation
24081
24196
  });
24082
24197
  },
24198
+ "./src/core/codex/remote-control.js" (__unused_rspack___webpack_module__, __webpack_exports__, __webpack_require__) {
24199
+ var node_fs__rspack_import_0 = __webpack_require__("node:fs");
24200
+ var node_os__rspack_import_1 = __webpack_require__("node:os");
24201
+ var node_path__rspack_import_2 = __webpack_require__("node:path");
24202
+ var _thread_id_js__rspack_import_3 = __webpack_require__("./src/core/codex/thread-id.js");
24203
+ const REMOTE_SUMMARY_KEY = /^remote-thread-summaries-v3:(.+)$/;
24204
+ class RemoteThreadNotLoadedError1 extends Error {
24205
+ constructor(threadId, hostId = null, hostName = null){
24206
+ const hostLabel = hostName ? `${JSON.stringify(hostName)} (${JSON.stringify(hostId)})` : hostId ? JSON.stringify(hostId) : null;
24207
+ const owner = hostLabel ? `owned by remote-control host ${hostLabel}` : 'owned by a remote-control host (hostId unknown; check ~/.codex/.codex-global-state.json)';
24208
+ const hint = hostId ? `Read this thread via host ${JSON.stringify(hostId)}'s app-server` + (hostName ? ` (${hostName})` : '') + '. SSH remoting is not implemented in this adapter.' : "Read this thread via the owning host's app-server. SSH remoting is not implemented in this adapter.";
24209
+ super(`Codex thread ${JSON.stringify(threadId)} is not loaded on the local app-server; ${owner}. ${hint}`);
24210
+ this.name = 'RemoteThreadNotLoadedError';
24211
+ this.code = 'REMOTE_THREAD_NOT_LOADED';
24212
+ this.delivery = 'rejected';
24213
+ this.reason = 'remote-thread-not-loaded';
24214
+ this.threadId = threadId;
24215
+ this.hostId = hostId;
24216
+ this.hostName = hostName;
24217
+ this.hint = hint;
24218
+ }
24219
+ }
24220
+ function codexGlobalStatePath(env = process.env) {
24221
+ const home = env.CODEX_HOME || (0, node_path__rspack_import_2.join)((0, node_os__rspack_import_1.homedir)(), '.codex');
24222
+ return (0, node_path__rspack_import_2.join)(home, '.codex-global-state.json');
24223
+ }
24224
+ function loadCodexGlobalState(env = process.env) {
24225
+ let raw;
24226
+ try {
24227
+ raw = (0, node_fs__rspack_import_0.readFileSync)(codexGlobalStatePath(env), 'utf8');
24228
+ } catch {
24229
+ return null;
24230
+ }
24231
+ try {
24232
+ const data = JSON.parse(raw);
24233
+ if (!data || typeof data !== 'object' || Array.isArray(data)) return null;
24234
+ return data;
24235
+ } catch {
24236
+ return null;
24237
+ }
24238
+ }
24239
+ function codexStateEntries(data) {
24240
+ const nested = data?.['electron-persisted-atom-state'];
24241
+ return [
24242
+ nested,
24243
+ data
24244
+ ].filter((value)=>value && typeof value === 'object' && !Array.isArray(value));
24245
+ }
24246
+ function findRemoteThreadHost(threadId, env = process.env) {
24247
+ let bare;
24248
+ try {
24249
+ bare = (0, _thread_id_js__rspack_import_3.SX)(threadId);
24250
+ } catch {
24251
+ bare = typeof threadId === 'string' && threadId.startsWith("local:") ? threadId.slice("local:".length) : threadId;
24252
+ }
24253
+ if (!bare) return null;
24254
+ const data = loadCodexGlobalState(env);
24255
+ if (!data) return null;
24256
+ const hostNames = collectHostNames(data);
24257
+ for (const [key, value] of codexStateEntries(data).flatMap(Object.entries)){
24258
+ const match = REMOTE_SUMMARY_KEY.exec(key);
24259
+ if (!match?.[1]) continue;
24260
+ const hostId = match[1];
24261
+ const hostName = hostNames.get(hostId) ?? extractHostName(value) ?? null;
24262
+ for (const entry of extractThreadEntries(value)){
24263
+ const id = threadIdFromEntry(entry);
24264
+ if (!id) continue;
24265
+ const entryBare = id.startsWith("local:") ? id.slice("local:".length) : id;
24266
+ if (entryBare === bare) return {
24267
+ hostId,
24268
+ hostName
24269
+ };
24270
+ }
24271
+ }
24272
+ return null;
24273
+ }
24274
+ function mapRemoteThreadError(error, threadId, env = process.env) {
24275
+ const message = errorMessage(error);
24276
+ if (!/thread not loaded|not loaded|no rollout found|thread not found|invalid thread id/i.test(message)) {
24277
+ return error instanceof Error ? error : new Error(message || String(error));
24278
+ }
24279
+ const remote = findRemoteThreadHost(threadId, env);
24280
+ if (remote) {
24281
+ return new RemoteThreadNotLoadedError1(threadId, remote.hostId, remote.hostName);
24282
+ }
24283
+ if (/thread not loaded|not loaded/i.test(message)) {
24284
+ return new RemoteThreadNotLoadedError1(threadId, null, null);
24285
+ }
24286
+ return error instanceof Error ? error : new Error(message || String(error));
24287
+ }
24288
+ function collectHostNames(data) {
24289
+ const names = new Map();
24290
+ for (const [key, value] of codexStateEntries(data).flatMap(Object.entries)){
24291
+ if (!/remote-host|hosts|host-meta|host_directory|environments/i.test(key)) continue;
24292
+ if (!value || typeof value !== 'object') continue;
24293
+ if (Array.isArray(value)) {
24294
+ for (const entry of value){
24295
+ const pair = hostNameFromEntry(entry);
24296
+ if (pair && !names.has(pair.hostId)) names.set(pair.hostId, pair.hostName);
24297
+ }
24298
+ continue;
24299
+ }
24300
+ for (const [hostId, entry] of Object.entries(value)){
24301
+ const fromEntry = hostNameFromEntry(entry);
24302
+ if (fromEntry) {
24303
+ if (!names.has(fromEntry.hostId)) names.set(fromEntry.hostId, fromEntry.hostName);
24304
+ continue;
24305
+ }
24306
+ const label = stringField(entry, [
24307
+ 'name',
24308
+ 'hostName',
24309
+ 'displayName',
24310
+ 'envName',
24311
+ 'label',
24312
+ 'title'
24313
+ ]);
24314
+ if (label && !names.has(hostId)) names.set(hostId, label);
24315
+ }
24316
+ }
24317
+ return names;
24318
+ }
24319
+ function hostNameFromEntry(entry) {
24320
+ if (!entry || typeof entry !== 'object' || Array.isArray(entry)) return null;
24321
+ const hostId = stringField(entry, [
24322
+ 'hostId',
24323
+ 'id',
24324
+ 'host_id',
24325
+ 'uuid'
24326
+ ]);
24327
+ const hostName = stringField(entry, [
24328
+ 'name',
24329
+ 'hostName',
24330
+ 'displayName',
24331
+ 'envName',
24332
+ 'label',
24333
+ 'title'
24334
+ ]);
24335
+ if (!hostId || !hostName) return null;
24336
+ return {
24337
+ hostId,
24338
+ hostName
24339
+ };
24340
+ }
24341
+ function extractHostName(value) {
24342
+ if (!value || typeof value !== 'object' || Array.isArray(value)) return null;
24343
+ return stringField(value, [
24344
+ 'hostName',
24345
+ 'displayName',
24346
+ 'name',
24347
+ 'envName',
24348
+ 'label',
24349
+ 'title',
24350
+ 'friendlyName'
24351
+ ]);
24352
+ }
24353
+ function extractThreadEntries(value) {
24354
+ if (Array.isArray(value)) return value;
24355
+ if (!value || typeof value !== 'object') return [];
24356
+ const record = value;
24357
+ for (const key of [
24358
+ 'threads',
24359
+ 'summaries',
24360
+ 'items',
24361
+ 'data',
24362
+ 'conversations'
24363
+ ]){
24364
+ if (Array.isArray(record[key])) return record[key];
24365
+ }
24366
+ const entries = [];
24367
+ for (const [key, entry] of Object.entries(record)){
24368
+ if ([
24369
+ 'pinnedIds',
24370
+ 'pinned',
24371
+ 'pinnedThreadIds',
24372
+ 'hostName',
24373
+ 'displayName',
24374
+ 'name',
24375
+ 'envName',
24376
+ 'label',
24377
+ 'title',
24378
+ 'friendlyName',
24379
+ 'hostId',
24380
+ 'version'
24381
+ ].includes(key)) continue;
24382
+ if (typeof entry === 'string') continue;
24383
+ if (entry && typeof entry === 'object' && !Array.isArray(entry)) {
24384
+ const id = stringField(entry, [
24385
+ 'id',
24386
+ 'threadId',
24387
+ 'thread_id',
24388
+ 'conversationId'
24389
+ ]);
24390
+ entries.push(id ? entry : {
24391
+ ...entry,
24392
+ id: key
24393
+ });
24394
+ }
24395
+ }
24396
+ return entries;
24397
+ }
24398
+ function threadIdFromEntry(entry) {
24399
+ if (typeof entry === 'string' && entry) return entry;
24400
+ if (!entry || typeof entry !== 'object' || Array.isArray(entry)) return null;
24401
+ return stringField(entry, [
24402
+ 'id',
24403
+ 'threadId',
24404
+ 'thread_id',
24405
+ 'conversationId'
24406
+ ]);
24407
+ }
24408
+ function stringField(row, keys) {
24409
+ if (!row || typeof row !== 'object' || Array.isArray(row)) return null;
24410
+ for (const key of keys){
24411
+ const value = row[key];
24412
+ if (typeof value === 'string' && value.trim()) return value.trim();
24413
+ }
24414
+ return null;
24415
+ }
24416
+ function errorMessage(error) {
24417
+ if (!error) return '';
24418
+ if (typeof error === 'string') return error;
24419
+ if (error instanceof Error) return error.message;
24420
+ if (typeof error === 'object' && error && 'message' in error) return String(error.message);
24421
+ if (typeof error === 'object' && error && 'rpc' in error) {
24422
+ const rpc = error.rpc;
24423
+ if (rpc && typeof rpc.message === 'string') return rpc.message;
24424
+ }
24425
+ return String(error);
24426
+ }
24427
+ __webpack_require__.d(__webpack_exports__, {
24428
+ R3: ()=>mapRemoteThreadError,
24429
+ Rg: ()=>RemoteThreadNotLoadedError1
24430
+ });
24431
+ },
24432
+ "./src/core/codex/thread-id.js" (__unused_rspack___webpack_module__, __webpack_exports__, __webpack_require__) {
24433
+ const LOCAL_THREAD_ID_PREFIX = 'local:';
24434
+ const TEMP_THREAD_ID_PREFIX = 'local:client-new-thread:';
24435
+ const ID_PATTERN = /^[A-Za-z0-9_.:-]{1,128}$/;
24436
+ function isTemporaryDesktopThreadId(threadId) {
24437
+ return typeof threadId === 'string' && (threadId.startsWith(TEMP_THREAD_ID_PREFIX) || threadId.startsWith('client-new-thread:'));
24438
+ }
24439
+ function normalizeCodexThreadId1(threadId) {
24440
+ if (typeof threadId !== 'string' || !threadId) {
24441
+ throw new RangeError('Invalid threadId');
24442
+ }
24443
+ if (isTemporaryDesktopThreadId(threadId)) {
24444
+ throw new RangeError('Temporary Desktop thread id ' + JSON.stringify(threadId) + ' has no app-server mapping; wait for the real conversation id');
24445
+ }
24446
+ const bare = threadId.startsWith(LOCAL_THREAD_ID_PREFIX) ? threadId.slice(LOCAL_THREAD_ID_PREFIX.length) : threadId;
24447
+ if (!bare || !ID_PATTERN.test(bare)) {
24448
+ throw new RangeError('Invalid threadId');
24449
+ }
24450
+ return bare;
24451
+ }
24452
+ __webpack_require__.d(__webpack_exports__, {
24453
+ SX: ()=>normalizeCodexThreadId1
24454
+ });
24455
+ },
24083
24456
  "./src/core/desktop-shim.js" (__unused_rspack___webpack_module__, __unused_rspack___webpack_exports__, __webpack_require__) {
24084
24457
  var node_child_process__rspack_import_0 = __webpack_require__("node:child_process");
24085
24458
  var node_fs__rspack_import_1 = __webpack_require__("node:fs");
@@ -27537,7 +27910,7 @@ ts() { date -u +%Y-%m-%dT%H:%M:%SZ; }
27537
27910
  },
27538
27911
  "./package.json" (module) {
27539
27912
  module.exports = {
27540
- "rE": "0.10.2"
27913
+ "rE": "0.11.0"
27541
27914
  };
27542
27915
  }
27543
27916
  };
@@ -0,0 +1,75 @@
1
+ ---
2
+ name: chatgpt-desktop
3
+ description: Use the gbot ChatGPT Desktop MCP tools to discover hosts and threads, page/search/read conversations, and send or wait for replies on the user's Mac. Use for local Desktop CDP automation and remote-control thread routing; do not use for ordinary Grok Bot messaging.
4
+ ---
5
+ # ChatGPT Desktop through gbot
6
+
7
+ Use the `chatgpt_desktop_*` MCP tools on the user's Mac. The CLI is a secondary
8
+ surface. CDP listens on `127.0.0.1` only; these tools do not transport CDP to
9
+ another machine.
10
+
11
+ ## Find the right conversation
12
+
13
+ 1. Call `chatgpt_desktop_status`. `reachable: false` or `exitCode: 1` means
14
+ CDP is down: open/send/wait cannot work. `appServerFallback.reachable` is a
15
+ separate list/search/read probe. Do not retry a send because CDP disappeared.
16
+ 2. Call `chatgpt_desktop_list_hosts` for dynamic host IDs, labels, and
17
+ `modelProvider` values. `local` is this Mac. A remote-control host ID looks
18
+ like `remote-control:env_e_...`; its `hostName` may be `null`. Do not infer a
19
+ host name from a project label. Managed SSH hosts may have no threads.
20
+ 3. Use `chatgpt_desktop_list_threads` with `host`, `modelProvider`, or `project`
21
+ as needed. Pass `nextCursor` back as `cursor` with the same filters until it
22
+ is `null`. The cursor refers to a sorted merged inventory; pages normally
23
+ fill `limit` and can stop earlier at the MCP document cap. If the inventory
24
+ changed after a cursor expired, restart without one. Project labels and IDs
25
+ come from Desktop project assignments and workspace roots; `projectRootPath`
26
+ is the resolved root when available.
27
+ `chatgpt_desktop_search_threads` also accepts `cursor`; keep its query and
28
+ filters unchanged. Search matches the full title even when the display row
29
+ has a 200-character `title` and `titleTruncated: true`.
30
+ 4. Read with `chatgpt_desktop_read_thread`. `full: false` gets the recent tail;
31
+ `full: true` pages chronologically from the start. Pass `nextCursor` back
32
+ as `cursor` with the same thread ID and `full` value until `complete: true`.
33
+ The byte budget can stop before `limit`. An oversized turn is returned as
34
+ ordered fragments with one stable `turnKey`; use `continuation.field`,
35
+ `offsetChars`, and `totalChars` to reassemble `text`, `userText`, and
36
+ `assistantText`. `textTruncated: true` signals a fragment, not missing text.
37
+ Check `complete` and `warnings` before treating a page as full history.
38
+
39
+ Durable local IDs are `local:<conversationId>` (a bare conversation ID also
40
+ works). `local:client-new-thread:*` is temporary and works only while that
41
+ conversation remains selected. `REMOTE_THREAD_NOT_LOADED` includes the
42
+ owning `hostId`: read or send through gbot running on that host's own Desktop
43
+ or app-server. The local Mac cannot fetch that remote history through CDP.
44
+
45
+ ## Open, send, and wait
46
+
47
+ - `chatgpt_desktop_open_thread` navigates to a durable conversation even if it
48
+ is absent from the sidebar. An archived route returns `archived: true`; the
49
+ thread remains readable through `chatgpt_desktop_read_thread`.
50
+ - `chatgpt_desktop_send` with `threadId` sends to that conversation. Omit
51
+ `threadId` and `project` to start outside a project. Omit `threadId` and set
52
+ `project` to start inside that project. The tool verifies the empty new-chat
53
+ view and its project selection before typing. Send once. A new-thread receipt may
54
+ include `temporaryThreadId` and resolves `threadId` to the durable
55
+ `local:<conversationId>` before returning. Keep both until the durable ID is
56
+ confirmed; use the durable ID for later work.
57
+ - Then call `chatgpt_desktop_wait_reply` with the returned `threadId` (or the
58
+ selected temporary ID). It returns the reply and durable `conversationId`.
59
+ A timeout is an observation, not permission to resend.
60
+
61
+ `ARCHIVED_THREAD` means send was rejected before submission. Read remains
62
+ available. Pass `unarchive: true` with an existing `threadId` on send only if
63
+ the user wants the archive state changed; this explicitly calls app-server
64
+ `thread/unarchive` and retries once. Never unarchive merely to read or open.
65
+
66
+ `COMPOSER_HAS_DRAFT` means the user's draft would be disturbed; leave it for
67
+ the operator. `PROJECT_UNAVAILABLE` means Desktop disabled a project's new-chat
68
+ action, often because its configured workspace root no longer exists; correct
69
+ the project root in Desktop before retrying. `NEW_CHAT_NAVIGATION_FAILED` means
70
+ the button did not open the requested empty view within three seconds; inspect
71
+ Desktop before retrying. `CDP_UNREACHABLE` requires restoring the local Desktop CDP
72
+ endpoint. App-server failures, including oversized frames, appear as errors or
73
+ read `warnings` with `complete: false`; inspect the partial result and source
74
+ status before continuing. A send with unknown delivery must not be repeated
75
+ without checking the selected thread and reply state.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "grok-bot-cli",
3
- "version": "0.10.2",
3
+ "version": "0.11.0",
4
4
  "description": "CLI and Agent Bundle plugin for Grok Bot agents and groups: create, update, message, inspect threads, and automate cleanup",
5
5
  "type": "module",
6
6
  "bin": {