leedab 0.1.1 → 0.1.3
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 +23 -45
- package/dist/dashboard/routes.js +15 -0
- package/dist/dashboard/static/index.html +28 -2
- package/dist/onboard/index.js +27 -12
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,39 +1,36 @@
|
|
|
1
1
|
# LeedAB
|
|
2
2
|
|
|
3
|
-
**
|
|
3
|
+
**Living OS. An autonomous AI agent that runs on your machine.**
|
|
4
4
|
|
|
5
|
-
LeedAB
|
|
5
|
+
LeedAB installs on any Mac, Windows, or Linux computer and becomes an AI agent for your enterprise. It talks to your team on WhatsApp and Telegram, learns your operations, uses your computer like a team member would, and keeps every byte of data on your hardware.
|
|
6
6
|
|
|
7
|
-
##
|
|
7
|
+
## Install
|
|
8
8
|
|
|
9
9
|
```bash
|
|
10
|
-
|
|
11
|
-
npx leedab onboard
|
|
12
|
-
npx leedab start
|
|
10
|
+
curl -fsSL https://leedab.com/install.sh | bash
|
|
13
11
|
```
|
|
14
12
|
|
|
15
|
-
|
|
13
|
+
Then run the setup wizard:
|
|
16
14
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
## What You Get
|
|
22
|
-
|
|
23
|
-
**Multi-channel messaging** — your team talks to the agent on Telegram, WhatsApp, or Teams. Each user gets an isolated session with their own context.
|
|
24
|
-
|
|
25
|
-
**Persistent memory** — the agent remembers across conversations. Daily notes, long-term memory, and workspace files survive restarts.
|
|
15
|
+
```bash
|
|
16
|
+
leedab onboard
|
|
17
|
+
```
|
|
26
18
|
|
|
27
|
-
|
|
19
|
+
You'll need a license key. Get one at [leedab.com](https://www.leedab.com).
|
|
28
20
|
|
|
29
|
-
|
|
21
|
+
## What you get
|
|
30
22
|
|
|
31
|
-
|
|
23
|
+
- **Messaging** - your team talks to the agent on WhatsApp, Telegram, or Slack. No new app to learn.
|
|
24
|
+
- **Computer use** - the agent opens browsers, reads screens, navigates apps, and fills forms.
|
|
25
|
+
- **Memory** - persistent across conversations. It learns your business and never starts from scratch.
|
|
26
|
+
- **Credential vault** - AES-256-GCM encrypted. The agent logs into services using stored credentials.
|
|
27
|
+
- **Dashboard** - web console at `localhost:3000` with agent status, sessions, and team management.
|
|
28
|
+
- **Local-first** - files, credentials, memory, and logs stay on your machine.
|
|
32
29
|
|
|
33
30
|
## Commands
|
|
34
31
|
|
|
35
32
|
```
|
|
36
|
-
leedab onboard
|
|
33
|
+
leedab onboard Setup wizard
|
|
37
34
|
leedab start Start the agent and dashboard
|
|
38
35
|
leedab terminal Chat with the agent in terminal
|
|
39
36
|
leedab stop Stop the agent
|
|
@@ -47,37 +44,18 @@ leedab --help All commands
|
|
|
47
44
|
|
|
48
45
|
## Requirements
|
|
49
46
|
|
|
50
|
-
-
|
|
51
|
-
-
|
|
52
|
-
-
|
|
47
|
+
- macOS, Windows, or Linux
|
|
48
|
+
- A license key from [leedab.com](https://www.leedab.com)
|
|
49
|
+
- An LLM API key (Anthropic, OpenAI, Google, DeepSeek, or OpenRouter)
|
|
53
50
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
LeedAB runs an AI agent gateway on your machine with enterprise onboarding, a management dashboard, encrypted credential vault, team roles, and audit logging.
|
|
57
|
-
|
|
58
|
-
```
|
|
59
|
-
┌─────────────────────────────────────────────┐
|
|
60
|
-
│ LeedAB │
|
|
61
|
-
│ ┌─────────┐ ┌──────────┐ ┌──────────────┐ │
|
|
62
|
-
│ │ Onboard │ │Dashboard │ │ Vault/Audit │ │
|
|
63
|
-
│ └─────────┘ └──────────┘ └──────────────┘ │
|
|
64
|
-
│ ┌─────────────────────────────────────────┐ │
|
|
65
|
-
│ │ Agent Gateway │ │
|
|
66
|
-
│ │ Sessions │ Skills │ Memory │ Browser │ │
|
|
67
|
-
│ └─────────────────────────────────────────┘ │
|
|
68
|
-
│ ┌──────────┐ ┌──────────┐ ┌─────────────┐ │
|
|
69
|
-
│ │ Telegram │ │ WhatsApp │ │ Teams │ │
|
|
70
|
-
│ └──────────┘ └──────────┘ └─────────────┘ │
|
|
71
|
-
└─────────────────────────────────────────────┘
|
|
72
|
-
```
|
|
51
|
+
The one-liner installer handles Node.js and all dependencies for you.
|
|
73
52
|
|
|
74
53
|
## Security
|
|
75
54
|
|
|
76
|
-
- All data stored locally in `.leedab/`
|
|
55
|
+
- All data stored locally in `.leedab/`
|
|
77
56
|
- Credentials encrypted with AES-256-GCM
|
|
78
57
|
- Per-channel allowlists control who can message the agent
|
|
79
|
-
-
|
|
80
|
-
- Team roles: admin, operator, viewer
|
|
58
|
+
- Team roles with admin, operator, and viewer access
|
|
81
59
|
- Full audit log of every interaction
|
|
82
60
|
|
|
83
61
|
## License
|
package/dist/dashboard/routes.js
CHANGED
|
@@ -2,6 +2,7 @@ import { execFile, spawn } from "node:child_process";
|
|
|
2
2
|
import { readFile, writeFile, mkdir, readdir } from "node:fs/promises";
|
|
3
3
|
import { resolve } from "node:path";
|
|
4
4
|
import { promisify } from "node:util";
|
|
5
|
+
import { userInfo } from "node:os";
|
|
5
6
|
import { resolveOpenClawBin, openclawEnv } from "../openclaw.js";
|
|
6
7
|
import { addEntry, removeEntry, listEntries } from "../vault.js";
|
|
7
8
|
import { readAuditLog } from "../audit.js";
|
|
@@ -130,6 +131,20 @@ export function createRoutes(config) {
|
|
|
130
131
|
json(res, []);
|
|
131
132
|
}
|
|
132
133
|
},
|
|
134
|
+
/**
|
|
135
|
+
* GET /api/whoami — current OS user's first name
|
|
136
|
+
*/
|
|
137
|
+
"GET /api/whoami": async (_req, res) => {
|
|
138
|
+
let firstName = userInfo().username;
|
|
139
|
+
try {
|
|
140
|
+
const { stdout } = await execFileAsync("id", ["-F"], { timeout: 2000 });
|
|
141
|
+
const fullName = stdout.trim();
|
|
142
|
+
if (fullName)
|
|
143
|
+
firstName = fullName.split(/\s+/)[0];
|
|
144
|
+
}
|
|
145
|
+
catch { }
|
|
146
|
+
json(res, { name: firstName });
|
|
147
|
+
},
|
|
133
148
|
/**
|
|
134
149
|
* GET /api/status — channel health status
|
|
135
150
|
*/
|
|
@@ -297,6 +297,10 @@
|
|
|
297
297
|
.msg-bubble.agent strong { font-weight: 600; }
|
|
298
298
|
.msg-bubble.agent em { font-style: italic; }
|
|
299
299
|
.msg-bubble.agent blockquote { border-left: 3px solid var(--accent); padding-left: 10px; color: var(--text-dim); margin: 6px 0; }
|
|
300
|
+
.msg-bubble.agent table { width: 100%; border-collapse: collapse; margin: 8px 0; font-size: 13px; }
|
|
301
|
+
.msg-bubble.agent th, .msg-bubble.agent td { text-align: left; padding: 6px 10px; border: 1px solid var(--border); }
|
|
302
|
+
.msg-bubble.agent th { background: var(--surface-raised); font-weight: 600; color: var(--text-secondary); font-size: 12px; }
|
|
303
|
+
.msg-bubble.agent td { color: var(--text); }
|
|
300
304
|
|
|
301
305
|
/* Thinking */
|
|
302
306
|
.thinking-row {
|
|
@@ -635,6 +639,15 @@
|
|
|
635
639
|
const params = new URLSearchParams(window.location.search);
|
|
636
640
|
let sessionId = params.get("session") || "console";
|
|
637
641
|
|
|
642
|
+
let userName = "You";
|
|
643
|
+
let userInitial = "Y";
|
|
644
|
+
fetch("/api/whoami").then(r => r.json()).then(d => {
|
|
645
|
+
if (d.name) {
|
|
646
|
+
userName = d.name;
|
|
647
|
+
userInitial = d.name.charAt(0).toUpperCase();
|
|
648
|
+
}
|
|
649
|
+
}).catch(() => {});
|
|
650
|
+
|
|
638
651
|
input.addEventListener("input", () => {
|
|
639
652
|
input.style.height = "auto";
|
|
640
653
|
input.style.height = Math.min(input.scrollHeight, 120) + "px";
|
|
@@ -727,7 +740,7 @@
|
|
|
727
740
|
img.alt = "LeedAB";
|
|
728
741
|
avatar.appendChild(img);
|
|
729
742
|
} else {
|
|
730
|
-
avatar.textContent =
|
|
743
|
+
avatar.textContent = userInitial;
|
|
731
744
|
}
|
|
732
745
|
|
|
733
746
|
const content = document.createElement("div");
|
|
@@ -735,7 +748,7 @@
|
|
|
735
748
|
|
|
736
749
|
const meta = document.createElement("div");
|
|
737
750
|
meta.className = "msg-meta";
|
|
738
|
-
meta.innerHTML = `<span class="msg-name">${type === "agent" ? "LeedAB" :
|
|
751
|
+
meta.innerHTML = `<span class="msg-name">${type === "agent" ? "LeedAB" : userName}</span><span class="msg-time">${now()}</span>`;
|
|
739
752
|
|
|
740
753
|
const bubble = document.createElement("div");
|
|
741
754
|
bubble.className = `msg-bubble ${type}`;
|
|
@@ -760,6 +773,19 @@
|
|
|
760
773
|
let h = text.replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">");
|
|
761
774
|
// Code blocks
|
|
762
775
|
h = h.replace(/```(\w*)\n([\s\S]*?)```/g, (_,lang,code) => `<pre><code>${code.trim()}</code></pre>`);
|
|
776
|
+
// Tables (must run before inline formatting)
|
|
777
|
+
h = h.replace(/(\n|^)(\|[^\n]+\|\n\|[\s\-:|]+\|\n(?:\|[^\n]+\|\n?)+)/g, function(_, prefix, block) {
|
|
778
|
+
const rows = block.trim().split("\n");
|
|
779
|
+
const parseRow = r => r.replace(/^\|/, "").replace(/\|$/, "").split("|").map(c => c.trim());
|
|
780
|
+
const headers = parseRow(rows[0]);
|
|
781
|
+
const body = rows.slice(2).map(parseRow);
|
|
782
|
+
let t = "<table><thead><tr>" + headers.map(h => `<th>${h}</th>`).join("") + "</tr></thead><tbody>";
|
|
783
|
+
for (const cells of body) {
|
|
784
|
+
t += "<tr>" + cells.map(c => `<td>${c}</td>`).join("") + "</tr>";
|
|
785
|
+
}
|
|
786
|
+
t += "</tbody></table>";
|
|
787
|
+
return prefix + t;
|
|
788
|
+
});
|
|
763
789
|
// Inline code
|
|
764
790
|
h = h.replace(/`([^`]+)`/g, "<code>$1</code>");
|
|
765
791
|
// Bold
|
package/dist/onboard/index.js
CHANGED
|
@@ -27,15 +27,23 @@ export async function runOnboard() {
|
|
|
27
27
|
console.log(chalk.dim(" Get your key at ") + chalk.cyan("https://leedab.com") + "\n");
|
|
28
28
|
let licensed = false;
|
|
29
29
|
while (!licensed) {
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
30
|
+
let licenseKey;
|
|
31
|
+
try {
|
|
32
|
+
const answers = await inquirer.prompt([
|
|
33
|
+
{
|
|
34
|
+
type: "password",
|
|
35
|
+
name: "licenseKey",
|
|
36
|
+
message: "License key:",
|
|
37
|
+
mask: "*",
|
|
38
|
+
validate: (v) => v.trim().startsWith("am_live_") || "Key should start with am_live_",
|
|
39
|
+
},
|
|
40
|
+
]);
|
|
41
|
+
licenseKey = answers.licenseKey;
|
|
42
|
+
}
|
|
43
|
+
catch {
|
|
44
|
+
console.log(chalk.dim("\n\n Exiting. Run `leedab onboard` when you have your key.\n"));
|
|
45
|
+
process.exit(0);
|
|
46
|
+
}
|
|
39
47
|
process.stdout.write(chalk.dim(" Validating..."));
|
|
40
48
|
const result = await validateLicenseKey(licenseKey.trim());
|
|
41
49
|
if (result.valid) {
|
|
@@ -45,9 +53,16 @@ export async function runOnboard() {
|
|
|
45
53
|
}
|
|
46
54
|
else {
|
|
47
55
|
console.log(chalk.red(" invalid key."));
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
56
|
+
let retry = true;
|
|
57
|
+
try {
|
|
58
|
+
const answers = await inquirer.prompt([
|
|
59
|
+
{ type: "confirm", name: "retry", message: "Try again?", default: true },
|
|
60
|
+
]);
|
|
61
|
+
retry = answers.retry;
|
|
62
|
+
}
|
|
63
|
+
catch {
|
|
64
|
+
retry = false;
|
|
65
|
+
}
|
|
51
66
|
if (!retry) {
|
|
52
67
|
console.log(chalk.red("\n A valid license key is required to use LeedAB."));
|
|
53
68
|
console.log(chalk.dim(" Get one at ") + chalk.cyan("https://leedab.com\n"));
|