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 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 Telegram, learns your operations, uses your computer like a team member would, and keeps every byte of data on your hardware.
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 Slack. No new app to learn.
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 agent status, sessions, and team management.
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 Setup wizard
34
- leedab start Start the agent and dashboard
35
- leedab terminal Chat with the agent in terminal
36
- leedab stop Stop the agent
37
- leedab configure model Change LLM provider or model
38
- leedab vault add <svc> Store credentials for a service
39
- leedab pairing add Add users to channel allowlist
40
- leedab team add <name> Add a team member
41
- leedab sessions View conversation sessions
42
- leedab --help All commands
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 OpenRouter)
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 `.leedab/`
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 admin, operator, and viewer access
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 { startDashboard } from "../dist/dashboard/server.js";
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
- await startDashboard(config, parseInt(opts.dashboardPort));
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)} Dashboard`);
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(` Logs: ${logPath}`));
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 setup dashboard (without starting the agent)")
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>", "Dashboard port", "3000")
238
+ .option("-p, --port <port>", "Console port", "3000")
236
239
  .action(async (opts) => {
237
240
  try {
238
- const config = await loadConfig(opts.config);
239
- console.log(chalk.bold("\n LeedAB Dashboard\n"));
240
- await startDashboard(config, parseInt(opts.port));
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 { startDashboard } from "./dashboard/server.js";
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 { startDashboard } from "./dashboard/server.js";
5
+ export { startConsole, stopConsole } from "./console-launcher.js";
package/dist/license.d.ts CHANGED
@@ -6,6 +6,8 @@ export interface LicenseInfo {
6
6
  seatsUsed: number;
7
7
  maxSeats: number;
8
8
  validatedAt: string;
9
+ expiresAt?: string;
10
+ signature?: string;
9
11
  email?: string;
10
12
  name?: string;
11
13
  orgName?: string;
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
- raw.agents.defaults.models = { [openclawModel]: {} };
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.2.6",
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/dashboard/static dist/templates && cp -r src/dashboard/static dist/dashboard/static && cp -r src/templates dist/templates",
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 {};