callwalkietalkie 0.9.0 → 0.9.1

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.
@@ -193,7 +193,7 @@ async function ensureMachineKey(site) {
193
193
  if (/^cwt_[a-f0-9]{48,128}$/i.test(existing)) return existing;
194
194
  }
195
195
 
196
- process.stdout.write(" Minting machine key… ");
196
+ process.stdout.write(" Starting server… ");
197
197
  let res;
198
198
  try {
199
199
  res = await fetch(`${site}/v1/keys`, {
package/package.json CHANGED
@@ -1,11 +1,11 @@
1
1
  {
2
2
  "name": "callwalkietalkie",
3
- "version": "0.9.0",
3
+ "version": "0.9.1",
4
4
  "description": "Chat into cmux from your phone. Run npx callwalkietalkie.",
5
5
  "license": "MIT",
6
6
  "repository": {
7
7
  "type": "git",
8
- "url": "git+https://github.com/nbened/longleash.git",
8
+ "url": "git+https://github.com/nbened/callwalkietalkie.git",
9
9
  "directory": "cli"
10
10
  },
11
11
  "homepage": "https://callwalkietalkie.com",
@@ -4,7 +4,7 @@
4
4
  <meta charset="utf-8">
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, viewport-fit=cover">
6
6
  <meta name="apple-mobile-web-app-capable" content="yes">
7
- <title>Sessions — Call Walkie Talkie</title>
7
+ <title>Sessions — WalkieTalkie</title>
8
8
  <style>
9
9
  :root {
10
10
  --bg: #ffffff;
@@ -135,6 +135,9 @@
135
135
  color: inherit;
136
136
  cursor: pointer;
137
137
  }
138
+ .session-icon { width: 24px; height: 24px; flex: none; margin-top: 3px; color: var(--muted); }
139
+ .sess.active .session-icon { color: var(--sent); }
140
+ .session-preview { display: block; margin-top: 4px; color: var(--muted); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
138
141
  .sess.active { background: #f7fbff; }
139
142
  .sess:active { background: var(--ground); }
140
143
  .sess .dot {
@@ -576,7 +579,7 @@
576
579
  </div>
577
580
  <div id="sess-list"></div>
578
581
  <div id="sessions-foot">
579
- <span class="hint">Terminals chat · browsers live-stream · <button id="restore-sessions" type="button">Restore</button></span>
582
+ <span class="hint">Terminals &amp; agent chats · live web previews · <button id="restore-sessions" type="button">Restore</button></span>
580
583
  <button id="leave" type="button">Disconnect</button>
581
584
  </div>
582
585
  </div>
@@ -649,6 +652,8 @@
649
652
  var holdStop = false;
650
653
  var sawBusy = false;
651
654
  var sendAt = 0;
655
+ var clearUntil = 0;
656
+ var clearTimers = [];
652
657
  var poller = null;
653
658
  var leaving = false;
654
659
  var showCmds = true;
@@ -700,15 +705,42 @@
700
705
  });
701
706
  }
702
707
 
703
- function clearComposer() {
704
- msg.value = "";
708
+ function wipeComposer() {
709
+ if (msg.value !== "") msg.value = "";
705
710
  msg.style.height = "auto";
711
+ }
712
+
713
+ function clearComposer() {
714
+ // Soft keyboards (esp. iOS enterkeyhint=send) often re-commit the draft
715
+ // after we clear. Wipe now and again for a short window.
716
+ clearTimers.forEach(function(id) { clearTimeout(id); });
717
+ clearTimers = [];
718
+ clearUntil = Date.now() + 400;
719
+ wipeComposer();
720
+ try { msg.setAttribute("readonly", "readonly"); } catch (e) {}
721
+ function finishUnlock() {
722
+ try { msg.removeAttribute("readonly"); } catch (e) {}
723
+ wipeComposer();
724
+ }
725
+ [0, 16, 50, 120, 250].forEach(function(ms) {
726
+ clearTimers.push(setTimeout(function() {
727
+ wipeComposer();
728
+ if (ms >= 250) finishUnlock();
729
+ }, ms));
730
+ });
706
731
  requestAnimationFrame(function() {
707
- msg.value = "";
708
- msg.dispatchEvent(new Event("input", { bubbles: true }));
732
+ wipeComposer();
733
+ requestAnimationFrame(wipeComposer);
709
734
  });
710
735
  }
711
736
 
737
+ function isReinjectedDraft() {
738
+ if (!pendingUser) return false;
739
+ if (!(sending || Date.now() < clearUntil)) return false;
740
+ var cur = normText((msg.value || "").replace(/\r?\n/g, ""));
741
+ return !!cur && cur === normText(pendingUser);
742
+ }
743
+
712
744
  function esc(s) {
713
745
  return String(s).replace(/[&<>"']/g, function(c) {
714
746
  return ({"&":"&amp;","<":"&lt;",">":"&gt;","\"":"&quot;","'":"&#39;"})[c];
@@ -990,7 +1022,7 @@
990
1022
  var html = "";
991
1023
  var expanded = expandMessages(known);
992
1024
  if (!expanded.length && !pendingUser) {
993
- html = '<div class="turn system"><div class="bubble">Say something to the agent.</div></div>';
1025
+ html = '<div class="turn system"><div class="bubble">' + (agentName(titleText.textContent) ? 'Start a conversation. Send your first message below.' : 'Your terminal is open. Send a command or text below.') + '</div></div>';
994
1026
  }
995
1027
  expanded.forEach(function(m) {
996
1028
  var role = m.role;
@@ -1022,6 +1054,19 @@
1022
1054
  sessionsMeta.className = cls || "";
1023
1055
  }
1024
1056
 
1057
+ var sessionActivity = {};
1058
+ function agentName(title) {
1059
+ var match = String(title || "").match(/\b(claude|codex|aider|opencode|gemini)\b/i);
1060
+ return match ? match[1] : "";
1061
+ }
1062
+ function sessionIcon(kind) {
1063
+ var paths = kind === "browser"
1064
+ ? '<circle cx="12" cy="12" r="9"/><ellipse cx="12" cy="12" rx="4" ry="9"/><path d="M3 12h18"/>'
1065
+ : kind === "chat"
1066
+ ? '<path d="M21 11a8 8 0 0 1-8 8H6l-4 3V11a9 9 0 0 1 19 0Z"/><path d="M7 10h10M7 14h6"/>'
1067
+ : '<rect x="2" y="4" width="20" height="16" rx="3"/><path d="m6 9 3 3-3 3m7 0h5"/>';
1068
+ return '<svg class="session-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">' + paths + '</svg>';
1069
+ }
1025
1070
  function renderPicker(sessions, currentSurface) {
1026
1071
  lastSessions = sessions || [];
1027
1072
  activeKey = currentSurface;
@@ -1041,15 +1086,22 @@
1041
1086
  var workspace = s.workspace || s.workspace_id || "";
1042
1087
  var kind = s.type || "terminal";
1043
1088
  var isBrowser = kind === "browser";
1089
+ var agent = !isBrowser && agentName(s.title);
1090
+ var activity = sessionActivity[surface];
1044
1091
  var title = esc(s.title || surface || (isBrowser ? "browser" : "session"));
1045
1092
  var path = isBrowser
1046
1093
  ? esc(shortUrl(s.url || ""))
1047
1094
  : esc(shortPath(s.cwd || s.current_directory || ""));
1048
1095
  var key = esc(sessionKey(s));
1049
1096
  var active = surface === currentSurface;
1050
- var snip = isBrowser
1051
- ? (active ? "Live stream" : "Browser · tap for live view")
1052
- : (active ? "Open in this chat" : "Terminal · tap to chat");
1097
+ var state = isBrowser ? (s.url ? "Live preview available" : "No page open")
1098
+ : agent ? (!activity ? "Open to check conversation" : activity.started ? "Chat started" : "Ready to chat")
1099
+ : "Open terminal";
1100
+ if (agent && activity && Date.now() - activity.at < 4000) {
1101
+ state = activity.busy ? "Working" : activity.started ? "Ready for your next message" : "Ready to chat";
1102
+ }
1103
+ var snip = esc(state);
1104
+ if (agent && activity && activity.preview) snip += '<span class="session-preview">' + esc(activity.preview) + '</span>';
1053
1105
  return '<div class="sess-row" data-key="' + key + '">' +
1054
1106
  '<button type="button" class="sess-delete">Delete</button>' +
1055
1107
  '<button type="button" class="sess' + (active ? " active" : "") + (isBrowser ? " browser" : "") + '"' +
@@ -1059,15 +1111,15 @@
1059
1111
  ' data-title="' + title + '"' +
1060
1112
  ' data-type="' + esc(kind) + '"' +
1061
1113
  ' data-url="' + esc(s.url || "") + '">' +
1062
- '<span class="dot"></span>' +
1114
+ sessionIcon(isBrowser ? 'browser' : agent ? 'chat' : 'terminal') +
1063
1115
  '<span class="body">' +
1064
- '<span class="top"><span class="name">' + title + '</span><span class="kind">' + (isBrowser ? "Browser" : "Terminal") + "</span></span>" +
1116
+ '<span class="top"><span class="name">' + title + '</span><span class="kind">' + (isBrowser ? "HTTP / Web" : agent ? "Agent chat" : "Terminal") + "</span></span>" +
1065
1117
  '<span class="snip">' + snip + "</span>" +
1066
1118
  (path ? '<span class="path">' + path + "</span>" : "") +
1067
1119
  "</span></button></div>";
1068
1120
  }
1069
1121
  sessList.innerHTML = [
1070
- { type: "browser", title: "Browsers" },
1122
+ { type: "browser", title: "Web pages" },
1071
1123
  { type: "terminal", title: "Terminals" }
1072
1124
  ].map(function(group) {
1073
1125
  var items = visible.filter(function(s) {
@@ -1222,8 +1274,11 @@
1222
1274
  }
1223
1275
  updateComposerMode();
1224
1276
  var msgs = j.messages || [];
1277
+ var chatMessages = msgs.filter(function(m) { return m.kind !== "command" && (m.role === "user" || m.role === "assistant"); });
1278
+ sessionActivity[activeSurface] = { started: chatMessages.length > 0, busy: !!j.busy, at: Date.now(), preview: chatMessages.length ? String(chatMessages[chatMessages.length - 1].text || "").slice(0, 120) : "" };
1279
+ msg.placeholder = agentName(titleText.textContent) ? "Message the agent…" : "Send a command or text…";
1225
1280
  var sig = msgs.map(function(m) { return (m.kind||"") + ":" + m.role + ":" + m.text; }).join("\x1e");
1226
- if (sig !== lastSig || pendingUser || agentBusy) {
1281
+ if (sig !== lastSig || pendingUser || agentBusy || !log.innerHTML) {
1227
1282
  lastSig = sig;
1228
1283
  render(msgs);
1229
1284
  }
@@ -1240,7 +1295,12 @@
1240
1295
  async function send() {
1241
1296
  var text = (msg.value || "").trim();
1242
1297
  // Allow queueing a follow-up while the agent is busy; only block on in-flight send/stop.
1243
- if (!text || sending || interrupting) return;
1298
+ if (!text || interrupting) return;
1299
+ if (sending) {
1300
+ // Keyboard re-inserted the just-sent draft; don't start another send — just clear.
1301
+ if (isReinjectedDraft()) clearComposer();
1302
+ return;
1303
+ }
1244
1304
  sending = true;
1245
1305
  sendBtn.disabled = true;
1246
1306
  pendingUser = text;
@@ -1261,6 +1321,8 @@
1261
1321
  setStatus(j.error || "send failed", "err");
1262
1322
  pendingUser = null;
1263
1323
  holdStop = false;
1324
+ clearUntil = 0;
1325
+ try { msg.removeAttribute("readonly"); } catch (e) {}
1264
1326
  msg.value = text;
1265
1327
  render(known);
1266
1328
  } else {
@@ -1271,6 +1333,8 @@
1271
1333
  setStatus("send failed", "err");
1272
1334
  pendingUser = null;
1273
1335
  holdStop = false;
1336
+ clearUntil = 0;
1337
+ try { msg.removeAttribute("readonly"); } catch (e2) {}
1274
1338
  msg.value = text;
1275
1339
  render(known);
1276
1340
  }
@@ -1355,6 +1419,11 @@
1355
1419
  send();
1356
1420
  });
1357
1421
  msg.addEventListener("input", function(e) {
1422
+ if (isReinjectedDraft()) {
1423
+ wipeComposer();
1424
+ updateComposerMode();
1425
+ return;
1426
+ }
1358
1427
  msg.style.height = "auto";
1359
1428
  msg.style.height = Math.min(msg.scrollHeight, 120) + "px";
1360
1429
  updateComposerMode();
@@ -411,13 +411,13 @@ _LASSO_SVG = """<svg class="lasso" width="16" height="16" viewBox="0 0 24 24" fi
411
411
  <path d="M16.2 13.8c1.6 1.4 3.4 3.8 4.6 6.4" stroke="currentColor" stroke-width="1.85" stroke-linecap="round"/>
412
412
  </svg>"""
413
413
 
414
- _BRAND = f'<span class="brand">{_LASSO_SVG}Call Walkie Talkie</span>'
414
+ _BRAND = f'<span class="brand">{_LASSO_SVG}WalkieTalkie</span>'
415
415
 
416
416
  SETUP_HTML = """<!doctype html>
417
417
  <html lang="en"><head>
418
418
  <meta charset="utf-8">
419
419
  <meta name="viewport" content="width=device-width, initial-scale=1">
420
- <title>Link your phone — Call Walkie Talkie</title>
420
+ <title>Link your phone — WalkieTalkie</title>
421
421
  <link rel="preconnect" href="https://fonts.googleapis.com">
422
422
  <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
423
423
  <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">
@@ -527,7 +527,7 @@ HANDOFF_HTML = """<!doctype html>
527
527
  <html lang="en"><head>
528
528
  <meta charset="utf-8">
529
529
  <meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover">
530
- <title>Phone linked — Call Walkie Talkie</title>
530
+ <title>Phone linked — WalkieTalkie</title>
531
531
  <link rel="preconnect" href="https://fonts.googleapis.com">
532
532
  <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
533
533
  <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">
@@ -548,7 +548,7 @@ STALE_HTML = """<!doctype html>
548
548
  <html lang="en"><head>
549
549
  <meta charset="utf-8">
550
550
  <meta name="viewport" content="width=device-width, initial-scale=1">
551
- <title>Session expired — Call Walkie Talkie</title>
551
+ <title>Session expired — WalkieTalkie</title>
552
552
  <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">
553
553
  <style>__CSS__</style>
554
554
  </head><body>