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 CHANGED
@@ -1,39 +1,36 @@
1
1
  # LeedAB
2
2
 
3
- **Your enterprise AI agent. Local-first, private by default.**
3
+ **Living OS. An autonomous AI agent that runs on your machine.**
4
4
 
5
- LeedAB deploys an AI agent on your own hardware Mac, Linux, or VPS. It connects to your team's messaging apps, remembers context across conversations, handles tasks with browser automation, and keeps every byte of data on your machine.
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
- ## Quick Start
7
+ ## Install
8
8
 
9
9
  ```bash
10
- mkdir my-agent && cd my-agent
11
- npx leedab onboard
12
- npx leedab start
10
+ curl -fsSL https://leedab.com/install.sh | bash
13
11
  ```
14
12
 
15
- That's it. The onboarding wizard walks you through:
13
+ Then run the setup wizard:
16
14
 
17
- 1. **LLM provider** — Anthropic, OpenAI, Gemini, DeepSeek, Bedrock, or OpenRouter
18
- 2. **Channels** — Telegram, WhatsApp, Microsoft Teams
19
- 3. **Credential vault** — encrypted storage for service logins the agent uses
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
- **Credential vault** AES-256-GCM encrypted storage. The agent logs into services (Gmail, CRM, carrier portals) using stored credentials via browser automation.
19
+ You'll need a license key. Get one at [leedab.com](https://www.leedab.com).
28
20
 
29
- **Dashboard** web console at `localhost:3000` showing agent status, channel health, session history, and team management.
21
+ ## What you get
30
22
 
31
- **Your data stays local** files, credentials, memory, and logs live on your machine. Messages route through your channel provider and prompts go to your AI provider, but nothing is stored externally.
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 Interactive setup wizard
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
- - Node.js 22+
51
- - An LLM API key (Anthropic, OpenAI, etc.)
52
- - At least one messaging channel (Telegram is the easiest to set up)
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
- ## How It Works
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/` (gitignored)
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
- - DM policies: open, pairing (approval required), or closed
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
@@ -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 = "A";
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" : "Admin"}</span><span class="msg-time">${now()}</span>`;
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,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;");
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
@@ -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
- const { licenseKey } = await inquirer.prompt([
31
- {
32
- type: "password",
33
- name: "licenseKey",
34
- message: "License key:",
35
- mask: "*",
36
- validate: (v) => v.trim().startsWith("am_live_") || "Key should start with am_live_",
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
- const { retry } = await inquirer.prompt([
49
- { type: "confirm", name: "retry", message: "Try again?", default: true },
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"));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "leedab",
3
- "version": "0.1.1",
3
+ "version": "0.1.3",
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>",