a2acalling 0.6.0 → 0.6.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/README.md +33 -9
- package/SKILL.md +67 -5
- package/bin/cli.js +468 -151
- package/docs/protocol.md +24 -14
- package/package.json +1 -1
- package/scripts/install-openclaw.js +64 -68
- package/src/dashboard/public/app.js +765 -28
- package/src/dashboard/public/index.html +57 -13
- package/src/dashboard/public/style.css +16 -0
- package/src/lib/callbook.js +358 -0
- package/src/lib/client.js +1 -2
- package/src/lib/config.js +67 -15
- package/src/lib/external-ip.js +18 -7
- package/src/lib/invite-host.js +26 -41
- package/src/lib/logger.js +26 -14
- package/src/lib/tokens.js +314 -113
- package/src/routes/a2a.js +11 -2
- package/src/routes/callbook.js +142 -0
- package/src/routes/dashboard.js +557 -25
- package/src/server.js +6 -0
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Callbook Remote Install UI
|
|
3
|
+
*
|
|
4
|
+
* A simple HTML+JS page that:
|
|
5
|
+
* - reads the provisioning code from the URL fragment (#code=...)
|
|
6
|
+
* - exchanges it for a long-lived session cookie via /api/a2a/dashboard/callbook/exchange
|
|
7
|
+
* - redirects into the dashboard UI
|
|
8
|
+
*
|
|
9
|
+
* Note: the code lives in the fragment so it is not sent in HTTP logs/referrers.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
const express = require('express');
|
|
13
|
+
|
|
14
|
+
function createCallbookRouter() {
|
|
15
|
+
const router = express.Router();
|
|
16
|
+
|
|
17
|
+
router.use((req, res, next) => {
|
|
18
|
+
const rawPath = String(req.originalUrl || '').split('?')[0];
|
|
19
|
+
if (rawPath === req.baseUrl) {
|
|
20
|
+
return res.redirect(302, `${req.baseUrl}/install`);
|
|
21
|
+
}
|
|
22
|
+
return next();
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
router.get('/install', (req, res) => {
|
|
26
|
+
return res.status(200).send(`<!doctype html>
|
|
27
|
+
<html lang="en">
|
|
28
|
+
<head>
|
|
29
|
+
<meta charset="utf-8">
|
|
30
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
31
|
+
<title>Callbook Remote Install</title>
|
|
32
|
+
<style>
|
|
33
|
+
body { font-family: system-ui, -apple-system, Segoe UI, sans-serif; padding: 2rem; max-width: 760px; margin: 0 auto; }
|
|
34
|
+
h1 { margin-top: 0; }
|
|
35
|
+
.card { border: 1px solid #d7dee6; border-radius: 12px; padding: 1rem; background: #fbfdff; }
|
|
36
|
+
label { display: block; margin: 0.8rem 0 0.35rem; }
|
|
37
|
+
input { width: 100%; padding: 0.55rem 0.65rem; border-radius: 10px; border: 1px solid #d7dee6; font: inherit; }
|
|
38
|
+
button { margin-top: 0.9rem; padding: 0.55rem 0.75rem; border-radius: 10px; border: 1px solid #d7dee6; background: #fff; font: inherit; cursor: pointer; }
|
|
39
|
+
button:hover { border-color: #1466c1; color: #1466c1; }
|
|
40
|
+
pre { white-space: pre-wrap; word-break: break-word; background: #f4f6f8; padding: 0.75rem; border-radius: 10px; }
|
|
41
|
+
.muted { color: #4b5d73; }
|
|
42
|
+
.error { color: #8a1f1f; }
|
|
43
|
+
.ok { color: #1b5e20; }
|
|
44
|
+
</style>
|
|
45
|
+
</head>
|
|
46
|
+
<body>
|
|
47
|
+
<h1>Install Callbook Remote</h1>
|
|
48
|
+
<p class="muted">This pairs your browser with the server dashboard so you can manage contacts, calls, logs, and settings remotely.</p>
|
|
49
|
+
|
|
50
|
+
<div class="card">
|
|
51
|
+
<div id="status" class="muted">Reading install code…</div>
|
|
52
|
+
|
|
53
|
+
<label for="label">Device label</label>
|
|
54
|
+
<input id="label" type="text" placeholder="e.g. Ben’s MacBook">
|
|
55
|
+
|
|
56
|
+
<button id="connect" disabled>Connect</button>
|
|
57
|
+
<button id="open-dashboard" style="display:none;">Open Dashboard</button>
|
|
58
|
+
|
|
59
|
+
<details style="margin-top: 1rem;">
|
|
60
|
+
<summary>Having trouble?</summary>
|
|
61
|
+
<p class="muted">This URL must include a fragment like <code>#code=cbk_...</code>.</p>
|
|
62
|
+
<pre id="debug"></pre>
|
|
63
|
+
</details>
|
|
64
|
+
</div>
|
|
65
|
+
|
|
66
|
+
<script>
|
|
67
|
+
const statusEl = document.getElementById('status');
|
|
68
|
+
const debugEl = document.getElementById('debug');
|
|
69
|
+
const labelEl = document.getElementById('label');
|
|
70
|
+
const connectBtn = document.getElementById('connect');
|
|
71
|
+
const openBtn = document.getElementById('open-dashboard');
|
|
72
|
+
|
|
73
|
+
function setStatus(text, cls) {
|
|
74
|
+
statusEl.textContent = text;
|
|
75
|
+
statusEl.className = cls || 'muted';
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function parseInstallCode() {
|
|
79
|
+
const hash = String(window.location.hash || '').replace(/^#/, '');
|
|
80
|
+
const params = new URLSearchParams(hash);
|
|
81
|
+
return params.get('code');
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
async function exchange(code) {
|
|
85
|
+
const label = String(labelEl.value || '').trim();
|
|
86
|
+
const res = await fetch('/api/a2a/dashboard/callbook/exchange', {
|
|
87
|
+
method: 'POST',
|
|
88
|
+
headers: { 'Content-Type': 'application/json' },
|
|
89
|
+
body: JSON.stringify({ code, label })
|
|
90
|
+
});
|
|
91
|
+
const payload = await res.json().catch(() => ({}));
|
|
92
|
+
if (!res.ok || payload.success === false) {
|
|
93
|
+
const msg = payload.message || payload.error || ('Request failed: ' + res.status);
|
|
94
|
+
throw new Error(msg);
|
|
95
|
+
}
|
|
96
|
+
return payload;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
const code = parseInstallCode();
|
|
100
|
+
debugEl.textContent = 'hash=' + window.location.hash + '\\ncode=' + (code ? '[present]' : '[missing]');
|
|
101
|
+
|
|
102
|
+
if (!labelEl.value) {
|
|
103
|
+
const guess = (navigator.platform || navigator.userAgent || '').replace(/\\s+/g, ' ').trim();
|
|
104
|
+
labelEl.value = guess ? ('Callbook Remote (' + guess.slice(0, 36) + ')') : 'Callbook Remote';
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
if (!code) {
|
|
108
|
+
setStatus('Missing install code in URL fragment. Ask your agent to generate a new install link.', 'error');
|
|
109
|
+
} else {
|
|
110
|
+
setStatus('Install code found. Click Connect to pair this device.', 'muted');
|
|
111
|
+
connectBtn.disabled = false;
|
|
112
|
+
connectBtn.addEventListener('click', async () => {
|
|
113
|
+
connectBtn.disabled = true;
|
|
114
|
+
setStatus('Connecting…', 'muted');
|
|
115
|
+
try {
|
|
116
|
+
const result = await exchange(code);
|
|
117
|
+
setStatus('Connected. Session cookie stored in your browser.', 'ok');
|
|
118
|
+
openBtn.style.display = 'inline-block';
|
|
119
|
+
openBtn.addEventListener('click', () => {
|
|
120
|
+
window.location.href = result.dashboard_path || '/dashboard/';
|
|
121
|
+
});
|
|
122
|
+
window.setTimeout(() => {
|
|
123
|
+
window.location.href = result.dashboard_path || '/dashboard/';
|
|
124
|
+
}, 350);
|
|
125
|
+
} catch (err) {
|
|
126
|
+
setStatus('Failed: ' + err.message, 'error');
|
|
127
|
+
connectBtn.disabled = false;
|
|
128
|
+
}
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
</script>
|
|
132
|
+
</body>
|
|
133
|
+
</html>`);
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
return router;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
module.exports = {
|
|
140
|
+
createCallbookRouter
|
|
141
|
+
};
|
|
142
|
+
|