leedab 0.2.6 → 0.3.1
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 +22 -16
- package/bin/leedab.js +13 -9
- package/dist/console-launcher.d.ts +11 -0
- package/dist/console-launcher.js +184 -0
- package/dist/gateway.js +39 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/license.d.ts +2 -0
- package/dist/license.js +2 -0
- package/dist/onboard/steps/provider.d.ts +7 -1
- package/dist/onboard/steps/provider.js +23 -7
- package/package.json +2 -2
- package/dist/dashboard/routes.d.ts +0 -17
- package/dist/dashboard/routes.js +0 -777
- package/dist/dashboard/server.d.ts +0 -2
- package/dist/dashboard/server.js +0 -85
- package/dist/dashboard/static/admin.html +0 -695
- package/dist/dashboard/static/favicon.png +0 -0
- package/dist/dashboard/static/index.html +0 -936
- package/dist/dashboard/static/logo-dark.png +0 -0
- package/dist/dashboard/static/logo-light.png +0 -0
- package/dist/dashboard/static/sessions.html +0 -162
- package/dist/dashboard/static/style.css +0 -493
package/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
**Living OS. An autonomous AI agent that runs on your machine.**
|
|
4
4
|
|
|
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
|
|
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, Telegram, and Microsoft Teams, 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
|
|
|
@@ -20,43 +20,49 @@ You'll need a license key. Get one at [leedab.com](https://www.leedab.com).
|
|
|
20
20
|
|
|
21
21
|
## What you get
|
|
22
22
|
|
|
23
|
-
- **Messaging** - your team talks to the agent on WhatsApp, Telegram, or
|
|
23
|
+
- **Messaging** - your team talks to the agent on WhatsApp, Telegram, or Microsoft Teams. No new app to learn.
|
|
24
|
+
- **Pre-built workflows** - one-click runs for credit notes, deduction reconciliation, attendance imports, delay detection, and more. Tailored to your license pack.
|
|
25
|
+
- **Specialised agents** - a roster of operators (COO, Procurement, CX, Finance, Sales, People, Concierge) tuned to your vertical. Founder packs unlock the full 26-agent internal team.
|
|
24
26
|
- **Computer use** - the agent opens browsers, reads screens, navigates apps, and fills forms.
|
|
25
27
|
- **Memory** - persistent across conversations. It learns your business and never starts from scratch.
|
|
26
28
|
- **Credential vault** - AES-256-GCM encrypted. The agent logs into services using stored credentials.
|
|
27
|
-
- **Dashboard** - web console at `localhost:3000` with
|
|
29
|
+
- **Dashboard** - web console at `localhost:3000` with operators, sessions, workflows, and admin.
|
|
28
30
|
- **Local-first** - files, credentials, memory, and logs stay on your machine.
|
|
29
31
|
|
|
30
32
|
## Commands
|
|
31
33
|
|
|
32
34
|
```
|
|
33
|
-
leedab onboard
|
|
34
|
-
leedab start
|
|
35
|
-
leedab
|
|
36
|
-
leedab
|
|
37
|
-
leedab
|
|
38
|
-
leedab
|
|
39
|
-
leedab
|
|
40
|
-
leedab
|
|
41
|
-
leedab
|
|
42
|
-
leedab
|
|
35
|
+
leedab onboard Setup wizard
|
|
36
|
+
leedab start Start the agent and dashboard
|
|
37
|
+
leedab dashboard Open the dashboard in your browser
|
|
38
|
+
leedab terminal Chat with the agent in terminal
|
|
39
|
+
leedab stop Stop the agent
|
|
40
|
+
leedab license Show license details — tier, seats, status
|
|
41
|
+
leedab configure model Change LLM provider or model
|
|
42
|
+
leedab vault add <svc> Store credentials for a service
|
|
43
|
+
leedab pairing add <ch> <u> Add users to a channel allowlist
|
|
44
|
+
leedab channels List configured channels
|
|
45
|
+
leedab team add <name> Add a team member
|
|
46
|
+
leedab sessions View conversation sessions
|
|
47
|
+
leedab --help All commands
|
|
43
48
|
```
|
|
44
49
|
|
|
45
50
|
## Requirements
|
|
46
51
|
|
|
47
52
|
- macOS, Windows, or Linux
|
|
48
53
|
- A license key from [leedab.com](https://www.leedab.com)
|
|
49
|
-
- An LLM API key (Anthropic, OpenAI, Google, DeepSeek, or
|
|
54
|
+
- An LLM API key (Anthropic, OpenAI, Google, DeepSeek, OpenRouter, or Amazon Bedrock)
|
|
50
55
|
|
|
51
56
|
The one-liner installer handles Node.js and all dependencies for you.
|
|
52
57
|
|
|
53
58
|
## Security
|
|
54
59
|
|
|
55
|
-
- All data stored locally in
|
|
60
|
+
- All data stored locally in `~/.leedab/`
|
|
56
61
|
- Credentials encrypted with AES-256-GCM
|
|
57
62
|
- Per-channel allowlists control who can message the agent
|
|
58
|
-
- Team roles with
|
|
63
|
+
- Team roles with owner, admin, and member access
|
|
59
64
|
- Full audit log of every interaction
|
|
65
|
+
- License payload signed (Ed25519) and verified locally so a tampered cache can't elevate tier
|
|
60
66
|
|
|
61
67
|
## License
|
|
62
68
|
|
package/bin/leedab.js
CHANGED
|
@@ -11,7 +11,7 @@ import { startGateway, stopGateway } from "../dist/gateway.js";
|
|
|
11
11
|
import { loadConfig, initConfig } from "../dist/config/index.js";
|
|
12
12
|
import { setupChannels } from "../dist/channels/index.js";
|
|
13
13
|
import { runOnboard } from "../dist/onboard/index.js";
|
|
14
|
-
import {
|
|
14
|
+
import { startConsole, stopConsole } from "../dist/console-launcher.js";
|
|
15
15
|
import { execBranded } from "../dist/brand.js";
|
|
16
16
|
import { resolveOpenClawBin, openclawEnv } from "../dist/openclaw.js";
|
|
17
17
|
import { addEntry, removeEntry, listEntries, encryptVault, decryptVault } from "../dist/vault.js";
|
|
@@ -203,7 +203,9 @@ program
|
|
|
203
203
|
const { logPath } = await startGateway(config);
|
|
204
204
|
clearInterval(rotate);
|
|
205
205
|
spinner.succeed(chalk.green("LeedAB is live"));
|
|
206
|
-
|
|
206
|
+
|
|
207
|
+
// Boot the Next.js console (apps/console).
|
|
208
|
+
const { logPath: consoleLogPath } = await startConsole(parseInt(opts.dashboardPort));
|
|
207
209
|
|
|
208
210
|
const enabledChannels = Object.entries(config.channels)
|
|
209
211
|
.filter(([_, v]) => v?.enabled)
|
|
@@ -214,12 +216,13 @@ program
|
|
|
214
216
|
console.log();
|
|
215
217
|
const dashUrl = `http://localhost:${opts.dashboardPort}`;
|
|
216
218
|
const link = `\x1b]8;;${dashUrl}\x1b\\${dashUrl}\x1b]8;;\x1b\\`;
|
|
217
|
-
console.log(` ${chalk.cyan(link)}
|
|
219
|
+
console.log(` ${chalk.cyan(link)} Console`);
|
|
218
220
|
if (enabledChannels.length > 0) {
|
|
219
221
|
console.log(` ${chalk.green("Channels")} ${enabledChannels.join(", ")}`);
|
|
220
222
|
}
|
|
221
223
|
console.log();
|
|
222
|
-
console.log(chalk.dim(`
|
|
224
|
+
console.log(chalk.dim(` Gateway log: ${logPath}`));
|
|
225
|
+
console.log(chalk.dim(` Console log: ${consoleLogPath}`));
|
|
223
226
|
console.log();
|
|
224
227
|
} catch (err) {
|
|
225
228
|
clearInterval(rotate);
|
|
@@ -230,14 +233,14 @@ program
|
|
|
230
233
|
|
|
231
234
|
program
|
|
232
235
|
.command("dashboard")
|
|
233
|
-
.description("Open the
|
|
236
|
+
.description("Open the LeedAB console (without starting the agent)")
|
|
234
237
|
.option("-c, --config <path>", "Path to config file", resolve(STATE_DIR, "config.json"))
|
|
235
|
-
.option("-p, --port <port>", "
|
|
238
|
+
.option("-p, --port <port>", "Console port", "3000")
|
|
236
239
|
.action(async (opts) => {
|
|
237
240
|
try {
|
|
238
|
-
|
|
239
|
-
console.log(chalk.bold("\n LeedAB
|
|
240
|
-
await
|
|
241
|
+
await loadConfig(opts.config);
|
|
242
|
+
console.log(chalk.bold("\n LeedAB Console\n"));
|
|
243
|
+
await startConsole(parseInt(opts.port));
|
|
241
244
|
console.log(chalk.dim(" Open the URL above in your browser.\n"));
|
|
242
245
|
} catch (err) {
|
|
243
246
|
console.error(chalk.red(`Failed: ${err.message}`));
|
|
@@ -283,6 +286,7 @@ program
|
|
|
283
286
|
.command("stop")
|
|
284
287
|
.description("Stop the LeedAB agent")
|
|
285
288
|
.action(async () => {
|
|
289
|
+
await stopConsole().catch(() => {});
|
|
286
290
|
await stopGateway();
|
|
287
291
|
console.log(chalk.green("LeedAB agent stopped."));
|
|
288
292
|
});
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Start the console subprocess on the given port. Resolves once the
|
|
3
|
+
* subprocess is alive (we don't poll the port; `next start` exits on its
|
|
4
|
+
* own if the port is taken or the build is missing, so a process-error
|
|
5
|
+
* would surface there).
|
|
6
|
+
*/
|
|
7
|
+
export declare function startConsole(port?: number): Promise<{
|
|
8
|
+
url: string;
|
|
9
|
+
logPath: string;
|
|
10
|
+
}>;
|
|
11
|
+
export declare function stopConsole(): Promise<void>;
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
// Spawn the LeedAB console (Next.js app at apps/console) as a subprocess.
|
|
2
|
+
// `leedab start` and `leedab dashboard` use this to swap the legacy
|
|
3
|
+
// src/dashboard server for the modern Next.js admin UI.
|
|
4
|
+
//
|
|
5
|
+
// Production-only: requires a built `.next` directory (`npm run build`
|
|
6
|
+
// inside apps/console). For dev, run `npm run dev` from apps/console
|
|
7
|
+
// directly — that gives hot reload and is independent of the gateway.
|
|
8
|
+
import { spawn } from "node:child_process";
|
|
9
|
+
import { createWriteStream, existsSync, readFileSync, unlinkSync, writeFileSync } from "node:fs";
|
|
10
|
+
import { mkdir } from "node:fs/promises";
|
|
11
|
+
import { resolve, dirname } from "node:path";
|
|
12
|
+
import { fileURLToPath } from "node:url";
|
|
13
|
+
import { STATE_DIR } from "./paths.js";
|
|
14
|
+
let consoleProcess = null;
|
|
15
|
+
// Cross-process handle for `leedab stop`. The leedab start process and the
|
|
16
|
+
// leedab stop process are different OS processes, so an in-memory ref to
|
|
17
|
+
// the spawned console isn't enough — stop reads the PID from this file.
|
|
18
|
+
const PID_FILE = resolve(STATE_DIR, "console.pid");
|
|
19
|
+
function writePidFile(pid) {
|
|
20
|
+
try {
|
|
21
|
+
writeFileSync(PID_FILE, String(pid), "utf8");
|
|
22
|
+
}
|
|
23
|
+
catch {
|
|
24
|
+
// best-effort — losing the pid file just means leedab stop falls back
|
|
25
|
+
// to the in-process handle, which is correct in the common case.
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
function readPidFile() {
|
|
29
|
+
try {
|
|
30
|
+
const raw = readFileSync(PID_FILE, "utf8").trim();
|
|
31
|
+
const pid = Number.parseInt(raw, 10);
|
|
32
|
+
return Number.isFinite(pid) && pid > 0 ? pid : null;
|
|
33
|
+
}
|
|
34
|
+
catch {
|
|
35
|
+
return null;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
function clearPidFile() {
|
|
39
|
+
try {
|
|
40
|
+
unlinkSync(PID_FILE);
|
|
41
|
+
}
|
|
42
|
+
catch {
|
|
43
|
+
// already gone, fine
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
function isAlive(pid) {
|
|
47
|
+
try {
|
|
48
|
+
process.kill(pid, 0);
|
|
49
|
+
return true;
|
|
50
|
+
}
|
|
51
|
+
catch {
|
|
52
|
+
return false;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
/** Walk up from this module to find the leedab repo root. */
|
|
56
|
+
function findRepoRoot() {
|
|
57
|
+
let dir = dirname(fileURLToPath(import.meta.url));
|
|
58
|
+
for (let i = 0; i < 10; i++) {
|
|
59
|
+
const pkgPath = resolve(dir, "package.json");
|
|
60
|
+
if (existsSync(pkgPath)) {
|
|
61
|
+
try {
|
|
62
|
+
const pkg = JSON.parse(readFileSync(pkgPath, "utf-8"));
|
|
63
|
+
if (pkg.name === "leedab")
|
|
64
|
+
return dir;
|
|
65
|
+
}
|
|
66
|
+
catch {
|
|
67
|
+
// ignore unreadable / non-JSON
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
const parent = resolve(dir, "..");
|
|
71
|
+
if (parent === dir)
|
|
72
|
+
break;
|
|
73
|
+
dir = parent;
|
|
74
|
+
}
|
|
75
|
+
throw new Error("Could not locate the leedab repo root from the console launcher.");
|
|
76
|
+
}
|
|
77
|
+
function consoleDir() {
|
|
78
|
+
return resolve(findRepoRoot(), "apps", "console");
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Start the console subprocess on the given port. Resolves once the
|
|
82
|
+
* subprocess is alive (we don't poll the port; `next start` exits on its
|
|
83
|
+
* own if the port is taken or the build is missing, so a process-error
|
|
84
|
+
* would surface there).
|
|
85
|
+
*/
|
|
86
|
+
export async function startConsole(port = 3000) {
|
|
87
|
+
const dir = consoleDir();
|
|
88
|
+
const buildDir = resolve(dir, ".next");
|
|
89
|
+
if (!existsSync(buildDir)) {
|
|
90
|
+
throw new Error(`Console build not found at ${buildDir}.\n` +
|
|
91
|
+
`Run \`cd ${dir} && npm run build\` once, then retry.`);
|
|
92
|
+
}
|
|
93
|
+
const nextBin = resolve(dir, "node_modules", ".bin", "next");
|
|
94
|
+
if (!existsSync(nextBin)) {
|
|
95
|
+
throw new Error(`Next.js binary not found at ${nextBin}.\n` +
|
|
96
|
+
`Run \`cd ${dir} && npm install\` first.`);
|
|
97
|
+
}
|
|
98
|
+
const logDir = resolve(STATE_DIR, "logs");
|
|
99
|
+
await mkdir(logDir, { recursive: true });
|
|
100
|
+
const today = new Date().toISOString().slice(0, 10);
|
|
101
|
+
const logPath = resolve(logDir, `console-${today}.log`);
|
|
102
|
+
const logStream = createWriteStream(logPath, { flags: "a" });
|
|
103
|
+
// If a previous run left a stale pid, clear it before spawning so a
|
|
104
|
+
// failed start doesn't leave us pointing at a dead process.
|
|
105
|
+
const stale = readPidFile();
|
|
106
|
+
if (stale && !isAlive(stale))
|
|
107
|
+
clearPidFile();
|
|
108
|
+
consoleProcess = spawn(nextBin, ["start", "-H", "127.0.0.1", "-p", String(port)], {
|
|
109
|
+
cwd: dir,
|
|
110
|
+
env: {
|
|
111
|
+
...process.env,
|
|
112
|
+
LEEDAB_STATE_DIR: STATE_DIR,
|
|
113
|
+
NODE_ENV: "production",
|
|
114
|
+
},
|
|
115
|
+
stdio: ["inherit", "pipe", "pipe"],
|
|
116
|
+
});
|
|
117
|
+
if (consoleProcess.pid)
|
|
118
|
+
writePidFile(consoleProcess.pid);
|
|
119
|
+
if (consoleProcess.stdout)
|
|
120
|
+
consoleProcess.stdout.pipe(logStream);
|
|
121
|
+
if (consoleProcess.stderr)
|
|
122
|
+
consoleProcess.stderr.pipe(logStream);
|
|
123
|
+
consoleProcess.on("error", (err) => {
|
|
124
|
+
console.error(`Console process error: ${err.message}`);
|
|
125
|
+
});
|
|
126
|
+
consoleProcess.on("exit", () => {
|
|
127
|
+
// If the subprocess crashed or was killed externally, drop the pid
|
|
128
|
+
// file so the next stop/start sequence isn't confused.
|
|
129
|
+
clearPidFile();
|
|
130
|
+
});
|
|
131
|
+
// Clean up on parent SIGTERM/SIGINT alongside the gateway.
|
|
132
|
+
const cleanup = () => {
|
|
133
|
+
if (consoleProcess) {
|
|
134
|
+
consoleProcess.kill("SIGTERM");
|
|
135
|
+
consoleProcess = null;
|
|
136
|
+
}
|
|
137
|
+
clearPidFile();
|
|
138
|
+
};
|
|
139
|
+
process.once("SIGTERM", cleanup);
|
|
140
|
+
process.once("SIGINT", cleanup);
|
|
141
|
+
return { url: `http://localhost:${port}`, logPath };
|
|
142
|
+
}
|
|
143
|
+
export async function stopConsole() {
|
|
144
|
+
// Same-process path first: if we hold the handle, kill directly.
|
|
145
|
+
if (consoleProcess) {
|
|
146
|
+
consoleProcess.kill("SIGTERM");
|
|
147
|
+
consoleProcess = null;
|
|
148
|
+
clearPidFile();
|
|
149
|
+
return;
|
|
150
|
+
}
|
|
151
|
+
// Cross-process path (`leedab stop` from a different terminal): signal
|
|
152
|
+
// the PID we wrote at start time. If it's already gone or was reaped,
|
|
153
|
+
// clear the stale file and return cleanly.
|
|
154
|
+
const pid = readPidFile();
|
|
155
|
+
if (!pid)
|
|
156
|
+
return;
|
|
157
|
+
if (!isAlive(pid)) {
|
|
158
|
+
clearPidFile();
|
|
159
|
+
return;
|
|
160
|
+
}
|
|
161
|
+
try {
|
|
162
|
+
process.kill(pid, "SIGTERM");
|
|
163
|
+
}
|
|
164
|
+
catch {
|
|
165
|
+
// already gone in the race window — fine.
|
|
166
|
+
}
|
|
167
|
+
// Give next a moment to flush + exit; if it's still alive after the
|
|
168
|
+
// grace window, escalate to SIGKILL so the operator isn't left with a
|
|
169
|
+
// zombie they have to hunt with `lsof`.
|
|
170
|
+
for (let i = 0; i < 20; i++) {
|
|
171
|
+
if (!isAlive(pid))
|
|
172
|
+
break;
|
|
173
|
+
await new Promise((r) => setTimeout(r, 250));
|
|
174
|
+
}
|
|
175
|
+
if (isAlive(pid)) {
|
|
176
|
+
try {
|
|
177
|
+
process.kill(pid, "SIGKILL");
|
|
178
|
+
}
|
|
179
|
+
catch {
|
|
180
|
+
// already gone
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
clearPidFile();
|
|
184
|
+
}
|
package/dist/gateway.js
CHANGED
|
@@ -25,6 +25,10 @@ export async function startGateway(config) {
|
|
|
25
25
|
await seedWorkspace();
|
|
26
26
|
// Auto-approve all tool executions (enterprise local deployment)
|
|
27
27
|
await ensureAutoApprove(bin, env);
|
|
28
|
+
// Make sure the OpenAI-compat /v1/chat/completions HTTP endpoint is on.
|
|
29
|
+
// The Next.js console proxies every chat through it, and OpenClaw ships
|
|
30
|
+
// it disabled by default.
|
|
31
|
+
await ensureChatCompletionsEndpoint(stateDir);
|
|
28
32
|
// Register channels before starting gateway
|
|
29
33
|
await registerChannels(bin, stateDir, config);
|
|
30
34
|
// Start gateway, pipe output to log file instead of terminal
|
|
@@ -129,6 +133,41 @@ async function registerChannels(bin, stateDir, config) {
|
|
|
129
133
|
}
|
|
130
134
|
}
|
|
131
135
|
}
|
|
136
|
+
/**
|
|
137
|
+
* Idempotently flip `gateway.http.endpoints.chatCompletions.enabled` to true
|
|
138
|
+
* in ~/.leedab/openclaw.json. The console (Next.js, apps/console) proxies
|
|
139
|
+
* every chat call through that endpoint, and OpenClaw ships it disabled.
|
|
140
|
+
*
|
|
141
|
+
* Safe to run on every startGateway: if the file is missing (first run
|
|
142
|
+
* before onboard) we no-op; if the flag is already true we don't write.
|
|
143
|
+
*/
|
|
144
|
+
async function ensureChatCompletionsEndpoint(stateDir) {
|
|
145
|
+
const configPath = resolve(stateDir, "openclaw.json");
|
|
146
|
+
let raw;
|
|
147
|
+
try {
|
|
148
|
+
raw = await readFile(configPath, "utf-8");
|
|
149
|
+
}
|
|
150
|
+
catch {
|
|
151
|
+
// No openclaw.json yet — onboard hasn't run. Skip; openclaw will create
|
|
152
|
+
// the file and a subsequent `leedab start` will flip the flag.
|
|
153
|
+
return;
|
|
154
|
+
}
|
|
155
|
+
let cfg;
|
|
156
|
+
try {
|
|
157
|
+
cfg = JSON.parse(raw);
|
|
158
|
+
}
|
|
159
|
+
catch {
|
|
160
|
+
return;
|
|
161
|
+
}
|
|
162
|
+
cfg.gateway = cfg.gateway ?? {};
|
|
163
|
+
cfg.gateway.http = cfg.gateway.http ?? {};
|
|
164
|
+
cfg.gateway.http.endpoints = cfg.gateway.http.endpoints ?? {};
|
|
165
|
+
cfg.gateway.http.endpoints.chatCompletions = cfg.gateway.http.endpoints.chatCompletions ?? {};
|
|
166
|
+
if (cfg.gateway.http.endpoints.chatCompletions.enabled === true)
|
|
167
|
+
return;
|
|
168
|
+
cfg.gateway.http.endpoints.chatCompletions.enabled = true;
|
|
169
|
+
await writeFile(configPath, JSON.stringify(cfg, null, 2) + "\n");
|
|
170
|
+
}
|
|
132
171
|
/**
|
|
133
172
|
* Set up exec-approvals so the agent can run tools without prompting.
|
|
134
173
|
* Safe for local enterprise deployments where the admin controls the box.
|
package/dist/index.d.ts
CHANGED
|
@@ -2,5 +2,5 @@ export { startGateway, stopGateway } from "./gateway.js";
|
|
|
2
2
|
export { loadConfig, initConfig } from "./config/index.js";
|
|
3
3
|
export { setupChannels } from "./channels/index.js";
|
|
4
4
|
export { runOnboard } from "./onboard/index.js";
|
|
5
|
-
export {
|
|
5
|
+
export { startConsole, stopConsole } from "./console-launcher.js";
|
|
6
6
|
export type { LeedABConfig } from "./config/schema.js";
|
package/dist/index.js
CHANGED
|
@@ -2,4 +2,4 @@ export { startGateway, stopGateway } from "./gateway.js";
|
|
|
2
2
|
export { loadConfig, initConfig } from "./config/index.js";
|
|
3
3
|
export { setupChannels } from "./channels/index.js";
|
|
4
4
|
export { runOnboard } from "./onboard/index.js";
|
|
5
|
-
export {
|
|
5
|
+
export { startConsole, stopConsole } from "./console-launcher.js";
|
package/dist/license.d.ts
CHANGED
package/dist/license.js
CHANGED
|
@@ -38,6 +38,8 @@ export async function validateLicenseKey(key) {
|
|
|
38
38
|
seatsUsed: data.seats_used ?? 0,
|
|
39
39
|
maxSeats: data.max_seats ?? 1,
|
|
40
40
|
validatedAt: new Date().toISOString(),
|
|
41
|
+
expiresAt: data.expires_at ?? undefined,
|
|
42
|
+
signature: data.signature ?? undefined,
|
|
41
43
|
email: data.email ?? undefined,
|
|
42
44
|
name: data.name ?? undefined,
|
|
43
45
|
orgName: data.org_name ?? undefined,
|
|
@@ -5,6 +5,12 @@ export interface ProviderResult {
|
|
|
5
5
|
}
|
|
6
6
|
/**
|
|
7
7
|
* Write the chosen model into .leedab/openclaw.json so the gateway actually uses it.
|
|
8
|
+
*
|
|
9
|
+
* Allowlist also gets every sibling model for the chosen provider, so the console's
|
|
10
|
+
* per-agent defaultModel (which can vary across COO, Procurement, CX, etc.) is
|
|
11
|
+
* accepted without re-onboarding. A single-model allowlist forces every agent to
|
|
12
|
+
* the same backend and surfaces as "Model X is not allowed for agent main" the
|
|
13
|
+
* first time a workflow routes through a non-primary agent.
|
|
8
14
|
*/
|
|
9
|
-
export declare function updateOpenClawModel(openclawModel: string): Promise<void>;
|
|
15
|
+
export declare function updateOpenClawModel(openclawModel: string, providerValue?: string): Promise<void>;
|
|
10
16
|
export declare function onboardProvider(): Promise<ProviderResult | null>;
|
|
@@ -14,7 +14,7 @@ const PROVIDERS = [
|
|
|
14
14
|
models: [
|
|
15
15
|
{ name: "Claude Opus 4.7 — most capable", value: "claude-opus-4-7", openclaw: "anthropic/claude-opus-4-7" },
|
|
16
16
|
{ name: "Claude Sonnet 4.6 — fast, capable (recommended)", value: "claude-sonnet-4-6", openclaw: "anthropic/claude-sonnet-4-6" },
|
|
17
|
-
{ name: "Claude Haiku 4.5 — fastest, cheapest", value: "claude-haiku-4-5", openclaw: "anthropic/claude-haiku-4-5" },
|
|
17
|
+
{ name: "Claude Haiku 4.5 — fastest, cheapest", value: "claude-haiku-4-5", openclaw: "anthropic/claude-haiku-4-5-20251001" },
|
|
18
18
|
],
|
|
19
19
|
},
|
|
20
20
|
{
|
|
@@ -56,7 +56,7 @@ const PROVIDERS = [
|
|
|
56
56
|
models: [
|
|
57
57
|
{ name: "Claude Sonnet 4.6 via Bedrock", value: "anthropic.claude-sonnet-4-6", openclaw: "anthropic/claude-sonnet-4-6" },
|
|
58
58
|
{ name: "Claude Opus 4.7 via Bedrock", value: "anthropic.claude-opus-4-7", openclaw: "anthropic/claude-opus-4-7" },
|
|
59
|
-
{ name: "Claude Haiku 4.5 via Bedrock", value: "anthropic.claude-haiku-4-5", openclaw: "anthropic/claude-haiku-4-5" },
|
|
59
|
+
{ name: "Claude Haiku 4.5 via Bedrock", value: "anthropic.claude-haiku-4-5", openclaw: "anthropic/claude-haiku-4-5-20251001" },
|
|
60
60
|
],
|
|
61
61
|
},
|
|
62
62
|
{
|
|
@@ -163,8 +163,14 @@ async function passKeyToOpenClaw(flag, apiKey) {
|
|
|
163
163
|
}
|
|
164
164
|
/**
|
|
165
165
|
* Write the chosen model into .leedab/openclaw.json so the gateway actually uses it.
|
|
166
|
+
*
|
|
167
|
+
* Allowlist also gets every sibling model for the chosen provider, so the console's
|
|
168
|
+
* per-agent defaultModel (which can vary across COO, Procurement, CX, etc.) is
|
|
169
|
+
* accepted without re-onboarding. A single-model allowlist forces every agent to
|
|
170
|
+
* the same backend and surfaces as "Model X is not allowed for agent main" the
|
|
171
|
+
* first time a workflow routes through a non-primary agent.
|
|
166
172
|
*/
|
|
167
|
-
export async function updateOpenClawModel(openclawModel) {
|
|
173
|
+
export async function updateOpenClawModel(openclawModel, providerValue) {
|
|
168
174
|
const configPath = resolve(STATE_DIR, "openclaw.json");
|
|
169
175
|
try {
|
|
170
176
|
const raw = JSON.parse(await readFile(configPath, "utf-8"));
|
|
@@ -173,7 +179,17 @@ export async function updateOpenClawModel(openclawModel) {
|
|
|
173
179
|
if (!raw.agents.defaults)
|
|
174
180
|
raw.agents.defaults = {};
|
|
175
181
|
raw.agents.defaults.model = { primary: openclawModel };
|
|
176
|
-
|
|
182
|
+
const allowed = new Set([openclawModel]);
|
|
183
|
+
if (providerValue) {
|
|
184
|
+
const provider = PROVIDERS.find(p => p.value === providerValue);
|
|
185
|
+
if (provider)
|
|
186
|
+
for (const m of provider.models)
|
|
187
|
+
allowed.add(m.openclaw);
|
|
188
|
+
}
|
|
189
|
+
const models = {};
|
|
190
|
+
for (const id of allowed)
|
|
191
|
+
models[id] = {};
|
|
192
|
+
raw.agents.defaults.models = models;
|
|
177
193
|
await writeFile(configPath, JSON.stringify(raw, null, 2) + "\n");
|
|
178
194
|
}
|
|
179
195
|
catch {
|
|
@@ -203,7 +219,7 @@ export async function onboardProvider() {
|
|
|
203
219
|
if (existing.provider.flag) {
|
|
204
220
|
await passKeyToOpenClaw(existing.provider.flag, apiKey);
|
|
205
221
|
}
|
|
206
|
-
await updateOpenClawModel(model.openclaw);
|
|
222
|
+
await updateOpenClawModel(model.openclaw, existing.provider.value);
|
|
207
223
|
console.log(chalk.green(` Using ${existing.provider.name}. Model: ${model.value}`));
|
|
208
224
|
return {
|
|
209
225
|
provider: existing.provider.value,
|
|
@@ -238,7 +254,7 @@ export async function onboardProvider() {
|
|
|
238
254
|
}
|
|
239
255
|
const bedrockProvider = PROVIDERS.find((p) => p.value === "bedrock");
|
|
240
256
|
const model = await pickModel(bedrockProvider);
|
|
241
|
-
await updateOpenClawModel(model.openclaw);
|
|
257
|
+
await updateOpenClawModel(model.openclaw, "bedrock");
|
|
242
258
|
return {
|
|
243
259
|
provider: "bedrock",
|
|
244
260
|
apiKey: "",
|
|
@@ -283,7 +299,7 @@ export async function onboardProvider() {
|
|
|
283
299
|
await passKeyToOpenClaw(selected.flag, trimmedKey);
|
|
284
300
|
}
|
|
285
301
|
const model = await pickModel(selected);
|
|
286
|
-
await updateOpenClawModel(model.openclaw);
|
|
302
|
+
await updateOpenClawModel(model.openclaw, selected.value);
|
|
287
303
|
console.log(chalk.green(`\n Using ${selected.name}. Model: ${model.value}`));
|
|
288
304
|
return {
|
|
289
305
|
provider: selected.value,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "leedab",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.1",
|
|
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>",
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
"LICENSE"
|
|
17
17
|
],
|
|
18
18
|
"scripts": {
|
|
19
|
-
"build": "tsc && rm -rf dist/
|
|
19
|
+
"build": "tsc && rm -rf dist/templates && cp -r src/templates dist/templates",
|
|
20
20
|
"dev": "tsc --watch",
|
|
21
21
|
"start": "node bin/leedab.js",
|
|
22
22
|
"lint": "eslint src/",
|
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
import { type IncomingMessage, type ServerResponse } from "node:http";
|
|
2
|
-
import type { LeedABConfig } from "../config/schema.js";
|
|
3
|
-
import { type ChannelName } from "../team/permissions.js";
|
|
4
|
-
type RouteHandler = (req: IncomingMessage, res: ServerResponse, url: URL) => Promise<void>;
|
|
5
|
-
export declare function createRoutes(config: LeedABConfig): Record<string, RouteHandler>;
|
|
6
|
-
/**
|
|
7
|
-
* Build the permission preamble injected into the agent prompt for a given
|
|
8
|
-
* channel message. Returns null when we have nothing to add (unknown user
|
|
9
|
-
* on a non-dashboard channel — the allowlist will already have rejected
|
|
10
|
-
* those).
|
|
11
|
-
*
|
|
12
|
-
* This preamble is the sole workflow-permission gate. We trust the agent to
|
|
13
|
-
* honor it. There is no server-side hard gate; admins restrict access by
|
|
14
|
-
* naming the workflows a member may use in the team page.
|
|
15
|
-
*/
|
|
16
|
-
export declare function buildPermissionPreamble(channel: ChannelName, userId: string): Promise<string | null>;
|
|
17
|
-
export {};
|