leedab 0.3.2 → 0.5.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/apps/console/LICENSE +6 -0
- package/apps/console/public/orbs.html +503 -0
- package/bin/leedab.js +11 -11
- package/dist/console-launcher.js +19 -14
- package/dist/gateway.js +14 -9
- package/dist/onboard/steps/provider.js +3 -3
- package/dist/onboard/steps/whatsapp.js +3 -3
- package/dist/openclaw.d.ts +9 -2
- package/dist/openclaw.js +16 -4
- package/dist/team/syncAllowlists.js +3 -3
- package/package.json +6 -3
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
Copyright (c) 2026 LeedAB Inc. All rights reserved.
|
|
2
|
+
|
|
3
|
+
This software is proprietary and confidential. Unauthorized copying, distribution,
|
|
4
|
+
modification, or use of this software, via any medium, is strictly prohibited.
|
|
5
|
+
|
|
6
|
+
For licensing inquiries, contact muiez@leedab.com.
|
|
@@ -0,0 +1,503 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="utf-8" />
|
|
5
|
+
<meta name="viewport" content="width=device-width,initial-scale=1" />
|
|
6
|
+
<title>LeedAB — Orbs (legacy)</title>
|
|
7
|
+
<link rel="preconnect" href="https://fonts.googleapis.com">
|
|
8
|
+
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
|
9
|
+
<link href="https://fonts.googleapis.com/css2?family=Space+Mono:wght@400;700&family=DM+Sans:wght@300;400;500;600&display=swap" rel="stylesheet">
|
|
10
|
+
<style>
|
|
11
|
+
:root {
|
|
12
|
+
--paper: #efe9dd;
|
|
13
|
+
--paper2: #e6dfcf;
|
|
14
|
+
--panel: #ebe5d7;
|
|
15
|
+
--hair: #c9bea6;
|
|
16
|
+
--hair2: #d8cebb;
|
|
17
|
+
--ink: #1a1713;
|
|
18
|
+
--ink2: #3d3730;
|
|
19
|
+
--mute: #8a7f72;
|
|
20
|
+
--mute2: #9a9081;
|
|
21
|
+
--mute3: #b0a495;
|
|
22
|
+
--em: #10b981;
|
|
23
|
+
--em3: #10b98133;
|
|
24
|
+
--amber: #c95c0a;
|
|
25
|
+
}
|
|
26
|
+
* { box-sizing: border-box; }
|
|
27
|
+
html, body {
|
|
28
|
+
margin: 0; padding: 0;
|
|
29
|
+
background: var(--paper);
|
|
30
|
+
color: var(--ink);
|
|
31
|
+
font-family: "DM Sans", ui-sans-serif, system-ui, sans-serif;
|
|
32
|
+
font-size: 14px; line-height: 1.6;
|
|
33
|
+
-webkit-font-smoothing: antialiased;
|
|
34
|
+
-moz-osx-font-smoothing: grayscale;
|
|
35
|
+
min-height: 100vh;
|
|
36
|
+
}
|
|
37
|
+
body::before {
|
|
38
|
+
content: "";
|
|
39
|
+
position: fixed; inset: 0;
|
|
40
|
+
pointer-events: none; z-index: 999; opacity: 0.025;
|
|
41
|
+
background-image: url("data:image/svg+xml,%3Csvg viewBox='0 0 256 256' xmlns='http://www.w3.org/2000/svg'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='4' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)'/%3E%3C/svg%3E");
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
nav.top {
|
|
45
|
+
height: 52px; padding: 0 28px;
|
|
46
|
+
border-bottom: 1px solid var(--hair);
|
|
47
|
+
background: var(--panel);
|
|
48
|
+
display: flex; align-items: center; justify-content: space-between;
|
|
49
|
+
font-family: "Space Mono", monospace;
|
|
50
|
+
}
|
|
51
|
+
nav.top .brand {
|
|
52
|
+
font-size: 13px; font-weight: 700; letter-spacing: 0.04em;
|
|
53
|
+
}
|
|
54
|
+
nav.top .brand .dot { color: var(--amber); }
|
|
55
|
+
nav.top .meta {
|
|
56
|
+
font-size: 10px; letter-spacing: 0.08em; color: var(--mute2);
|
|
57
|
+
text-transform: uppercase;
|
|
58
|
+
}
|
|
59
|
+
nav.top .meta .live {
|
|
60
|
+
display: inline-block; width: 6px; height: 6px; border-radius: 50%;
|
|
61
|
+
background: var(--em); box-shadow: 0 0 8px var(--em); margin-right: 8px;
|
|
62
|
+
vertical-align: middle;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
.legacy-tag {
|
|
66
|
+
position: fixed; top: 64px; right: 24px;
|
|
67
|
+
font-family: "Space Mono", monospace; font-size: 9px;
|
|
68
|
+
letter-spacing: 0.18em; text-transform: uppercase;
|
|
69
|
+
color: var(--amber);
|
|
70
|
+
border: 1px dashed var(--amber);
|
|
71
|
+
background: rgba(201, 92, 10, 0.06);
|
|
72
|
+
padding: 4px 10px; border-radius: 3px;
|
|
73
|
+
z-index: 100;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
main {
|
|
77
|
+
max-width: 1180px; margin: 0 auto;
|
|
78
|
+
padding: 56px 32px 120px;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
.hero { text-align: center; margin-bottom: 56px; }
|
|
82
|
+
.hero h1 {
|
|
83
|
+
font-family: "Space Mono", monospace;
|
|
84
|
+
font-size: 22px; font-weight: 700; letter-spacing: 0.04em;
|
|
85
|
+
margin: 0 0 6px; color: var(--ink);
|
|
86
|
+
}
|
|
87
|
+
.hero p {
|
|
88
|
+
margin: 0;
|
|
89
|
+
font-size: 12px; color: var(--mute);
|
|
90
|
+
letter-spacing: 0.04em;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
.team {
|
|
94
|
+
margin: 48px 0;
|
|
95
|
+
}
|
|
96
|
+
.team-head {
|
|
97
|
+
display: flex; align-items: center; gap: 14px;
|
|
98
|
+
margin-bottom: 28px;
|
|
99
|
+
font-family: "Space Mono", monospace;
|
|
100
|
+
font-size: 9px; letter-spacing: 0.22em;
|
|
101
|
+
text-transform: uppercase; color: var(--mute2);
|
|
102
|
+
}
|
|
103
|
+
.team-head .rule {
|
|
104
|
+
flex: 1; height: 1px;
|
|
105
|
+
background: linear-gradient(to right, var(--hair), transparent);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
.orb-row {
|
|
109
|
+
display: flex; flex-wrap: wrap;
|
|
110
|
+
justify-content: center;
|
|
111
|
+
gap: 28px 36px;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/* ───── Orb (rebuilt 1:1 from page.tsx AgentOrb) ───── */
|
|
115
|
+
.orb-cell {
|
|
116
|
+
display: flex; flex-direction: column; align-items: center;
|
|
117
|
+
cursor: pointer;
|
|
118
|
+
transition: transform 0.22s cubic-bezier(0.4,0,0.2,1);
|
|
119
|
+
}
|
|
120
|
+
.orb-cell:hover { transform: scale(1.08); }
|
|
121
|
+
.orb-cell.open { transform: scale(1.15); }
|
|
122
|
+
|
|
123
|
+
.orb-wrap {
|
|
124
|
+
position: relative;
|
|
125
|
+
width: 80px; height: 80px;
|
|
126
|
+
animation: orb-float 4s ease-in-out infinite;
|
|
127
|
+
will-change: transform;
|
|
128
|
+
}
|
|
129
|
+
@keyframes orb-float {
|
|
130
|
+
0%, 100% { transform: translateY(0); }
|
|
131
|
+
50% { transform: translateY(-8px); }
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
.orb-sphere {
|
|
135
|
+
width: 80px; height: 80px;
|
|
136
|
+
border-radius: 50%;
|
|
137
|
+
position: relative; overflow: hidden;
|
|
138
|
+
display: flex; align-items: center; justify-content: center;
|
|
139
|
+
background: radial-gradient(circle at 35% 28%, #f5f0e6 0%, #ebe2d3 50%, #d8ccb8 100%);
|
|
140
|
+
box-shadow:
|
|
141
|
+
0 0 0 1px var(--hair),
|
|
142
|
+
0 2px 12px rgba(26,23,19,0.10),
|
|
143
|
+
inset 0 1px 0 rgba(255,255,255,0.80),
|
|
144
|
+
inset 0 0 12px rgba(26,23,19,0.04);
|
|
145
|
+
transition: background 0.3s, box-shadow 0.3s;
|
|
146
|
+
}
|
|
147
|
+
.orb-cell:hover .orb-sphere {
|
|
148
|
+
background: radial-gradient(circle at 35% 28%, #f0e8d8 0%, #e3d6c2 50%, #cfc0a5 100%);
|
|
149
|
+
box-shadow:
|
|
150
|
+
0 0 0 1px #b5a892,
|
|
151
|
+
0 4px 20px rgba(26,23,19,0.16),
|
|
152
|
+
inset 0 1px 0 rgba(255,255,255,0.85),
|
|
153
|
+
inset 0 0 14px rgba(26,23,19,0.05);
|
|
154
|
+
}
|
|
155
|
+
.orb-cell.open .orb-sphere {
|
|
156
|
+
background:
|
|
157
|
+
radial-gradient(circle at 35% 28%,
|
|
158
|
+
var(--accent-30) 0%,
|
|
159
|
+
var(--accent-14) 35%,
|
|
160
|
+
#ebe2d3 65%,
|
|
161
|
+
#d8ccb8 100%);
|
|
162
|
+
box-shadow:
|
|
163
|
+
0 0 0 1.5px var(--accent-44),
|
|
164
|
+
0 0 20px var(--accent-22),
|
|
165
|
+
0 6px 24px rgba(26,23,19,0.14),
|
|
166
|
+
inset 0 1px 0 rgba(255,255,255,0.75);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
.specular {
|
|
170
|
+
position: absolute; top: 12%; left: 18%; width: 38%; height: 26%;
|
|
171
|
+
border-radius: 50%;
|
|
172
|
+
background: radial-gradient(ellipse, rgba(255,255,255,0.80) 0%, transparent 75%);
|
|
173
|
+
pointer-events: none;
|
|
174
|
+
}
|
|
175
|
+
.shadow-pool {
|
|
176
|
+
position: absolute; bottom: 8%; right: 10%; width: 44%; height: 34%;
|
|
177
|
+
border-radius: 50%;
|
|
178
|
+
background: radial-gradient(ellipse, rgba(0,0,0,0.10) 0%, transparent 70%);
|
|
179
|
+
pointer-events: none;
|
|
180
|
+
}
|
|
181
|
+
.rim-shimmer {
|
|
182
|
+
position: absolute; inset: 0; border-radius: 50%;
|
|
183
|
+
background: radial-gradient(ellipse at 50% 0%, rgba(255,255,255,0.45) 0%, transparent 55%);
|
|
184
|
+
pointer-events: none;
|
|
185
|
+
}
|
|
186
|
+
.orb-glow {
|
|
187
|
+
position: absolute; inset: -12px; border-radius: 50%;
|
|
188
|
+
background: radial-gradient(ellipse, var(--accent-28) 0%, transparent 70%);
|
|
189
|
+
pointer-events: none;
|
|
190
|
+
opacity: 0; transition: opacity 0.3s;
|
|
191
|
+
}
|
|
192
|
+
.orb-cell.open .orb-glow { opacity: 1; }
|
|
193
|
+
|
|
194
|
+
.pulse-ring {
|
|
195
|
+
position: absolute; inset: -4px; border-radius: 50%;
|
|
196
|
+
border: 1px solid var(--accent-22);
|
|
197
|
+
pointer-events: none;
|
|
198
|
+
animation: orb-pulse 3s ease-in-out infinite;
|
|
199
|
+
}
|
|
200
|
+
.orb-cell.open .pulse-ring { border-color: var(--accent-44); }
|
|
201
|
+
@keyframes orb-pulse {
|
|
202
|
+
0%, 100% { opacity: 0; transform: scale(1); }
|
|
203
|
+
50% { opacity: 1; transform: scale(1.14); }
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
.orb-code {
|
|
207
|
+
font-family: "Space Mono", monospace;
|
|
208
|
+
font-size: 11px; font-weight: 700; letter-spacing: 0.04em;
|
|
209
|
+
color: var(--mute2);
|
|
210
|
+
position: relative; z-index: 1;
|
|
211
|
+
transition: color 0.25s, text-shadow 0.25s;
|
|
212
|
+
}
|
|
213
|
+
.orb-cell:hover .orb-code { color: var(--ink2); }
|
|
214
|
+
.orb-cell.open .orb-code {
|
|
215
|
+
color: var(--accent);
|
|
216
|
+
text-shadow: 0 0 12px var(--accent-55);
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
.status-dots {
|
|
220
|
+
position: absolute; bottom: 1px; right: 1px;
|
|
221
|
+
display: flex; align-items: center; gap: 3px;
|
|
222
|
+
}
|
|
223
|
+
.msg-dot {
|
|
224
|
+
width: 5px; height: 5px; border-radius: 50%;
|
|
225
|
+
background: var(--em);
|
|
226
|
+
box-shadow: 0 0 6px var(--em);
|
|
227
|
+
}
|
|
228
|
+
.orb-cell.open .msg-dot { display: none; }
|
|
229
|
+
.status-dot {
|
|
230
|
+
width: 8px; height: 8px; border-radius: 50%;
|
|
231
|
+
background: #c5b9a8;
|
|
232
|
+
border: 1.5px solid var(--paper);
|
|
233
|
+
transition: all 0.3s;
|
|
234
|
+
}
|
|
235
|
+
.orb-cell[data-status="active"] .status-dot {
|
|
236
|
+
background: var(--em);
|
|
237
|
+
box-shadow: 0 0 8px rgba(16,185,129,0.67);
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
.ground-shadow {
|
|
241
|
+
width: 56px; height: 6px; border-radius: 50%;
|
|
242
|
+
background: radial-gradient(ellipse, rgba(0,0,0,0.47) 0%, transparent 80%);
|
|
243
|
+
margin-top: 6px;
|
|
244
|
+
animation: orb-shadow 4s ease-in-out infinite;
|
|
245
|
+
will-change: transform, opacity;
|
|
246
|
+
}
|
|
247
|
+
@keyframes orb-shadow {
|
|
248
|
+
0%, 100% { opacity: 0.6; transform: scaleX(1); }
|
|
249
|
+
50% { opacity: 0.3; transform: scaleX(0.72); }
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
.orb-name {
|
|
253
|
+
margin-top: 10px;
|
|
254
|
+
font-family: "Space Mono", monospace;
|
|
255
|
+
font-size: 9px; letter-spacing: 0.1em;
|
|
256
|
+
text-align: center; text-transform: uppercase;
|
|
257
|
+
color: var(--mute3); white-space: nowrap;
|
|
258
|
+
transition: color 0.2s;
|
|
259
|
+
}
|
|
260
|
+
.orb-cell:hover .orb-name { color: var(--mute); }
|
|
261
|
+
.orb-cell.open .orb-name { color: var(--ink2); }
|
|
262
|
+
|
|
263
|
+
/* ───── Detail panel (when an orb is open) ───── */
|
|
264
|
+
.detail {
|
|
265
|
+
position: fixed; left: 50%; bottom: 28px; transform: translateX(-50%);
|
|
266
|
+
width: min(640px, calc(100% - 48px));
|
|
267
|
+
background: var(--panel);
|
|
268
|
+
border: 1px solid var(--hair);
|
|
269
|
+
border-radius: 6px;
|
|
270
|
+
padding: 18px 22px;
|
|
271
|
+
box-shadow: 0 12px 40px rgba(26,23,19,0.18);
|
|
272
|
+
display: none;
|
|
273
|
+
z-index: 50;
|
|
274
|
+
animation: panel-up 0.28s cubic-bezier(0.16,1,0.3,1);
|
|
275
|
+
}
|
|
276
|
+
.detail.show { display: block; }
|
|
277
|
+
@keyframes panel-up {
|
|
278
|
+
from { opacity: 0; transform: translate(-50%, 12px); }
|
|
279
|
+
to { opacity: 1; transform: translate(-50%, 0); }
|
|
280
|
+
}
|
|
281
|
+
.detail-head {
|
|
282
|
+
display: flex; align-items: center; gap: 12px; margin-bottom: 10px;
|
|
283
|
+
}
|
|
284
|
+
.detail-code {
|
|
285
|
+
font-family: "Space Mono", monospace;
|
|
286
|
+
font-size: 13px; font-weight: 700; letter-spacing: 0.06em;
|
|
287
|
+
color: var(--ink);
|
|
288
|
+
}
|
|
289
|
+
.detail-name {
|
|
290
|
+
font-family: "Space Mono", monospace;
|
|
291
|
+
font-size: 11px; letter-spacing: 0.18em; text-transform: uppercase;
|
|
292
|
+
color: var(--mute);
|
|
293
|
+
}
|
|
294
|
+
.detail-section {
|
|
295
|
+
margin-left: auto;
|
|
296
|
+
font-family: "Space Mono", monospace;
|
|
297
|
+
font-size: 9px; letter-spacing: 0.18em; text-transform: uppercase;
|
|
298
|
+
color: var(--mute2);
|
|
299
|
+
}
|
|
300
|
+
.detail-tagline {
|
|
301
|
+
font-size: 13px; color: var(--ink2); margin: 0 0 14px;
|
|
302
|
+
}
|
|
303
|
+
.detail-foot {
|
|
304
|
+
display: flex; justify-content: space-between; align-items: center;
|
|
305
|
+
padding-top: 12px; border-top: 1px solid var(--hair2);
|
|
306
|
+
font-family: "Space Mono", monospace;
|
|
307
|
+
font-size: 10px; letter-spacing: 0.08em;
|
|
308
|
+
}
|
|
309
|
+
.detail-status {
|
|
310
|
+
color: var(--mute2); text-transform: uppercase;
|
|
311
|
+
}
|
|
312
|
+
.detail-status .dot {
|
|
313
|
+
display: inline-block; width: 6px; height: 6px; border-radius: 50%;
|
|
314
|
+
background: #c5b9a8; margin-right: 6px; vertical-align: middle;
|
|
315
|
+
}
|
|
316
|
+
.detail-status.active .dot {
|
|
317
|
+
background: var(--em); box-shadow: 0 0 8px var(--em);
|
|
318
|
+
}
|
|
319
|
+
.detail-status.active { color: var(--em); }
|
|
320
|
+
.open-chat {
|
|
321
|
+
color: var(--em);
|
|
322
|
+
text-decoration: none;
|
|
323
|
+
border: 1px solid var(--em3);
|
|
324
|
+
padding: 6px 12px; border-radius: 3px;
|
|
325
|
+
font-family: "Space Mono", monospace;
|
|
326
|
+
font-size: 10px; letter-spacing: 0.1em; text-transform: uppercase;
|
|
327
|
+
transition: all 0.15s;
|
|
328
|
+
}
|
|
329
|
+
.open-chat:hover { background: var(--em3); }
|
|
330
|
+
.detail-close {
|
|
331
|
+
background: none; border: none; cursor: pointer;
|
|
332
|
+
color: var(--mute2); font-family: "Space Mono", monospace;
|
|
333
|
+
font-size: 11px; padding: 4px 6px;
|
|
334
|
+
}
|
|
335
|
+
.detail-close:hover { color: var(--ink); }
|
|
336
|
+
</style>
|
|
337
|
+
</head>
|
|
338
|
+
<body>
|
|
339
|
+
|
|
340
|
+
<nav class="top">
|
|
341
|
+
<div class="brand">LeedAB<span class="dot">.</span></div>
|
|
342
|
+
<div class="meta"><span class="live"></span>20 agents · local · no data leaves this machine</div>
|
|
343
|
+
</nav>
|
|
344
|
+
|
|
345
|
+
<div class="legacy-tag">orb design · legacy</div>
|
|
346
|
+
|
|
347
|
+
<main>
|
|
348
|
+
<div class="hero">
|
|
349
|
+
<h1>operations room</h1>
|
|
350
|
+
<p>tap an orb to focus an agent</p>
|
|
351
|
+
</div>
|
|
352
|
+
|
|
353
|
+
<div id="board"></div>
|
|
354
|
+
</main>
|
|
355
|
+
|
|
356
|
+
<div class="detail" id="detail">
|
|
357
|
+
<div class="detail-head">
|
|
358
|
+
<span class="detail-code" id="d-code"></span>
|
|
359
|
+
<span class="detail-name" id="d-name"></span>
|
|
360
|
+
<span class="detail-section" id="d-section"></span>
|
|
361
|
+
<button class="detail-close" id="d-close" aria-label="Close">esc</button>
|
|
362
|
+
</div>
|
|
363
|
+
<p class="detail-tagline" id="d-tagline"></p>
|
|
364
|
+
<div class="detail-foot">
|
|
365
|
+
<span class="detail-status" id="d-status"><span class="dot"></span><span id="d-status-label"></span></span>
|
|
366
|
+
<a class="open-chat" href="/" id="d-open">open chat →</a>
|
|
367
|
+
</div>
|
|
368
|
+
</div>
|
|
369
|
+
|
|
370
|
+
<script>
|
|
371
|
+
const AGENTS = [
|
|
372
|
+
{ team: "Executive", id: "coo", code: "COO", name: "COO", section: "§ 00", accent: "#10b981", status: "active", tagline: "Org coordination, daily briefs" },
|
|
373
|
+
|
|
374
|
+
{ team: "Marketing", id: "marketing", code: "MKT", name: "Marketing", section: "§ 10", accent: "#f59e0b", status: "active", tagline: "Team lead, brand, ICP, strategy" },
|
|
375
|
+
{ team: "Marketing", id: "content", code: "CNT", name: "Content", section: "§ 11", accent: "#f59e0b", status: "active", tagline: "Copywriting, SEO, social, newsletters" },
|
|
376
|
+
{ team: "Marketing", id: "video", code: "VDO", name: "Video", section: "§ 12", accent: "#f59e0b", status: "idle", tagline: "AI video, scripts, programmatic production" },
|
|
377
|
+
{ team: "Marketing", id: "campaigns", code: "CMP", name: "Campaigns", section: "§ 13", accent: "#f59e0b", status: "active", tagline: "Paid ads, A/B tests, growth experiments" },
|
|
378
|
+
{ team: "Marketing", id: "email", code: "EML", name: "Email", section: "§ 14", accent: "#f59e0b", status: "active", tagline: "Sequences, cold outreach, nurture flows" },
|
|
379
|
+
|
|
380
|
+
{ team: "Product", id: "product", code: "PRD", name: "Product", section: "§ 20", accent: "#fbbf24", status: "active", tagline: "Specs, PRDs, release notes" },
|
|
381
|
+
|
|
382
|
+
{ team: "Engineering", id: "engineering", code: "ENG", name: "Engineering", section: "§ 30", accent: "#d97706", status: "active", tagline: "Tech lead, architecture, code review" },
|
|
383
|
+
{ team: "Engineering", id: "frontend", code: "FE", name: "Frontend", section: "§ 31", accent: "#d97706", status: "active", tagline: "React, UI, design implementation" },
|
|
384
|
+
{ team: "Engineering", id: "backend", code: "BE", name: "Backend", section: "§ 32", accent: "#d97706", status: "idle", tagline: "APIs, databases, server architecture" },
|
|
385
|
+
{ team: "Engineering", id: "devops", code: "DVP", name: "DevOps", section: "§ 33", accent: "#d97706", status: "idle", tagline: "CI/CD, infra, monitoring, runbooks" },
|
|
386
|
+
|
|
387
|
+
{ team: "Automation", id: "automation", code: "N8N", name: "Automation", section: "§ 35", accent: "#f97316", status: "active", tagline: "Build & manage n8n workflows" },
|
|
388
|
+
|
|
389
|
+
{ team: "Strategy", id: "strategy", code: "STR", name: "Strategy", section: "§ 40", accent: "#f59e0b", status: "active", tagline: "GTM, competitive, pricing" },
|
|
390
|
+
{ team: "Strategy", id: "sales", code: "SLS", name: "Sales", section: "§ 50", accent: "#10b981", status: "active", tagline: "Deals, proposals, outreach, pipeline" },
|
|
391
|
+
{ team: "Strategy", id: "finance", code: "CFN", name: "Finance", section: "§ 60", accent: "#10b981", status: "idle", tagline: "Runway, burn, scenario planning" },
|
|
392
|
+
{ team: "Strategy", id: "cx", code: "CXS", name: "Customer Success", section: "§ 70", accent: "#10b981", status: "active", tagline: "Onboarding, health scores, churn" },
|
|
393
|
+
|
|
394
|
+
{ team: "Capital & Ops", id: "investor", code: "INV", name: "Investor", section: "§ 80", accent: "#fde68a", status: "idle", tagline: "Deck, updates, fundraising" },
|
|
395
|
+
{ team: "Capital & Ops", id: "data", code: "DAT", name: "Data", section: "§ 85", accent: "#06b6d4", status: "active", tagline: "Metrics, cohorts, retention" },
|
|
396
|
+
{ team: "Capital & Ops", id: "ops", code: "OPS", name: "Operations", section: "§ 90", accent: "#fbbf24", status: "active", tagline: "Hiring, internal, partnerships" },
|
|
397
|
+
{ team: "Capital & Ops", id: "legal", code: "LGL", name: "Legal", section: "§ 95", accent: "#6366f1", status: "idle", tagline: "Contracts, privacy, compliance" },
|
|
398
|
+
];
|
|
399
|
+
|
|
400
|
+
// hex helpers — produce the alpha-suffixed accents the orb expects
|
|
401
|
+
const withAlpha = (hex, hh) => hex + hh;
|
|
402
|
+
|
|
403
|
+
const board = document.getElementById("board");
|
|
404
|
+
const teams = [...new Set(AGENTS.map(a => a.team))];
|
|
405
|
+
let openId = null;
|
|
406
|
+
|
|
407
|
+
teams.forEach((teamName, ti) => {
|
|
408
|
+
const team = document.createElement("section");
|
|
409
|
+
team.className = "team";
|
|
410
|
+
team.innerHTML = `
|
|
411
|
+
<div class="team-head">
|
|
412
|
+
<span>${teamName}</span>
|
|
413
|
+
<span class="rule"></span>
|
|
414
|
+
</div>
|
|
415
|
+
<div class="orb-row" data-team="${teamName}"></div>
|
|
416
|
+
`;
|
|
417
|
+
board.appendChild(team);
|
|
418
|
+
|
|
419
|
+
const row = team.querySelector(".orb-row");
|
|
420
|
+
AGENTS.filter(a => a.team === teamName).forEach((a, i) => {
|
|
421
|
+
const cell = document.createElement("div");
|
|
422
|
+
cell.className = "orb-cell";
|
|
423
|
+
cell.dataset.id = a.id;
|
|
424
|
+
cell.dataset.status = a.status;
|
|
425
|
+
|
|
426
|
+
// accent CSS vars (alpha variants used by the sphere)
|
|
427
|
+
cell.style.setProperty("--accent", a.accent);
|
|
428
|
+
cell.style.setProperty("--accent-14", withAlpha(a.accent, "14"));
|
|
429
|
+
cell.style.setProperty("--accent-22", withAlpha(a.accent, "22"));
|
|
430
|
+
cell.style.setProperty("--accent-28", withAlpha(a.accent, "28"));
|
|
431
|
+
cell.style.setProperty("--accent-30", withAlpha(a.accent, "30"));
|
|
432
|
+
cell.style.setProperty("--accent-44", withAlpha(a.accent, "44"));
|
|
433
|
+
cell.style.setProperty("--accent-55", withAlpha(a.accent, "55"));
|
|
434
|
+
|
|
435
|
+
// randomize float delay so orbs drift independently
|
|
436
|
+
const delay = (Math.random() * 2.4).toFixed(2);
|
|
437
|
+
|
|
438
|
+
cell.innerHTML = `
|
|
439
|
+
<div class="orb-wrap" style="animation-delay:${delay}s">
|
|
440
|
+
<div class="orb-glow"></div>
|
|
441
|
+
${a.status === "active" ? '<div class="pulse-ring"></div>' : ""}
|
|
442
|
+
<div class="orb-sphere">
|
|
443
|
+
<div class="specular"></div>
|
|
444
|
+
<div class="shadow-pool"></div>
|
|
445
|
+
<div class="rim-shimmer"></div>
|
|
446
|
+
<span class="orb-code">${a.code}</span>
|
|
447
|
+
<div class="status-dots">
|
|
448
|
+
<span class="status-dot"></span>
|
|
449
|
+
</div>
|
|
450
|
+
</div>
|
|
451
|
+
</div>
|
|
452
|
+
<div class="ground-shadow" style="animation-delay:${delay}s"></div>
|
|
453
|
+
<div class="orb-name">${a.name}</div>
|
|
454
|
+
`;
|
|
455
|
+
cell.addEventListener("click", () => openOrb(a.id));
|
|
456
|
+
row.appendChild(cell);
|
|
457
|
+
});
|
|
458
|
+
});
|
|
459
|
+
|
|
460
|
+
const detail = document.getElementById("detail");
|
|
461
|
+
const dCode = document.getElementById("d-code");
|
|
462
|
+
const dName = document.getElementById("d-name");
|
|
463
|
+
const dSection = document.getElementById("d-section");
|
|
464
|
+
const dTagline = document.getElementById("d-tagline");
|
|
465
|
+
const dStatus = document.getElementById("d-status");
|
|
466
|
+
const dStatusLabel = document.getElementById("d-status-label");
|
|
467
|
+
const dOpen = document.getElementById("d-open");
|
|
468
|
+
|
|
469
|
+
function openOrb(id) {
|
|
470
|
+
document.querySelectorAll(".orb-cell").forEach(c => c.classList.toggle("open", c.dataset.id === id));
|
|
471
|
+
const a = AGENTS.find(x => x.id === id);
|
|
472
|
+
if (!a) return;
|
|
473
|
+
openId = id;
|
|
474
|
+
dCode.textContent = a.code;
|
|
475
|
+
dCode.style.color = a.accent;
|
|
476
|
+
dName.textContent = a.name;
|
|
477
|
+
dSection.textContent = a.section;
|
|
478
|
+
dTagline.textContent = a.tagline;
|
|
479
|
+
dStatus.classList.toggle("active", a.status === "active");
|
|
480
|
+
dStatusLabel.textContent = a.status;
|
|
481
|
+
dOpen.style.color = a.accent;
|
|
482
|
+
dOpen.style.borderColor = withAlpha(a.accent, "55");
|
|
483
|
+
detail.classList.add("show");
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
function closeOrb() {
|
|
487
|
+
document.querySelectorAll(".orb-cell.open").forEach(c => c.classList.remove("open"));
|
|
488
|
+
detail.classList.remove("show");
|
|
489
|
+
openId = null;
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
document.getElementById("d-close").addEventListener("click", closeOrb);
|
|
493
|
+
document.addEventListener("keydown", e => { if (e.key === "Escape") closeOrb(); });
|
|
494
|
+
detail.addEventListener("click", e => e.stopPropagation());
|
|
495
|
+
document.addEventListener("click", e => {
|
|
496
|
+
if (!openId) return;
|
|
497
|
+
if (e.target.closest(".orb-cell") || e.target.closest(".detail")) return;
|
|
498
|
+
closeOrb();
|
|
499
|
+
});
|
|
500
|
+
</script>
|
|
501
|
+
|
|
502
|
+
</body>
|
|
503
|
+
</html>
|
package/bin/leedab.js
CHANGED
|
@@ -13,7 +13,7 @@ import { setupChannels } from "../dist/channels/index.js";
|
|
|
13
13
|
import { runOnboard } from "../dist/onboard/index.js";
|
|
14
14
|
import { startConsole, stopConsole } from "../dist/console-launcher.js";
|
|
15
15
|
import { execBranded } from "../dist/brand.js";
|
|
16
|
-
import { resolveOpenClawBin, openclawEnv } from "../dist/openclaw.js";
|
|
16
|
+
import { resolveOpenClawBin, openclawArgs, openclawEnv } from "../dist/openclaw.js";
|
|
17
17
|
import { addEntry, removeEntry, listEntries, encryptVault, decryptVault } from "../dist/vault.js";
|
|
18
18
|
import { loadTeam, addMember, removeMember } from "../dist/team.js";
|
|
19
19
|
import { ensureLicense } from "../dist/license.js";
|
|
@@ -269,7 +269,7 @@ program
|
|
|
269
269
|
const args = ["tui"];
|
|
270
270
|
if (opts.session) args.push("--session", opts.session);
|
|
271
271
|
if (opts.thinking) args.push("--thinking", opts.thinking);
|
|
272
|
-
const code = await execBranded(OPENCLAW_BIN, args, { env: termEnv });
|
|
272
|
+
const code = await execBranded(OPENCLAW_BIN, openclawArgs(args), { env: termEnv });
|
|
273
273
|
process.exit(code);
|
|
274
274
|
});
|
|
275
275
|
|
|
@@ -278,7 +278,7 @@ program
|
|
|
278
278
|
.description("Run the LeedAB gateway (foreground)")
|
|
279
279
|
.action(async () => {
|
|
280
280
|
console.log(chalk.bold("\n LeedAB Gateway\n"));
|
|
281
|
-
const code = await execBranded(OPENCLAW_BIN, ["gateway", "run"], { env: ocEnv });
|
|
281
|
+
const code = await execBranded(OPENCLAW_BIN, openclawArgs(["gateway", "run"]), { env: ocEnv });
|
|
282
282
|
process.exit(code);
|
|
283
283
|
});
|
|
284
284
|
|
|
@@ -299,7 +299,7 @@ pairing
|
|
|
299
299
|
.command("approve <channel> <code>")
|
|
300
300
|
.description("Approve a pairing request (e.g. leedab pairing approve telegram 67D9348E)")
|
|
301
301
|
.action(async (channel, code) => {
|
|
302
|
-
const codeResult = await execBranded(OPENCLAW_BIN, ["pairing", "approve", channel, code], { env: ocEnv });
|
|
302
|
+
const codeResult = await execBranded(OPENCLAW_BIN, openclawArgs(["pairing", "approve", channel, code]), { env: ocEnv });
|
|
303
303
|
process.exit(codeResult);
|
|
304
304
|
});
|
|
305
305
|
|
|
@@ -307,7 +307,7 @@ pairing
|
|
|
307
307
|
.command("list")
|
|
308
308
|
.description("List pending pairing requests")
|
|
309
309
|
.action(async () => {
|
|
310
|
-
const code = await execBranded(OPENCLAW_BIN, ["pairing", "list"], { env: ocEnv });
|
|
310
|
+
const code = await execBranded(OPENCLAW_BIN, openclawArgs(["pairing", "list"]), { env: ocEnv });
|
|
311
311
|
process.exit(code);
|
|
312
312
|
});
|
|
313
313
|
|
|
@@ -359,10 +359,10 @@ pairing
|
|
|
359
359
|
try {
|
|
360
360
|
const { execFile: ef } = await import("node:child_process");
|
|
361
361
|
const { promisify: p } = await import("node:util");
|
|
362
|
-
await p(ef)(OPENCLAW_BIN, ["gateway", "health"], { env: ocEnv, timeout: 3000 });
|
|
362
|
+
await p(ef)(OPENCLAW_BIN, openclawArgs(["gateway", "health"]), { env: ocEnv, timeout: 3000 });
|
|
363
363
|
// Gateway is running, restart it
|
|
364
364
|
const spin = ora("Restarting gateway...").start();
|
|
365
|
-
await p(ef)(OPENCLAW_BIN, ["gateway", "restart"], { env: ocEnv, timeout: 10000 });
|
|
365
|
+
await p(ef)(OPENCLAW_BIN, openclawArgs(["gateway", "restart"]), { env: ocEnv, timeout: 10000 });
|
|
366
366
|
spin.succeed(chalk.green("Gateway restarted. Change is live."));
|
|
367
367
|
} catch {
|
|
368
368
|
// Gateway not running, no restart needed
|
|
@@ -397,9 +397,9 @@ pairing
|
|
|
397
397
|
try {
|
|
398
398
|
const { execFile: ef } = await import("node:child_process");
|
|
399
399
|
const { promisify: p } = await import("node:util");
|
|
400
|
-
await p(ef)(OPENCLAW_BIN, ["gateway", "health"], { env: ocEnv, timeout: 3000 });
|
|
400
|
+
await p(ef)(OPENCLAW_BIN, openclawArgs(["gateway", "health"]), { env: ocEnv, timeout: 3000 });
|
|
401
401
|
const spin = ora("Restarting gateway...").start();
|
|
402
|
-
await p(ef)(OPENCLAW_BIN, ["gateway", "restart"], { env: ocEnv, timeout: 10000 });
|
|
402
|
+
await p(ef)(OPENCLAW_BIN, openclawArgs(["gateway", "restart"]), { env: ocEnv, timeout: 10000 });
|
|
403
403
|
spin.succeed(chalk.green("Gateway restarted. Change is live."));
|
|
404
404
|
} catch {
|
|
405
405
|
// Gateway not running, no restart needed
|
|
@@ -427,7 +427,7 @@ sessions
|
|
|
427
427
|
.command("list")
|
|
428
428
|
.description("List all sessions")
|
|
429
429
|
.action(async () => {
|
|
430
|
-
const code = await execBranded(OPENCLAW_BIN, ["sessions", "--json"], { env: ocEnv });
|
|
430
|
+
const code = await execBranded(OPENCLAW_BIN, openclawArgs(["sessions", "--json"]), { env: ocEnv });
|
|
431
431
|
// If branded exec didn't output, fallback
|
|
432
432
|
if (code !== 0) {
|
|
433
433
|
console.log(chalk.dim("No sessions found."));
|
|
@@ -678,7 +678,7 @@ if (process.argv.length <= 2) {
|
|
|
678
678
|
try {
|
|
679
679
|
const { execFile: ef } = await import("node:child_process");
|
|
680
680
|
const { promisify: p } = await import("node:util");
|
|
681
|
-
await p(ef)(OPENCLAW_BIN, ["gateway", "health"], { env: ocEnv, timeout: 3000 });
|
|
681
|
+
await p(ef)(OPENCLAW_BIN, openclawArgs(["gateway", "health"]), { env: ocEnv, timeout: 3000 });
|
|
682
682
|
gatewayUp = true;
|
|
683
683
|
} catch {}
|
|
684
684
|
|
package/dist/console-launcher.js
CHANGED
|
@@ -2,9 +2,11 @@
|
|
|
2
2
|
// `leedab start` and `leedab dashboard` use this to swap the legacy
|
|
3
3
|
// src/dashboard server for the modern Next.js admin UI.
|
|
4
4
|
//
|
|
5
|
-
//
|
|
6
|
-
//
|
|
7
|
-
//
|
|
5
|
+
// We launch the Next.js standalone bundle (apps/console/.next/standalone/
|
|
6
|
+
// server.js) directly via `node`. The bundle is produced by `next build`
|
|
7
|
+
// with output: "standalone" and ships pre-built inside the published npm
|
|
8
|
+
// package — customers never need to run `npm install` or `next build`.
|
|
9
|
+
// For dev, run `npm run dev` from apps/console directly.
|
|
8
10
|
import { spawn } from "node:child_process";
|
|
9
11
|
import { createWriteStream, existsSync, readFileSync, unlinkSync, writeFileSync } from "node:fs";
|
|
10
12
|
import { mkdir } from "node:fs/promises";
|
|
@@ -85,15 +87,13 @@ function consoleDir() {
|
|
|
85
87
|
*/
|
|
86
88
|
export async function startConsole(port = 3000) {
|
|
87
89
|
const dir = consoleDir();
|
|
88
|
-
const
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
throw new Error(`Next.js binary not found at ${nextBin}.\n` +
|
|
96
|
-
`Run \`cd ${dir} && npm install\` first.`);
|
|
90
|
+
const standaloneDir = resolve(dir, ".next", "standalone");
|
|
91
|
+
const serverEntry = resolve(standaloneDir, "server.js");
|
|
92
|
+
if (!existsSync(serverEntry)) {
|
|
93
|
+
throw new Error(`Console build not found at ${serverEntry}.\n` +
|
|
94
|
+
`If you installed leedab via npm, this is a packaging bug — please report it.\n` +
|
|
95
|
+
`If you're running from a source checkout, build the console once with:\n` +
|
|
96
|
+
` cd ${dir} && npm install && npm run build`);
|
|
97
97
|
}
|
|
98
98
|
const logDir = resolve(STATE_DIR, "logs");
|
|
99
99
|
await mkdir(logDir, { recursive: true });
|
|
@@ -105,12 +105,17 @@ export async function startConsole(port = 3000) {
|
|
|
105
105
|
const stale = readPidFile();
|
|
106
106
|
if (stale && !isAlive(stale))
|
|
107
107
|
clearPidFile();
|
|
108
|
-
|
|
109
|
-
|
|
108
|
+
// Next.js standalone reads HOSTNAME and PORT from the environment.
|
|
109
|
+
// We launch with the current node binary so the customer doesn't need
|
|
110
|
+
// a `next` CLI or a populated node_modules at runtime.
|
|
111
|
+
consoleProcess = spawn(process.execPath, [serverEntry], {
|
|
112
|
+
cwd: standaloneDir,
|
|
110
113
|
env: {
|
|
111
114
|
...process.env,
|
|
112
115
|
LEEDAB_STATE_DIR: STATE_DIR,
|
|
113
116
|
NODE_ENV: "production",
|
|
117
|
+
HOSTNAME: "127.0.0.1",
|
|
118
|
+
PORT: String(port),
|
|
114
119
|
},
|
|
115
120
|
stdio: ["inherit", "pipe", "pipe"],
|
|
116
121
|
});
|
package/dist/gateway.js
CHANGED
|
@@ -4,7 +4,7 @@ import { resolve, dirname, join } from "node:path";
|
|
|
4
4
|
import { readFile, writeFile, readdir, copyFile, mkdir, access } from "node:fs/promises";
|
|
5
5
|
import { fileURLToPath } from "node:url";
|
|
6
6
|
import { promisify } from "node:util";
|
|
7
|
-
import { resolveOpenClawBin, openclawEnv } from "./openclaw.js";
|
|
7
|
+
import { resolveOpenClawBin, openclawArgs, openclawEnv } from "./openclaw.js";
|
|
8
8
|
import { ensureLicense } from "./license.js";
|
|
9
9
|
import { STATE_DIR } from "./paths.js";
|
|
10
10
|
const execFileAsync = promisify(execFile);
|
|
@@ -37,7 +37,7 @@ export async function startGateway(config) {
|
|
|
37
37
|
const today = new Date().toISOString().slice(0, 10);
|
|
38
38
|
const logPath = resolve(logDir, `gateway-${today}.log`);
|
|
39
39
|
const logStream = createWriteStream(logPath, { flags: "a" });
|
|
40
|
-
gatewayProcess = spawn(bin, ["gateway", "run", "--force"], {
|
|
40
|
+
gatewayProcess = spawn(bin, openclawArgs(["gateway", "run", "--force"]), {
|
|
41
41
|
env,
|
|
42
42
|
stdio: ["inherit", "pipe", "pipe"],
|
|
43
43
|
});
|
|
@@ -71,7 +71,7 @@ export async function stopGateway() {
|
|
|
71
71
|
const bin = resolveOpenClawBin();
|
|
72
72
|
const stateDir = STATE_DIR;
|
|
73
73
|
try {
|
|
74
|
-
await execFileAsync(bin, ["gateway", "stop"], {
|
|
74
|
+
await execFileAsync(bin, openclawArgs(["gateway", "stop"]), {
|
|
75
75
|
env: openclawEnv(stateDir),
|
|
76
76
|
});
|
|
77
77
|
}
|
|
@@ -94,7 +94,7 @@ async function registerChannels(bin, stateDir, config) {
|
|
|
94
94
|
const env = openclawEnv(stateDir);
|
|
95
95
|
if (config.channels.whatsapp?.enabled && secrets.whatsapp) {
|
|
96
96
|
try {
|
|
97
|
-
await execFileAsync(bin, ["channels", "add", "--channel", "whatsapp"], { env });
|
|
97
|
+
await execFileAsync(bin, openclawArgs(["channels", "add", "--channel", "whatsapp"]), { env });
|
|
98
98
|
}
|
|
99
99
|
catch {
|
|
100
100
|
// Already exists — fine
|
|
@@ -102,7 +102,7 @@ async function registerChannels(bin, stateDir, config) {
|
|
|
102
102
|
}
|
|
103
103
|
if (config.channels.telegram?.enabled && secrets.telegram?.token) {
|
|
104
104
|
try {
|
|
105
|
-
await execFileAsync(bin, ["channels", "add", "--channel", "telegram", "--token", secrets.telegram.token], { env });
|
|
105
|
+
await execFileAsync(bin, openclawArgs(["channels", "add", "--channel", "telegram", "--token", secrets.telegram.token]), { env });
|
|
106
106
|
}
|
|
107
107
|
catch {
|
|
108
108
|
// Already exists — fine
|
|
@@ -126,7 +126,7 @@ async function registerChannels(bin, stateDir, config) {
|
|
|
126
126
|
}
|
|
127
127
|
if (config.channels.teams?.enabled && secrets.teams?.appSecret) {
|
|
128
128
|
try {
|
|
129
|
-
await execFileAsync(bin, ["channels", "add", "--channel", "teams"], { env });
|
|
129
|
+
await execFileAsync(bin, openclawArgs(["channels", "add", "--channel", "teams"]), { env });
|
|
130
130
|
}
|
|
131
131
|
catch {
|
|
132
132
|
// Already exists — fine
|
|
@@ -200,7 +200,7 @@ async function ensureAutoApprove(bin, env, gateway = false) {
|
|
|
200
200
|
const args = ["approvals", "set", "--stdin"];
|
|
201
201
|
if (gateway)
|
|
202
202
|
args.push("--gateway");
|
|
203
|
-
const proc = spawn(bin, args, { env, stdio: ["pipe", "pipe", "pipe"] });
|
|
203
|
+
const proc = spawn(bin, openclawArgs(args), { env, stdio: ["pipe", "pipe", "pipe"] });
|
|
204
204
|
proc.stdin.write(approvals);
|
|
205
205
|
proc.stdin.end();
|
|
206
206
|
await new Promise((resolve, reject) => {
|
|
@@ -237,7 +237,12 @@ async function seedWorkspace() {
|
|
|
237
237
|
console.warn("[leedab] workspace seed failed:", err);
|
|
238
238
|
}
|
|
239
239
|
}
|
|
240
|
-
async function waitForGateway(port,
|
|
240
|
+
async function waitForGateway(port,
|
|
241
|
+
// 240s gives comfortable headroom on Windows, where openclaw cold-start
|
|
242
|
+
// can take ~70s to bind (vs ~10s on macOS/Linux). Each health probe
|
|
243
|
+
// itself can sit for the 30s execFile timeout when the gateway isn't
|
|
244
|
+
// listening yet, so don't cut this too tight.
|
|
245
|
+
timeoutMs = 240000) {
|
|
241
246
|
const bin = resolveOpenClawBin();
|
|
242
247
|
const stateDir = STATE_DIR;
|
|
243
248
|
const start = Date.now();
|
|
@@ -245,7 +250,7 @@ async function waitForGateway(port, timeoutMs = 90000) {
|
|
|
245
250
|
try {
|
|
246
251
|
// Use openclaw's own health check which knows the WS protocol.
|
|
247
252
|
// Child timeout must exceed openclaw CLI cold-start time (~15s).
|
|
248
|
-
await execFileAsync(bin, ["gateway", "health"], {
|
|
253
|
+
await execFileAsync(bin, openclawArgs(["gateway", "health"]), {
|
|
249
254
|
env: openclawEnv(stateDir),
|
|
250
255
|
timeout: 30000,
|
|
251
256
|
});
|
|
@@ -4,7 +4,7 @@ import { resolve } from "node:path";
|
|
|
4
4
|
import { promisify } from "node:util";
|
|
5
5
|
import inquirer from "inquirer";
|
|
6
6
|
import chalk from "chalk";
|
|
7
|
-
import { resolveOpenClawBin, openclawEnv } from "../../openclaw.js";
|
|
7
|
+
import { resolveOpenClawBin, openclawArgs, openclawEnv } from "../../openclaw.js";
|
|
8
8
|
import { STATE_DIR } from "../../paths.js";
|
|
9
9
|
const execFileAsync = promisify(execFile);
|
|
10
10
|
const PROVIDERS = [
|
|
@@ -142,7 +142,7 @@ async function passKeyToOpenClaw(flag, apiKey) {
|
|
|
142
142
|
const bin = resolveOpenClawBin();
|
|
143
143
|
const env = openclawEnv();
|
|
144
144
|
try {
|
|
145
|
-
await execFileAsync(bin, [
|
|
145
|
+
await execFileAsync(bin, openclawArgs([
|
|
146
146
|
"onboard",
|
|
147
147
|
"--non-interactive",
|
|
148
148
|
"--accept-risk",
|
|
@@ -154,7 +154,7 @@ async function passKeyToOpenClaw(flag, apiKey) {
|
|
|
154
154
|
"--skip-ui",
|
|
155
155
|
flag,
|
|
156
156
|
apiKey,
|
|
157
|
-
], { env, timeout: 30000 });
|
|
157
|
+
]), { env, timeout: 30000 });
|
|
158
158
|
}
|
|
159
159
|
catch {
|
|
160
160
|
// Runtime onboard may exit non-zero if already configured — that's fine.
|
|
@@ -3,7 +3,7 @@ import { promisify } from "node:util";
|
|
|
3
3
|
import inquirer from "inquirer";
|
|
4
4
|
import chalk from "chalk";
|
|
5
5
|
import { spawnBranded } from "../../brand.js";
|
|
6
|
-
import { resolveOpenClawBin, openclawEnv } from "../../openclaw.js";
|
|
6
|
+
import { resolveOpenClawBin, openclawArgs, openclawEnv } from "../../openclaw.js";
|
|
7
7
|
const execFileAsync = promisify(execFile);
|
|
8
8
|
export async function onboardWhatsApp() {
|
|
9
9
|
console.log(chalk.bold("\n Step 2: WhatsApp") + chalk.dim(" (requires phone)") + "\n");
|
|
@@ -21,7 +21,7 @@ export async function onboardWhatsApp() {
|
|
|
21
21
|
// Register WhatsApp channel
|
|
22
22
|
console.log(chalk.dim("\n Registering WhatsApp channel..."));
|
|
23
23
|
try {
|
|
24
|
-
await execFileAsync(bin, ["channels", "add", "--channel", "whatsapp"], {
|
|
24
|
+
await execFileAsync(bin, openclawArgs(["channels", "add", "--channel", "whatsapp"]), {
|
|
25
25
|
env: openclawEnv(),
|
|
26
26
|
});
|
|
27
27
|
}
|
|
@@ -34,7 +34,7 @@ export async function onboardWhatsApp() {
|
|
|
34
34
|
// Run QR code pairing — this is interactive, needs stdio
|
|
35
35
|
console.log(chalk.cyan("\n Scan the QR code below with WhatsApp on your phone:"));
|
|
36
36
|
console.log(chalk.dim(" Open WhatsApp > Settings > Linked Devices > Link a Device\n"));
|
|
37
|
-
const loginProcess = spawnBranded(bin, ["channels", "login", "--channel", "whatsapp", "--verbose"], {
|
|
37
|
+
const loginProcess = spawnBranded(bin, openclawArgs(["channels", "login", "--channel", "whatsapp", "--verbose"]), {
|
|
38
38
|
env: openclawEnv(),
|
|
39
39
|
});
|
|
40
40
|
const exitCode = await new Promise((resolve) => {
|
package/dist/openclaw.d.ts
CHANGED
|
@@ -1,8 +1,15 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
2
|
+
* Command to spawn for invoking the OpenClaw runtime. We always go through
|
|
3
|
+
* the current node binary so this works on Windows (where `child_process.spawn`
|
|
4
|
+
* cannot directly execute a `.mjs` file via shebang).
|
|
4
5
|
*/
|
|
5
6
|
export declare function resolveOpenClawBin(): string;
|
|
7
|
+
/**
|
|
8
|
+
* Build the args array for an OpenClaw invocation. Prepends the resolved
|
|
9
|
+
* `openclaw.mjs` script path so callers can write
|
|
10
|
+
* `spawn(bin, openclawArgs(["gateway", "run"]), ...)`.
|
|
11
|
+
*/
|
|
12
|
+
export declare function openclawArgs(args?: string[]): string[];
|
|
6
13
|
/**
|
|
7
14
|
* Build the env object for the agent runtime.
|
|
8
15
|
*/
|
package/dist/openclaw.js
CHANGED
|
@@ -2,13 +2,25 @@ import { createRequire } from "node:module";
|
|
|
2
2
|
import { dirname, resolve } from "node:path";
|
|
3
3
|
import { STATE_DIR } from "./paths.js";
|
|
4
4
|
const require = createRequire(import.meta.url);
|
|
5
|
+
function openclawScript() {
|
|
6
|
+
const openclawMain = require.resolve("openclaw");
|
|
7
|
+
return resolve(dirname(openclawMain), "..", "openclaw.mjs");
|
|
8
|
+
}
|
|
5
9
|
/**
|
|
6
|
-
*
|
|
7
|
-
*
|
|
10
|
+
* Command to spawn for invoking the OpenClaw runtime. We always go through
|
|
11
|
+
* the current node binary so this works on Windows (where `child_process.spawn`
|
|
12
|
+
* cannot directly execute a `.mjs` file via shebang).
|
|
8
13
|
*/
|
|
9
14
|
export function resolveOpenClawBin() {
|
|
10
|
-
|
|
11
|
-
|
|
15
|
+
return process.execPath;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Build the args array for an OpenClaw invocation. Prepends the resolved
|
|
19
|
+
* `openclaw.mjs` script path so callers can write
|
|
20
|
+
* `spawn(bin, openclawArgs(["gateway", "run"]), ...)`.
|
|
21
|
+
*/
|
|
22
|
+
export function openclawArgs(args = []) {
|
|
23
|
+
return [openclawScript(), ...args];
|
|
12
24
|
}
|
|
13
25
|
/**
|
|
14
26
|
* Build the env object for the agent runtime.
|
|
@@ -3,7 +3,7 @@ import { resolve } from "node:path";
|
|
|
3
3
|
import { execFile } from "node:child_process";
|
|
4
4
|
import { promisify } from "node:util";
|
|
5
5
|
import { STATE_DIR } from "../paths.js";
|
|
6
|
-
import { resolveOpenClawBin, openclawEnv } from "../openclaw.js";
|
|
6
|
+
import { resolveOpenClawBin, openclawArgs, openclawEnv } from "../openclaw.js";
|
|
7
7
|
import { readOverlay } from "./permissions.js";
|
|
8
8
|
const execFileAsync = promisify(execFile);
|
|
9
9
|
const MESSAGING_CHANNELS = ["whatsapp", "telegram", "teams"];
|
|
@@ -53,8 +53,8 @@ async function restartGatewayQuietly() {
|
|
|
53
53
|
try {
|
|
54
54
|
const bin = resolveOpenClawBin();
|
|
55
55
|
const env = openclawEnv(STATE_DIR);
|
|
56
|
-
await execFileAsync(bin, ["gateway", "health"], { env, timeout: 3000 });
|
|
57
|
-
await execFileAsync(bin, ["gateway", "restart"], { env, timeout: 10000 });
|
|
56
|
+
await execFileAsync(bin, openclawArgs(["gateway", "health"]), { env, timeout: 3000 });
|
|
57
|
+
await execFileAsync(bin, openclawArgs(["gateway", "restart"]), { env, timeout: 10000 });
|
|
58
58
|
}
|
|
59
59
|
catch {
|
|
60
60
|
// Gateway not running — nothing to restart.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "leedab",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.0",
|
|
4
4
|
"description": "LeedAB — Your enterprise AI agent. Local-first, private by default.",
|
|
5
5
|
"license": "SEE LICENSE IN LICENSE",
|
|
6
6
|
"author": "LeedAB <hello@leedab.com>",
|
|
@@ -13,14 +13,17 @@
|
|
|
13
13
|
"files": [
|
|
14
14
|
"bin/",
|
|
15
15
|
"dist/",
|
|
16
|
+
"apps/console/.next/standalone/",
|
|
17
|
+
"apps/console/.next/static/",
|
|
18
|
+
"apps/console/public/",
|
|
16
19
|
"LICENSE"
|
|
17
20
|
],
|
|
18
21
|
"scripts": {
|
|
19
|
-
"build": "tsc &&
|
|
22
|
+
"build": "tsc && node scripts/copy-templates.mjs",
|
|
20
23
|
"dev": "tsc --watch",
|
|
21
24
|
"start": "node bin/leedab.js",
|
|
22
25
|
"lint": "eslint src/",
|
|
23
|
-
"prepublishOnly": "npm run build"
|
|
26
|
+
"prepublishOnly": "npm run build && npm --prefix apps/console install && npm --prefix apps/console run build"
|
|
24
27
|
},
|
|
25
28
|
"dependencies": {
|
|
26
29
|
"@aws-sdk/client-bedrock": "^3.1021.0",
|