levix-bot 2.1.1 → 2.2.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/README.md +3 -1
- package/app.cjs +13 -5
- package/package.json +1 -1
- package/public/dashboard.css +2167 -519
- package/public/dashboard.js +100 -36
- package/scheduler.cjs +84 -17
- package/src/bootstrap/core.js +17 -11
- package/src/bootstrap/session-startup-policy.js +23 -0
- package/src/commands/autoschedule.cjs +19 -42
- package/src/config/ai-identity.cjs +4 -3
- package/src/config/brand.cjs +3 -0
- package/src/db/db.cjs +8 -0
- package/src/db/store.cjs +25 -5
- package/src/routes/dashboard.api.esm.js +60 -3
- package/src/utils/recurrence.cjs +161 -0
- package/src/utils/storage.cjs +1 -0
- package/src/utils/storage.esm.js +1 -0
- package/views/dashboard.ejs +464 -94
- package/views/login.ejs +50 -86
- package/views/qr.ejs +51 -53
- package/views/setup.ejs +67 -110
package/views/login.ejs
CHANGED
|
@@ -7,99 +7,63 @@
|
|
|
7
7
|
<link rel="icon" type="image/png" sizes="32x32" href="/brand/icon-32.png" />
|
|
8
8
|
<link rel="apple-touch-icon" href="/brand/apple-touch-icon.png" />
|
|
9
9
|
<link rel="stylesheet" href="/dashboard.css" />
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
var(--bg);
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
.login {
|
|
22
|
-
width: 100%;
|
|
23
|
-
max-width: 380px;
|
|
24
|
-
text-align: center;
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
.login .brand-lockup {
|
|
28
|
-
margin-bottom: 22px;
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
.login .card {
|
|
32
|
-
text-align: left;
|
|
33
|
-
padding: 24px;
|
|
34
|
-
box-shadow: var(--shadow);
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
.login h1 {
|
|
38
|
-
font-size: 1.05rem;
|
|
39
|
-
font-weight: 650;
|
|
40
|
-
margin-bottom: 4px;
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
.login p.sub {
|
|
44
|
-
color: var(--muted);
|
|
45
|
-
font-size: 0.86rem;
|
|
46
|
-
margin-bottom: 18px;
|
|
47
|
-
}
|
|
10
|
+
</head>
|
|
11
|
+
<body class="gateway-page">
|
|
12
|
+
<main class="gateway-shell">
|
|
13
|
+
<section class="gateway-story" aria-label="About <%= brand.name %>">
|
|
14
|
+
<div class="brand-lockup" aria-label="<%= brand.name %>">
|
|
15
|
+
<img src="/brand/mark.svg" alt="" />
|
|
16
|
+
<span><strong><%= brand.name %></strong><small><%= brand.tagline %></small></span>
|
|
17
|
+
</div>
|
|
48
18
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
19
|
+
<div class="gateway-copy">
|
|
20
|
+
<span class="eyebrow">Self-hosted by design</span>
|
|
21
|
+
<h2>The quiet control room behind every chat.</h2>
|
|
22
|
+
<p>Connection, commands, memory, and moderation stay together—on your server and within reach.</p>
|
|
23
|
+
</div>
|
|
54
24
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
background: color-mix(in srgb, var(--danger) 14%, transparent);
|
|
60
|
-
border: 1px solid color-mix(in srgb, var(--danger) 45%, transparent);
|
|
61
|
-
color: var(--danger);
|
|
62
|
-
font-size: 0.85rem;
|
|
63
|
-
text-align: center;
|
|
64
|
-
}
|
|
25
|
+
<div class="gateway-mark" aria-hidden="true">
|
|
26
|
+
<span class="gateway-ring"></span>
|
|
27
|
+
<img src="/brand/mark.svg" alt="" />
|
|
28
|
+
</div>
|
|
65
29
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
color: var(--faint);
|
|
69
|
-
font-size: 0.78rem;
|
|
70
|
-
}
|
|
71
|
-
</style>
|
|
72
|
-
</head>
|
|
73
|
-
<body>
|
|
74
|
-
<div class="login">
|
|
75
|
-
<div class="brand-lockup" aria-label="<%= brand.name %>">
|
|
76
|
-
<img src="/brand/mark.svg" alt="" />
|
|
77
|
-
<strong><%= brand.name %></strong>
|
|
78
|
-
</div>
|
|
30
|
+
<p class="gateway-trust"><span></span> Private workspace · direct control</p>
|
|
31
|
+
</section>
|
|
79
32
|
|
|
80
|
-
<
|
|
81
|
-
<
|
|
82
|
-
|
|
33
|
+
<section class="gateway-panel">
|
|
34
|
+
<div class="gateway-panel-inner">
|
|
35
|
+
<form class="card auth-card" action="/login" method="post">
|
|
36
|
+
<div class="auth-head">
|
|
37
|
+
<span class="eyebrow">Secure access</span>
|
|
38
|
+
<h1>Welcome back</h1>
|
|
39
|
+
<p class="sub">Sign in to manage your Levix workspace.</p>
|
|
40
|
+
</div>
|
|
83
41
|
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
42
|
+
<label class="field">
|
|
43
|
+
<span>Control panel password</span>
|
|
44
|
+
<input
|
|
45
|
+
type="password"
|
|
46
|
+
name="password"
|
|
47
|
+
autocomplete="current-password"
|
|
48
|
+
placeholder="Enter your password"
|
|
49
|
+
autofocus
|
|
50
|
+
required
|
|
51
|
+
/>
|
|
52
|
+
</label>
|
|
94
53
|
|
|
95
|
-
|
|
54
|
+
<button type="submit" class="btn btn-primary auth-submit">
|
|
55
|
+
Open workspace
|
|
56
|
+
<svg viewBox="0 0 24 24" aria-hidden="true"><path d="M5 12h14M13 6l6 6-6 6"/></svg>
|
|
57
|
+
</button>
|
|
96
58
|
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
59
|
+
<% if (error) { %>
|
|
60
|
+
<div class="error" role="alert"><%= error %></div>
|
|
61
|
+
<% } %>
|
|
62
|
+
</form>
|
|
101
63
|
|
|
102
|
-
|
|
103
|
-
|
|
64
|
+
<div class="foot"><%= brand.name %> by <%= brand.developer %> · <%= brand.studio %></div>
|
|
65
|
+
</div>
|
|
66
|
+
</section>
|
|
67
|
+
</main>
|
|
104
68
|
</body>
|
|
105
69
|
</html>
|
package/views/qr.ejs
CHANGED
|
@@ -7,65 +7,63 @@
|
|
|
7
7
|
<link rel="icon" type="image/png" sizes="32x32" href="/brand/icon-32.png" />
|
|
8
8
|
<link rel="apple-touch-icon" href="/brand/apple-touch-icon.png" />
|
|
9
9
|
<link rel="stylesheet" href="/dashboard.css" />
|
|
10
|
-
<style>
|
|
11
|
-
body {
|
|
12
|
-
display: grid;
|
|
13
|
-
place-items: center;
|
|
14
|
-
padding: 24px;
|
|
15
|
-
background:
|
|
16
|
-
radial-gradient(900px 500px at 15% -10%, rgba(22, 82, 240, 0.2), transparent 60%),
|
|
17
|
-
var(--bg);
|
|
18
|
-
}
|
|
19
|
-
.wrap {
|
|
20
|
-
width: 100%;
|
|
21
|
-
max-width: 430px;
|
|
22
|
-
text-align: center;
|
|
23
|
-
}
|
|
24
|
-
.wrap .brand-lockup {
|
|
25
|
-
margin-bottom: 20px;
|
|
26
|
-
}
|
|
27
|
-
.wrap .card {
|
|
28
|
-
box-shadow: var(--shadow);
|
|
29
|
-
}
|
|
30
|
-
.foot {
|
|
31
|
-
margin-top: 18px;
|
|
32
|
-
color: var(--faint);
|
|
33
|
-
font-size: 0.78rem;
|
|
34
|
-
}
|
|
35
|
-
</style>
|
|
36
10
|
</head>
|
|
37
|
-
<body>
|
|
38
|
-
<
|
|
39
|
-
<
|
|
40
|
-
<
|
|
41
|
-
|
|
42
|
-
|
|
11
|
+
<body class="gateway-page">
|
|
12
|
+
<main class="gateway-shell">
|
|
13
|
+
<section class="gateway-story" aria-label="About <%= brand.name %>">
|
|
14
|
+
<div class="brand-lockup" aria-label="<%= brand.name %>">
|
|
15
|
+
<img src="/brand/mark.svg" alt="" />
|
|
16
|
+
<span><strong><%= brand.name %></strong><small><%= brand.tagline %></small></span>
|
|
17
|
+
</div>
|
|
43
18
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
<h2>
|
|
47
|
-
<
|
|
48
|
-
<span class="pill" id="state"><span class="dot"></span>Waiting…</span>
|
|
19
|
+
<div class="gateway-copy">
|
|
20
|
+
<span class="eyebrow">A direct line</span>
|
|
21
|
+
<h2>Bring your assistant online.</h2>
|
|
22
|
+
<p>One scan securely links Levix to the WhatsApp account you choose.</p>
|
|
49
23
|
</div>
|
|
50
24
|
|
|
51
|
-
<div class="
|
|
52
|
-
<
|
|
53
|
-
<
|
|
54
|
-
<li>Open WhatsApp on the phone the bot should use.</li>
|
|
55
|
-
<li>Settings → Linked devices → Link a device.</li>
|
|
56
|
-
<li>Point the camera at the code above.</li>
|
|
57
|
-
</ol>
|
|
58
|
-
<div class="hint">
|
|
59
|
-
No code yet? Levix only connects when you ask it to — press
|
|
60
|
-
<strong>Start session</strong> on the dashboard's Connection screen.
|
|
61
|
-
</div>
|
|
25
|
+
<div class="gateway-mark" aria-hidden="true">
|
|
26
|
+
<span class="gateway-ring"></span>
|
|
27
|
+
<img src="/brand/mark.svg" alt="" />
|
|
62
28
|
</div>
|
|
63
29
|
|
|
64
|
-
<
|
|
65
|
-
</
|
|
30
|
+
<p class="gateway-trust"><span></span> Live pairing status</p>
|
|
31
|
+
</section>
|
|
32
|
+
|
|
33
|
+
<section class="gateway-panel">
|
|
34
|
+
<div class="gateway-panel-inner qr-panel-inner">
|
|
35
|
+
<div class="card auth-card qr-card">
|
|
36
|
+
<div class="card-head qr-card-head">
|
|
37
|
+
<div>
|
|
38
|
+
<span class="eyebrow">Link device</span>
|
|
39
|
+
<h1>Scan with WhatsApp</h1>
|
|
40
|
+
</div>
|
|
41
|
+
<span class="pill" id="state"><span class="dot"></span>Waiting…</span>
|
|
42
|
+
</div>
|
|
43
|
+
|
|
44
|
+
<div class="qr-box">
|
|
45
|
+
<div class="qr-frame" id="frame"></div>
|
|
46
|
+
<ol class="steps">
|
|
47
|
+
<li><span>Open</span> WhatsApp on the phone the bot should use.</li>
|
|
48
|
+
<li><span>Choose</span> Settings → Linked devices → Link a device.</li>
|
|
49
|
+
<li><span>Scan</span> the code shown above.</li>
|
|
50
|
+
</ol>
|
|
51
|
+
<div class="callout compact-callout">
|
|
52
|
+
<span class="callout-icon" aria-hidden="true">i</span>
|
|
53
|
+
<p>No code yet? Press <strong>Start session</strong> on the dashboard's Connection screen.</p>
|
|
54
|
+
</div>
|
|
55
|
+
</div>
|
|
66
56
|
|
|
67
|
-
|
|
68
|
-
|
|
57
|
+
<a class="btn qr-back" href="/">
|
|
58
|
+
<svg viewBox="0 0 24 24" aria-hidden="true"><path d="M19 12H5M11 6l-6 6 6 6"/></svg>
|
|
59
|
+
Back to dashboard
|
|
60
|
+
</a>
|
|
61
|
+
</div>
|
|
62
|
+
|
|
63
|
+
<div class="foot"><%= brand.name %> by <%= brand.developer %> · <%= brand.studio %></div>
|
|
64
|
+
</div>
|
|
65
|
+
</section>
|
|
66
|
+
</main>
|
|
69
67
|
|
|
70
68
|
<script src="/socket.io.min.js"></script>
|
|
71
69
|
<script src="/qrcode.min.js"></script>
|
|
@@ -114,7 +112,7 @@
|
|
|
114
112
|
|
|
115
113
|
socket.on("session", (state) => {
|
|
116
114
|
if (state.state === "connected") {
|
|
117
|
-
frame.innerHTML = '<div
|
|
115
|
+
frame.innerHTML = '<div class="qr-success">Linked ✓</div>';
|
|
118
116
|
painted = null;
|
|
119
117
|
setState("Connected", "on");
|
|
120
118
|
return;
|
package/views/setup.ejs
CHANGED
|
@@ -7,116 +7,73 @@
|
|
|
7
7
|
<link rel="icon" type="image/png" sizes="32x32" href="/brand/icon-32.png" />
|
|
8
8
|
<link rel="apple-touch-icon" href="/brand/apple-touch-icon.png" />
|
|
9
9
|
<link rel="stylesheet" href="/dashboard.css" />
|
|
10
|
-
<style>
|
|
11
|
-
body {
|
|
12
|
-
display: grid;
|
|
13
|
-
place-items: center;
|
|
14
|
-
padding: 24px;
|
|
15
|
-
background:
|
|
16
|
-
radial-gradient(900px 500px at 15% -10%, rgba(22, 82, 240, 0.22), transparent 60%),
|
|
17
|
-
radial-gradient(700px 420px at 90% 110%, rgba(33, 212, 253, 0.16), transparent 60%),
|
|
18
|
-
var(--bg);
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
.login {
|
|
22
|
-
width: 100%;
|
|
23
|
-
max-width: 400px;
|
|
24
|
-
text-align: center;
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
.login .brand-lockup {
|
|
28
|
-
margin-bottom: 22px;
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
.login .card {
|
|
32
|
-
text-align: left;
|
|
33
|
-
padding: 24px;
|
|
34
|
-
box-shadow: var(--shadow);
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
.login h1 {
|
|
38
|
-
font-size: 1.05rem;
|
|
39
|
-
font-weight: 650;
|
|
40
|
-
margin-bottom: 4px;
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
.login p.sub {
|
|
44
|
-
color: var(--muted);
|
|
45
|
-
font-size: 0.86rem;
|
|
46
|
-
margin-bottom: 18px;
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
.login button {
|
|
50
|
-
width: 100%;
|
|
51
|
-
margin-top: 6px;
|
|
52
|
-
padding: 11px;
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
.login .field {
|
|
56
|
-
margin-bottom: 12px;
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
.error {
|
|
60
|
-
margin-top: 12px;
|
|
61
|
-
padding: 9px 12px;
|
|
62
|
-
border-radius: var(--radius-sm);
|
|
63
|
-
background: color-mix(in srgb, var(--danger) 14%, transparent);
|
|
64
|
-
border: 1px solid color-mix(in srgb, var(--danger) 45%, transparent);
|
|
65
|
-
color: var(--danger);
|
|
66
|
-
font-size: 0.85rem;
|
|
67
|
-
text-align: center;
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
.foot {
|
|
71
|
-
margin-top: 20px;
|
|
72
|
-
color: var(--faint);
|
|
73
|
-
font-size: 0.78rem;
|
|
74
|
-
}
|
|
75
|
-
</style>
|
|
76
10
|
</head>
|
|
77
|
-
<body>
|
|
78
|
-
<
|
|
79
|
-
<
|
|
80
|
-
<
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
<
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
<
|
|
101
|
-
<
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
11
|
+
<body class="gateway-page">
|
|
12
|
+
<main class="gateway-shell">
|
|
13
|
+
<section class="gateway-story" aria-label="About <%= brand.name %>">
|
|
14
|
+
<div class="brand-lockup" aria-label="<%= brand.name %>">
|
|
15
|
+
<img src="/brand/mark.svg" alt="" />
|
|
16
|
+
<span><strong><%= brand.name %></strong><small><%= brand.tagline %></small></span>
|
|
17
|
+
</div>
|
|
18
|
+
|
|
19
|
+
<div class="gateway-copy">
|
|
20
|
+
<span class="eyebrow">Your first minute</span>
|
|
21
|
+
<h2>Make the workspace yours.</h2>
|
|
22
|
+
<p>Choose one strong password, then manage the assistant from a single private control room.</p>
|
|
23
|
+
</div>
|
|
24
|
+
|
|
25
|
+
<div class="gateway-mark" aria-hidden="true">
|
|
26
|
+
<span class="gateway-ring"></span>
|
|
27
|
+
<img src="/brand/mark.svg" alt="" />
|
|
28
|
+
</div>
|
|
29
|
+
|
|
30
|
+
<p class="gateway-trust"><span></span> One setup · no configuration files</p>
|
|
31
|
+
</section>
|
|
32
|
+
|
|
33
|
+
<section class="gateway-panel">
|
|
34
|
+
<div class="gateway-panel-inner">
|
|
35
|
+
<form class="card auth-card setup-card" action="/setup" method="post">
|
|
36
|
+
<div class="auth-head">
|
|
37
|
+
<span class="eyebrow">Create your workspace</span>
|
|
38
|
+
<h1>Welcome to <%= brand.name %></h1>
|
|
39
|
+
<p class="sub">Protect the linked WhatsApp account with at least <%= minLength %> characters.</p>
|
|
40
|
+
</div>
|
|
41
|
+
|
|
42
|
+
<% if (needsCode) { %>
|
|
43
|
+
<label class="field">
|
|
44
|
+
<span>Setup code</span>
|
|
45
|
+
<input type="text" name="code" autocomplete="off" autofocus required
|
|
46
|
+
placeholder="Printed in the bot's terminal" />
|
|
47
|
+
<small>Confirms you can access the server running Levix.</small>
|
|
48
|
+
</label>
|
|
49
|
+
<% } %>
|
|
50
|
+
|
|
51
|
+
<label class="field">
|
|
52
|
+
<span>Password</span>
|
|
53
|
+
<input type="password" name="password" autocomplete="new-password"
|
|
54
|
+
placeholder="At least <%= minLength %> characters"
|
|
55
|
+
minlength="<%= minLength %>" <%= needsCode ? "" : "autofocus" %> required />
|
|
56
|
+
</label>
|
|
57
|
+
|
|
58
|
+
<label class="field">
|
|
59
|
+
<span>Repeat password</span>
|
|
60
|
+
<input type="password" name="confirm" autocomplete="new-password"
|
|
61
|
+
placeholder="Type it again" minlength="<%= minLength %>" required />
|
|
62
|
+
</label>
|
|
63
|
+
|
|
64
|
+
<button type="submit" class="btn btn-primary auth-submit">
|
|
65
|
+
Create workspace
|
|
66
|
+
<svg viewBox="0 0 24 24" aria-hidden="true"><path d="M5 12h14M13 6l6 6-6 6"/></svg>
|
|
67
|
+
</button>
|
|
68
|
+
|
|
69
|
+
<% if (error) { %>
|
|
70
|
+
<div class="error" role="alert"><%= error %></div>
|
|
71
|
+
<% } %>
|
|
72
|
+
</form>
|
|
73
|
+
|
|
74
|
+
<div class="foot"><%= brand.name %> by <%= brand.developer %> · <%= brand.studio %></div>
|
|
75
|
+
</div>
|
|
76
|
+
</section>
|
|
77
|
+
</main>
|
|
121
78
|
</body>
|
|
122
79
|
</html>
|