clawborrator-cli 0.0.22 → 0.0.24

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-bundled/claw.cjs +161 -60
  2. package/package.json +1 -1
@@ -6844,7 +6844,7 @@ var whoamiCmd = new Command("whoami").description("show the currently authentica
6844
6844
  });
6845
6845
 
6846
6846
  // src/commands/session-attach.ts
6847
- var import_node_readline = require("node:readline");
6847
+ var import_node_readline2 = require("node:readline");
6848
6848
 
6849
6849
  // ../node_modules/ws/wrapper.mjs
6850
6850
  var import_stream = __toESM(require_stream(), 1);
@@ -6857,13 +6857,65 @@ var import_websocket = __toESM(require_websocket(), 1);
6857
6857
  var import_websocket_server = __toESM(require_websocket_server(), 1);
6858
6858
  var wrapper_default = import_websocket.default;
6859
6859
 
6860
- // src/commands/session-attach.ts
6860
+ // src/util/disambiguate.ts
6861
+ var import_node_readline = require("node:readline");
6861
6862
  var RESET = "\x1B[0m";
6862
6863
  var DIM = "\x1B[2m";
6864
+ var BOLD = "\x1B[1m";
6865
+ var AmbiguousError = class extends Error {
6866
+ constructor(candidates, input) {
6867
+ super(`ambiguous reference '${input}' \u2014 multiple online sessions match`);
6868
+ this.candidates = candidates;
6869
+ this.input = input;
6870
+ }
6871
+ code = "CLW_AMBIGUOUS";
6872
+ };
6873
+ async function pickCandidate(input, candidates) {
6874
+ if (candidates.length === 1) return candidates[0];
6875
+ const live = candidates.filter((c) => c.connected);
6876
+ if (live.length <= 1) {
6877
+ return live[0] ?? candidates[0];
6878
+ }
6879
+ if (!process.stdin.isTTY || !process.stderr.isTTY) {
6880
+ throw new AmbiguousError(live, input);
6881
+ }
6882
+ process.stderr.write(`${BOLD}'${input}' is ambiguous \u2014 pick a session:${RESET}
6883
+ `);
6884
+ for (let i = 0; i < live.length; i++) {
6885
+ const c = live[i];
6886
+ const qualified = c.routingName ? `@${c.startedByLogin}/${c.routingName.replace(/^@/, "")}` : `(no routing name)`;
6887
+ const cwd = c.cwd ? ` ${DIM}${c.cwd}${RESET}` : "";
6888
+ const host = c.host ? ` ${DIM}${c.host}${RESET}` : "";
6889
+ process.stderr.write(` ${BOLD}${i + 1}${RESET}. ${qualified}${host}${cwd}
6890
+ `);
6891
+ process.stderr.write(` ${DIM}id ${c.id}${RESET}
6892
+ `);
6893
+ }
6894
+ process.stderr.write(` ${BOLD}q${RESET}. cancel
6895
+ `);
6896
+ const rl = (0, import_node_readline.createInterface)({ input: process.stdin, output: process.stderr });
6897
+ const answer = await new Promise((resolve3) => {
6898
+ rl.question(`pick [1-${live.length}]: `, resolve3);
6899
+ });
6900
+ rl.close();
6901
+ const trimmed = answer.trim().toLowerCase();
6902
+ if (trimmed === "q" || trimmed === "quit" || trimmed === "") {
6903
+ throw new Error("cancelled");
6904
+ }
6905
+ const idx = parseInt(trimmed, 10);
6906
+ if (!Number.isInteger(idx) || idx < 1 || idx > live.length) {
6907
+ throw new Error(`invalid selection '${answer}'`);
6908
+ }
6909
+ return live[idx - 1];
6910
+ }
6911
+
6912
+ // src/commands/session-attach.ts
6913
+ var RESET2 = "\x1B[0m";
6914
+ var DIM2 = "\x1B[2m";
6863
6915
  var AMBER = "\x1B[33m";
6864
6916
  var BLUE = "\x1B[36m";
6865
6917
  var RED = "\x1B[31m";
6866
- var BOLD = "\x1B[1m";
6918
+ var BOLD2 = "\x1B[1m";
6867
6919
  var GREEN = "\x1B[32m";
6868
6920
  function ts() {
6869
6921
  return (/* @__PURE__ */ new Date()).toLocaleTimeString();
@@ -6894,15 +6946,32 @@ var sessionAttach = new Command("attach").description("open a TUI on a session \
6894
6946
  slug = needle;
6895
6947
  }
6896
6948
  const data = await api.get("/api/v1/sessions");
6897
- const match = data.items.find(
6898
- (s) => s.routingName === slug && (ownerLogin === null || s.startedByLogin === ownerLogin)
6899
- );
6900
- if (!match) {
6949
+ let candidates = data.items.filter((s) => s.routingName === slug);
6950
+ if (ownerLogin !== null) {
6951
+ candidates = candidates.filter((s) => s.startedByLogin === ownerLogin);
6952
+ } else {
6953
+ const mine = candidates.filter((s) => s.role === "owner");
6954
+ if (mine.length > 0) candidates = mine;
6955
+ }
6956
+ if (candidates.length === 0) {
6901
6957
  const label = ownerLogin ? `@${ownerLogin}/${slug.slice(1)}` : slug;
6902
6958
  console.error(`error: no session with routing name ${label} (run \`claw session list\` to see what's available)`);
6903
6959
  process.exit(2);
6904
6960
  }
6905
- sessionId = match.id;
6961
+ try {
6962
+ const picked = await pickCandidate(ref, candidates);
6963
+ sessionId = picked.id;
6964
+ } catch (e) {
6965
+ if (e instanceof AmbiguousError) {
6966
+ console.error(`error: '${ref}' is ambiguous \u2014 multiple online sessions match. Re-run with a UUID or qualified form (e.g. @owner/slug). Candidates:`);
6967
+ for (const c of e.candidates) {
6968
+ console.error(` ${c.id} @${c.startedByLogin}/${(c.routingName ?? "").replace(/^@/, "")} ${c.cwd ?? ""}`);
6969
+ }
6970
+ process.exit(2);
6971
+ }
6972
+ console.error(`error: ${e?.message ?? String(e)}`);
6973
+ process.exit(2);
6974
+ }
6906
6975
  }
6907
6976
  const wsUrl = cfg.hubUrl.replace(/^http/i, "ws") + "/cli";
6908
6977
  const ws = new wrapper_default(wsUrl, {
@@ -6911,7 +6980,7 @@ var sessionAttach = new Command("attach").description("open a TUI on a session \
6911
6980
  let mySubscription = false;
6912
6981
  const pendingPerms = [];
6913
6982
  ws.on("open", () => {
6914
- console.log(`${DIM}[${ts()}]${RESET} connected to ${cfg.hubUrl}`);
6983
+ console.log(`${DIM2}[${ts()}]${RESET2} connected to ${cfg.hubUrl}`);
6915
6984
  const sub = { type: "subscribe", sessionId };
6916
6985
  ws.send(JSON.stringify(sub));
6917
6986
  });
@@ -6931,22 +7000,22 @@ var sessionAttach = new Command("attach").description("open a TUI on a session \
6931
7000
  printInbound(msg, myLogin);
6932
7001
  if (msg.type === "subscribed") {
6933
7002
  mySubscription = true;
6934
- console.log(`${DIM}attached as ${BOLD}${msg.role}${RESET}${DIM}. type for prompt \xB7 @other <text> to route \xB7 /m <text> for op-msg \xB7 /y /n on permissions \xB7 /q to quit${RESET}`);
7003
+ console.log(`${DIM2}attached as ${BOLD2}${msg.role}${RESET2}${DIM2}. type for prompt \xB7 @other <text> to route \xB7 /m <text> for op-msg \xB7 /y /n on permissions \xB7 /q to quit${RESET2}`);
6935
7004
  }
6936
7005
  });
6937
7006
  ws.on("close", (code, reason) => {
6938
- console.log(`${DIM}[${ts()}] disconnected (${code}${reason ? ": " + reason.toString() : ""})${RESET}`);
7007
+ console.log(`${DIM2}[${ts()}] disconnected (${code}${reason ? ": " + reason.toString() : ""})${RESET2}`);
6939
7008
  process.exit(0);
6940
7009
  });
6941
7010
  ws.on("error", (err) => {
6942
- console.error(`${RED}ws error: ${err.message}${RESET}`);
7011
+ console.error(`${RED}ws error: ${err.message}${RESET2}`);
6943
7012
  });
6944
- const rl = (0, import_node_readline.createInterface)({ input: process.stdin, terminal: false });
7013
+ const rl = (0, import_node_readline2.createInterface)({ input: process.stdin, terminal: false });
6945
7014
  rl.on("line", (raw) => {
6946
7015
  const text = raw.trim();
6947
7016
  if (!text) return;
6948
7017
  if (!mySubscription) {
6949
- console.log(`${DIM}(not subscribed yet \u2014 waiting...)${RESET}`);
7018
+ console.log(`${DIM2}(not subscribed yet \u2014 waiting...)${RESET2}`);
6950
7019
  return;
6951
7020
  }
6952
7021
  if (text === "/q" || text === "/quit") {
@@ -6956,7 +7025,7 @@ var sessionAttach = new Command("attach").description("open a TUI on a session \
6956
7025
  if (text === "/y" || text === "/yes" || text === "/n" || text === "/no") {
6957
7026
  const pending = pendingPerms[pendingPerms.length - 1];
6958
7027
  if (!pending) {
6959
- console.log(`${DIM}(no pending permission to act on)${RESET}`);
7028
+ console.log(`${DIM2}(no pending permission to act on)${RESET2}`);
6960
7029
  return;
6961
7030
  }
6962
7031
  const decision = text === "/y" || text === "/yes" ? "allow" : "deny";
@@ -6973,7 +7042,7 @@ var sessionAttach = new Command("attach").description("open a TUI on a session \
6973
7042
  if (text === "/m" || text.startsWith("/m ")) {
6974
7043
  const opText = text.slice(2).trim();
6975
7044
  if (!opText) {
6976
- console.log(`${DIM}usage: /m <text> (sends as op-message; bare text is a prompt)${RESET}`);
7045
+ console.log(`${DIM2}usage: /m <text> (sends as op-message; bare text is a prompt)${RESET2}`);
6977
7046
  return;
6978
7047
  }
6979
7048
  const out2 = { type: "op_message", sessionId, text: opText };
@@ -6983,16 +7052,16 @@ var sessionAttach = new Command("attach").description("open a TUI on a session \
6983
7052
  if (text === "/p" || text.startsWith("/p ")) {
6984
7053
  const promptText = text.slice(2).trim();
6985
7054
  if (!promptText) {
6986
- console.log(`${DIM}usage: /p <text> (or just type \u2014 bare text is a prompt now)${RESET}`);
7055
+ console.log(`${DIM2}usage: /p <text> (or just type \u2014 bare text is a prompt now)${RESET2}`);
6987
7056
  return;
6988
7057
  }
6989
7058
  const out2 = { type: "prompt", sessionId, text: promptText };
6990
7059
  ws.send(JSON.stringify(out2));
6991
- console.log(`${DIM}[${ts()}]${RESET} ${AMBER}\u2192 prompt sent${RESET} ${promptText}`);
7060
+ console.log(`${DIM2}[${ts()}]${RESET2} ${AMBER}\u2192 prompt sent${RESET2} ${promptText}`);
6992
7061
  return;
6993
7062
  }
6994
7063
  if (text.startsWith("/")) {
6995
- console.log(`${DIM}unknown slash-command: ${text} (try /m /y /n /q)${RESET}`);
7064
+ console.log(`${DIM2}unknown slash-command: ${text} (try /m /y /n /q)${RESET2}`);
6996
7065
  return;
6997
7066
  }
6998
7067
  const xMatch = /^(@[A-Za-z0-9._\-/]+)\s+([\s\S]+)$/.exec(text);
@@ -7000,7 +7069,7 @@ var sessionAttach = new Command("attach").description("open a TUI on a session \
7000
7069
  const targetRef = xMatch[1];
7001
7070
  const promptText = xMatch[2].trim();
7002
7071
  if (!promptText) {
7003
- console.log(`${DIM}usage: ${targetRef} <prompt>${RESET}`);
7072
+ console.log(`${DIM2}usage: ${targetRef} <prompt>${RESET2}`);
7004
7073
  return;
7005
7074
  }
7006
7075
  (async () => {
@@ -7016,13 +7085,26 @@ var sessionAttach = new Command("attach").description("open a TUI on a session \
7016
7085
  }
7017
7086
  try {
7018
7087
  const data = await api.get("/api/v1/sessions");
7019
- const match = data.items.find(
7020
- (s) => s.routingName === slug && (ownerLogin === null || s.startedByLogin === ownerLogin)
7021
- );
7022
- if (!match) {
7023
- console.error(`${RED}error: no session ${targetRef} (try \`claw session list\` in another terminal)${RESET}`);
7088
+ let candidates = data.items.filter((s) => s.routingName === slug);
7089
+ if (ownerLogin !== null) {
7090
+ candidates = candidates.filter((s) => s.startedByLogin === ownerLogin);
7091
+ } else {
7092
+ const mine = candidates.filter((s) => s.role === "owner");
7093
+ if (mine.length > 0) candidates = mine;
7094
+ }
7095
+ if (candidates.length === 0) {
7096
+ console.error(`${RED}error: no session ${targetRef} (try \`claw session list\` in another terminal)${RESET2}`);
7097
+ return;
7098
+ }
7099
+ const liveMatches = candidates.filter((c) => c.connected);
7100
+ if (liveMatches.length > 1) {
7101
+ console.error(`${RED}error: '${targetRef}' is ambiguous \u2014 multiple online matches. Use the qualified form:${RESET2}`);
7102
+ for (const c of liveMatches) {
7103
+ console.error(` ${DIM2}${c.id.slice(0, 8)}\u2026${RESET2} @${c.startedByLogin}/${(c.routingName ?? "").replace(/^@/, "")} ${DIM2}${c.cwd ?? ""}${RESET2}`);
7104
+ }
7024
7105
  return;
7025
7106
  }
7107
+ const match = candidates[0];
7026
7108
  const out2 = {
7027
7109
  type: "prompt",
7028
7110
  sessionId: match.id,
@@ -7034,16 +7116,16 @@ var sessionAttach = new Command("attach").description("open a TUI on a session \
7034
7116
  sourceSessionId: sessionId
7035
7117
  };
7036
7118
  ws.send(JSON.stringify(out2));
7037
- console.log(`${DIM}[${ts()}]${RESET} ${AMBER}\u2192 prompt \u2192 ${targetRef}${RESET} ${promptText}`);
7119
+ console.log(`${DIM2}[${ts()}]${RESET2} ${AMBER}\u2192 prompt \u2192 ${targetRef}${RESET2} ${promptText}`);
7038
7120
  } catch (e) {
7039
- console.error(`${RED}error: ${e?.message ?? String(e)}${RESET}`);
7121
+ console.error(`${RED}error: ${e?.message ?? String(e)}${RESET2}`);
7040
7122
  }
7041
7123
  })();
7042
7124
  return;
7043
7125
  }
7044
7126
  const out = { type: "prompt", sessionId, text };
7045
7127
  ws.send(JSON.stringify(out));
7046
- console.log(`${DIM}[${ts()}]${RESET} ${AMBER}\u2192 prompt${RESET} ${text}`);
7128
+ console.log(`${DIM2}[${ts()}]${RESET2} ${AMBER}\u2192 prompt${RESET2} ${text}`);
7047
7129
  });
7048
7130
  process.on("SIGINT", () => {
7049
7131
  ws.close(1e3, "sigint");
@@ -7058,26 +7140,26 @@ function printInbound(msg, myLogin) {
7058
7140
  break;
7059
7141
  }
7060
7142
  case "op_message": {
7061
- console.log(`${DIM}[${shortTs(msg.ts)}]${RESET} ${GREEN}@${msg.authorLogin}${RESET} ${msg.text}`);
7143
+ console.log(`${DIM2}[${shortTs(msg.ts)}]${RESET2} ${GREEN}@${msg.authorLogin}${RESET2} ${msg.text}`);
7062
7144
  break;
7063
7145
  }
7064
7146
  case "permission_request": {
7065
- console.log(`${RED}[!] ${shortTs(msg.ts)}${RESET} approval needed: ${BOLD}${msg.tool}${RESET} \u2014 ${msg.inputPreview}`);
7147
+ console.log(`${RED}[!] ${shortTs(msg.ts)}${RESET2} approval needed: ${BOLD2}${msg.tool}${RESET2} \u2014 ${msg.inputPreview}`);
7066
7148
  break;
7067
7149
  }
7068
7150
  case "permission_resolved": {
7069
- const dec = msg.decision === "allow" ? `${GREEN}allowed${RESET}` : msg.decision === "deny" ? `${RED}denied${RESET}` : `${DIM}expired${RESET}`;
7070
- console.log(`${DIM}[${ts()}]${RESET} permission ${msg.requestId} ${dec} by @${msg.resolverLogin ?? "?"}`);
7151
+ const dec = msg.decision === "allow" ? `${GREEN}allowed${RESET2}` : msg.decision === "deny" ? `${RED}denied${RESET2}` : `${DIM2}expired${RESET2}`;
7152
+ console.log(`${DIM2}[${ts()}]${RESET2} permission ${msg.requestId} ${dec} by @${msg.resolverLogin ?? "?"}`);
7071
7153
  break;
7072
7154
  }
7073
7155
  case "presence": {
7074
- console.log(`${DIM}[${ts()}] presence: ${msg.attached.map((l) => "@" + l).join(", ") || "(empty)"}${RESET}`);
7156
+ console.log(`${DIM2}[${ts()}] presence: ${msg.attached.map((l) => "@" + l).join(", ") || "(empty)"}${RESET2}`);
7075
7157
  break;
7076
7158
  }
7077
7159
  case "ack":
7078
7160
  break;
7079
7161
  case "error":
7080
- console.error(`${RED}error (${msg.code}): ${msg.message}${RESET}`);
7162
+ console.error(`${RED}error (${msg.code}): ${msg.message}${RESET2}`);
7081
7163
  break;
7082
7164
  }
7083
7165
  }
@@ -7100,58 +7182,58 @@ function renderEvent(ev, myLogin) {
7100
7182
  const text = String(p.text ?? p.prompt ?? "").trim() || JSON.stringify(p).slice(0, 200);
7101
7183
  const source = String(p.source ?? "cli");
7102
7184
  if (source === "operator" && myLogin && p.authorLogin === myLogin) return;
7103
- const label = source === "operator" ? `${BOLD}@${String(p.authorLogin ?? "remote")} \u203A${RESET}` : `${BOLD}(cli) \u203A${RESET}`;
7104
- console.log(`${DIM}[${ts2}]${RESET} ${label} ${text}`);
7185
+ const label = source === "operator" ? `${BOLD2}@${String(p.authorLogin ?? "remote")} \u203A${RESET2}` : `${BOLD2}(cli) \u203A${RESET2}`;
7186
+ console.log(`${DIM2}[${ts2}]${RESET2} ${label} ${text}`);
7105
7187
  return;
7106
7188
  }
7107
7189
  if (ev.type === "assistant_text") {
7108
7190
  const text = String(p.text ?? "").trim();
7109
7191
  const isPlaceholder = !!p.placeholder;
7110
- const tag = isPlaceholder ? `${DIM}claude \xB7 thinking${RESET}` : `${AMBER}claude${RESET}`;
7111
- console.log(`${DIM}[${ts2}]${RESET} ${tag} ${isPlaceholder ? DIM + text + RESET : text}`);
7192
+ const tag = isPlaceholder ? `${DIM2}claude \xB7 thinking${RESET2}` : `${AMBER}claude${RESET2}`;
7193
+ console.log(`${DIM2}[${ts2}]${RESET2} ${tag} ${isPlaceholder ? DIM2 + text + RESET2 : text}`);
7112
7194
  return;
7113
7195
  }
7114
7196
  if (ev.type === "reply") {
7115
7197
  const text = String(p.text ?? "").trim();
7116
7198
  if (p.source === "peer-reply" && p.peerLogin) {
7117
- console.log(`${DIM}[${ts2}]${RESET} ${AMBER}${String(p.peerLogin)} answered${RESET} ${text}`);
7199
+ console.log(`${DIM2}[${ts2}]${RESET2} ${AMBER}${String(p.peerLogin)} answered${RESET2} ${text}`);
7118
7200
  return;
7119
7201
  }
7120
- const tag = p.chatId ? `${AMBER}claude${RESET} ${DIM}(reply to ${String(p.chatId).slice(0, 8)})${RESET}` : `${AMBER}claude${RESET}`;
7121
- console.log(`${DIM}[${ts2}]${RESET} ${tag} ${text}`);
7202
+ const tag = p.chatId ? `${AMBER}claude${RESET2} ${DIM2}(reply to ${String(p.chatId).slice(0, 8)})${RESET2}` : `${AMBER}claude${RESET2}`;
7203
+ console.log(`${DIM2}[${ts2}]${RESET2} ${tag} ${text}`);
7122
7204
  return;
7123
7205
  }
7124
- console.log(`${DIM}[${ts2}]${RESET} ${AMBER}[chat:${ev.type}]${RESET} ${previewPayload(p)}`);
7206
+ console.log(`${DIM2}[${ts2}]${RESET2} ${AMBER}[chat:${ev.type}]${RESET2} ${previewPayload(p)}`);
7125
7207
  return;
7126
7208
  }
7127
7209
  switch (ev.type) {
7128
7210
  case "PreToolUse": {
7129
7211
  const tool = String(p.tool_name ?? p.toolName ?? p.tool ?? "?");
7130
7212
  const input = renderToolInput(p);
7131
- console.log(`${DIM}[${ts2}]${RESET} ${BLUE}\u2192 ${tool}${RESET} ${DIM}${input}${RESET}`);
7213
+ console.log(`${DIM2}[${ts2}]${RESET2} ${BLUE}\u2192 ${tool}${RESET2} ${DIM2}${input}${RESET2}`);
7132
7214
  return;
7133
7215
  }
7134
7216
  case "PostToolUse": {
7135
7217
  const tool = String(p.tool_name ?? p.toolName ?? p.tool ?? "?");
7136
7218
  const out = stringifyToolResponse(p.tool_response ?? p.toolResponse ?? p.outputPreview ?? p.output);
7137
- const ok = p.ok === false ? `${RED}\u2717${RESET}` : `${BLUE}\u2713${RESET}`;
7138
- console.log(`${DIM}[${ts2}]${RESET} ${ok} ${tool}${out ? " " + DIM + truncate(out, 200) + RESET : ""}`);
7219
+ const ok = p.ok === false ? `${RED}\u2717${RESET2}` : `${BLUE}\u2713${RESET2}`;
7220
+ console.log(`${DIM2}[${ts2}]${RESET2} ${ok} ${tool}${out ? " " + DIM2 + truncate(out, 200) + RESET2 : ""}`);
7139
7221
  return;
7140
7222
  }
7141
7223
  case "PostToolUseFailure": {
7142
7224
  const tool = String(p.tool_name ?? p.toolName ?? p.tool ?? "?");
7143
7225
  const err = stringifyToolResponse(p.error ?? p.message ?? p.tool_response);
7144
- console.log(`${DIM}[${ts2}]${RESET} ${RED}\u2717 ${tool}${RESET} ${RED}${truncate(err, 200)}${RESET}`);
7226
+ console.log(`${DIM2}[${ts2}]${RESET2} ${RED}\u2717 ${tool}${RESET2} ${RED}${truncate(err, 200)}${RESET2}`);
7145
7227
  return;
7146
7228
  }
7147
7229
  case "Stop":
7148
- console.log(`${DIM}[${ts2}] \u2014 turn end \u2014${RESET}`);
7230
+ console.log(`${DIM2}[${ts2}] \u2014 turn end \u2014${RESET2}`);
7149
7231
  return;
7150
7232
  case "SessionStart":
7151
- console.log(`${DIM}[${ts2}] \u25B8 session start${RESET}`);
7233
+ console.log(`${DIM2}[${ts2}] \u25B8 session start${RESET2}`);
7152
7234
  return;
7153
7235
  case "SessionEnd":
7154
- console.log(`${DIM}[${ts2}] \u25C2 session end${RESET}`);
7236
+ console.log(`${DIM2}[${ts2}] \u25C2 session end${RESET2}`);
7155
7237
  return;
7156
7238
  case "TaskCreated":
7157
7239
  case "SubagentStart":
@@ -7159,16 +7241,16 @@ function renderEvent(ev, myLogin) {
7159
7241
  case "TaskCompleted": {
7160
7242
  const which = ev.type;
7161
7243
  const desc = String(p.description ?? p.agentType ?? "");
7162
- console.log(`${DIM}[${ts2}] ${BLUE}\u21AA ${which}${RESET}${desc ? " " + desc : ""}`);
7244
+ console.log(`${DIM2}[${ts2}] ${BLUE}\u21AA ${which}${RESET2}${desc ? " " + desc : ""}`);
7163
7245
  return;
7164
7246
  }
7165
7247
  case "Notification": {
7166
7248
  const msg = String(p.message ?? p.text ?? "").trim();
7167
- console.log(`${DIM}[${ts2}]${RESET} ${BOLD}\u{1F514}${RESET} ${msg || JSON.stringify(p).slice(0, 200)}`);
7249
+ console.log(`${DIM2}[${ts2}]${RESET2} ${BOLD2}\u{1F514}${RESET2} ${msg || JSON.stringify(p).slice(0, 200)}`);
7168
7250
  return;
7169
7251
  }
7170
7252
  default:
7171
- console.log(`${DIM}[${ts2}]${RESET} ${BLUE}[${ev.type}]${RESET} ${previewPayload(p)}`);
7253
+ console.log(`${DIM2}[${ts2}]${RESET2} ${BLUE}[${ev.type}]${RESET2} ${previewPayload(p)}`);
7172
7254
  }
7173
7255
  }
7174
7256
  function stringifyToolResponse(v) {
@@ -7235,17 +7317,34 @@ async function resolveSessionId(idOrName) {
7235
7317
  slug = needle;
7236
7318
  }
7237
7319
  const data = await api.get("/api/v1/sessions");
7238
- const match = data.items.find(
7239
- (s) => s.routingName === slug && (ownerLogin === null || s.startedByLogin === ownerLogin)
7240
- );
7241
- if (!match) {
7320
+ let candidates = data.items.filter((s) => s.routingName === slug);
7321
+ if (ownerLogin !== null) {
7322
+ candidates = candidates.filter((s) => s.startedByLogin === ownerLogin);
7323
+ } else {
7324
+ const mine = candidates.filter((s) => s.role === "owner");
7325
+ if (mine.length > 0) candidates = mine;
7326
+ }
7327
+ if (candidates.length === 0) {
7242
7328
  const err = new Error(
7243
7329
  ownerLogin ? `no session @${ownerLogin}/${slug.slice(1)} (run \`claw session list\`)` : `no session with routing name ${slug} (run \`claw session list\`)`
7244
7330
  );
7245
7331
  err.code = "CLW_NO_ROUTING_MATCH";
7246
7332
  throw err;
7247
7333
  }
7248
- return match.id;
7334
+ try {
7335
+ const picked = await pickCandidate(idOrName, candidates);
7336
+ return picked.id;
7337
+ } catch (e) {
7338
+ if (e instanceof AmbiguousError) {
7339
+ const err = new Error(
7340
+ `ambiguous reference '${idOrName}' \u2014 multiple online sessions match. Re-run with a UUID or qualified form. Candidates:
7341
+ ` + e.candidates.map((c) => ` ${c.id} @${c.startedByLogin}/${(c.routingName ?? "").replace(/^@/, "")} ${c.cwd ?? ""}`).join("\n")
7342
+ );
7343
+ err.code = "CLW_AMBIGUOUS";
7344
+ throw err;
7345
+ }
7346
+ throw e;
7347
+ }
7249
7348
  }
7250
7349
  var sessionList = new Command("list").alias("ls").description("list sessions you can see").option("--connected", "only sessions whose channel WS is currently open").option("--all", "include archived sessions").action(async (opts) => {
7251
7350
  const qs = new URLSearchParams();
@@ -7564,7 +7663,7 @@ function fmtAgo2(iso) {
7564
7663
  }
7565
7664
 
7566
7665
  // src/commands/peers.ts
7567
- var peersCmd = new Command("peers").description("list your sessions reachable for cross-session routing").action(async () => {
7666
+ var peersCmd = new Command("peers").description("list your sessions reachable for cross-session routing \u2014 own + shared").action(async () => {
7568
7667
  const data = await api.get("/api/v1/peers");
7569
7668
  if (data.peers.length === 0) {
7570
7669
  console.log("no peers (no sessions registered yet)");
@@ -7573,7 +7672,9 @@ var peersCmd = new Command("peers").description("list your sessions reachable fo
7573
7672
  for (const p of data.peers) {
7574
7673
  const dot = p.online ? "\u25CF" : "\u25CB";
7575
7674
  const where = p.cwd ? ` ${p.cwd}` : "";
7576
- console.log(`${dot} ${p.routingName.padEnd(20)} ${p.online ? "online" : "offline"}${where}`);
7675
+ const qualifiedName = `@${p.ownerLogin}/${p.routingName.replace(/^@/, "")}`;
7676
+ const tag = p.mine ? "" : " (shared)";
7677
+ console.log(`${dot} ${qualifiedName.padEnd(28)} ${p.online ? "online " : "offline"}${tag}${where}`);
7577
7678
  }
7578
7679
  });
7579
7680
  var routeCmd = new Command("route").description("send a one-shot prompt to a peer session; ask mode (default) blocks for the reply, tell mode is fire-and-forget").argument("<peer>", "routingName (e.g. @foo, foo, @owner/foo)").argument("<prompt>", "text to send (quote it to keep spaces)").option("--mode <mode>", "ask | tell", "ask").action(async (peer, prompt, opts) => {
@@ -7662,7 +7763,7 @@ var webhookCmd = new Command("webhook").description("manage webhook subscription
7662
7763
 
7663
7764
  // src/index.ts
7664
7765
  var program2 = new Command();
7665
- program2.name("claw").description("clawborrator CLI \u2014 control your Claude Code sessions from the terminal").version("0.0.22");
7766
+ program2.name("claw").description("clawborrator CLI \u2014 control your Claude Code sessions from the terminal").version("0.0.24");
7666
7767
  program2.addCommand(loginCmd);
7667
7768
  program2.addCommand(logoutCmd);
7668
7769
  program2.addCommand(whoamiCmd);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "clawborrator-cli",
3
- "version": "0.0.22",
3
+ "version": "0.0.24",
4
4
  "type": "module",
5
5
  "description": "claw — command-line client for clawborrator hub_v1. Manages PATs, channel tokens, sessions, cross-session routing, and webhooks; ships an inline TUI for live multi-operator session attach.",
6
6
  "license": "MIT",