callwalkietalkie 0.9.0 → 0.9.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.
package/bin/callwalkietalkie.js
CHANGED
|
@@ -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("
|
|
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.
|
|
3
|
+
"version": "0.9.2",
|
|
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/
|
|
8
|
+
"url": "git+https://github.com/nbened/callwalkietalkie.git",
|
|
9
9
|
"directory": "cli"
|
|
10
10
|
},
|
|
11
11
|
"homepage": "https://callwalkietalkie.com",
|
package/runtime/agent_ui.html
CHANGED
|
@@ -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 —
|
|
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
|
|
582
|
+
<span class="hint">Terminals & 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
|
|
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
|
-
|
|
708
|
-
|
|
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 ({"&":"&","<":"<",">":">","\"":""","'":"'"})[c];
|
|
@@ -842,7 +874,7 @@
|
|
|
842
874
|
}
|
|
843
875
|
if (!line.trim()) { flushPara([]); i++; continue; }
|
|
844
876
|
var para = [];
|
|
845
|
-
while (i < lines.length && lines[i].trim() &&
|
|
877
|
+
while (i < lines.length && lines[i].trim() && !/^```([\w-]*)\s*$/.test(lines[i])) {
|
|
846
878
|
para.push(lines[i]); i++;
|
|
847
879
|
}
|
|
848
880
|
flushPara(para);
|
|
@@ -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">
|
|
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
|
|
1051
|
-
? (
|
|
1052
|
-
:
|
|
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
|
-
'
|
|
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 ? "
|
|
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: "
|
|
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 ||
|
|
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();
|
|
@@ -840,8 +840,52 @@ def _strip_assistant_line(line: str) -> str:
|
|
|
840
840
|
return line.rstrip()
|
|
841
841
|
|
|
842
842
|
|
|
843
|
+
def _parse_codex_screen(text: str) -> list[ChatMessage]:
|
|
844
|
+
"""Parse explicit Codex turn markers without relying on terminal padding."""
|
|
845
|
+
out: list[ChatMessage] = []
|
|
846
|
+
role, kind = "assistant", "message"
|
|
847
|
+
block: list[str] = []
|
|
848
|
+
|
|
849
|
+
def flush():
|
|
850
|
+
body = "\n".join(block).strip()
|
|
851
|
+
block.clear()
|
|
852
|
+
if body:
|
|
853
|
+
out.append(ChatMessage(role=role, kind=kind, text=body))
|
|
854
|
+
|
|
855
|
+
for line in text.splitlines():
|
|
856
|
+
if line.startswith("›"):
|
|
857
|
+
flush()
|
|
858
|
+
role, kind = "user", "message"
|
|
859
|
+
value = line[1:].strip()
|
|
860
|
+
if value and value != "Ask Codex to do anything":
|
|
861
|
+
block.append(value)
|
|
862
|
+
continue
|
|
863
|
+
if line.startswith(("• ", "✔ ", "✗ ")):
|
|
864
|
+
flush()
|
|
865
|
+
value = line[2:].strip()
|
|
866
|
+
role = "assistant"
|
|
867
|
+
kind = "command" if line.startswith(("✔ ", "✗ ")) or re.match(
|
|
868
|
+
r"^(Ran|Running|Explored|Edited|Read|Searched|Updated Plan|Called)\b", value
|
|
869
|
+
) else "message"
|
|
870
|
+
if "esc to interrupt" not in value:
|
|
871
|
+
block.append(value)
|
|
872
|
+
continue
|
|
873
|
+
if "esc to interrupt" in line or re.match(r"^\s*gpt-[\w.-]+.*·", line):
|
|
874
|
+
flush()
|
|
875
|
+
continue
|
|
876
|
+
if line.strip() and set(line.strip()) <= {"─", "━", "-"}:
|
|
877
|
+
flush()
|
|
878
|
+
continue
|
|
879
|
+
# Wrapped turns use two spaces; preserve deeper code indentation.
|
|
880
|
+
block.append(line[2:] if line.startswith(" ") else line)
|
|
881
|
+
flush()
|
|
882
|
+
return out
|
|
883
|
+
|
|
884
|
+
|
|
843
885
|
def parse_screen(text: str) -> list[ChatMessage]:
|
|
844
886
|
"""Turn a cmux terminal dump into chat + command blocks."""
|
|
887
|
+
if re.search(r"^›", text, re.M) and re.search(r"^• ", text, re.M):
|
|
888
|
+
return _parse_codex_screen(text)
|
|
845
889
|
out: list[ChatMessage] = []
|
|
846
890
|
abuf: list[str] = []
|
|
847
891
|
ubuf: list[str] = []
|
package/runtime/winproxy.py
CHANGED
|
@@ -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}
|
|
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 —
|
|
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 —
|
|
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 —
|
|
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>
|