leedab 0.1.6 → 0.1.8

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/bin/leedab.js CHANGED
@@ -3,6 +3,7 @@
3
3
  import { createRequire } from "node:module";
4
4
  import { existsSync } from "node:fs";
5
5
  import { resolve } from "node:path";
6
+ import { homedir } from "node:os";
6
7
  import { program } from "commander";
7
8
  import chalk from "chalk";
8
9
  import ora from "ora";
@@ -19,7 +20,7 @@ import { ensureLicense } from "../dist/license.js";
19
20
 
20
21
  const pkg = createRequire(import.meta.url)("../package.json");
21
22
  const OPENCLAW_BIN = resolveOpenClawBin();
22
- const STATE_DIR = resolve(".leedab");
23
+ const STATE_DIR = resolve(homedir(), ".leedab");
23
24
  const ocEnv = openclawEnv(STATE_DIR);
24
25
 
25
26
  /** Commands that don't require an existing project directory or license. */
@@ -37,12 +38,9 @@ function requireProject(command) {
37
38
  const parentName = command.parent?.name();
38
39
  if (NO_PROJECT_REQUIRED.has(name) || NO_PROJECT_REQUIRED.has(parentName)) return;
39
40
 
40
- const hasConfig = existsSync(resolve("leedab.config.json"));
41
- const hasState = existsSync(STATE_DIR);
42
- if (!hasConfig && !hasState) {
43
- console.error(chalk.red("\n Not a LeedAB project directory.\n"));
44
- console.error(chalk.dim(" Run ") + chalk.cyan("leedab onboard") + chalk.dim(" to set up a new project here,"));
45
- console.error(chalk.dim(" or cd into an existing project directory.\n"));
41
+ if (!existsSync(STATE_DIR)) {
42
+ console.error(chalk.red("\n LeedAB is not set up yet.\n"));
43
+ console.error(chalk.dim(" Run ") + chalk.cyan("leedab onboard") + chalk.dim(" to get started.\n"));
46
44
  process.exit(1);
47
45
  }
48
46
  }
@@ -143,7 +141,7 @@ configure
143
141
  const result = await onboardProvider();
144
142
  if (result) {
145
143
  const { readFile, writeFile } = await import("node:fs/promises");
146
- const configPath = resolve("leedab.config.json");
144
+ const configPath = resolve(resolve(STATE_DIR, "config.json"));
147
145
  try {
148
146
  const raw = JSON.parse(await readFile(configPath, "utf-8"));
149
147
  raw.agent.model = result.model;
@@ -173,7 +171,7 @@ program
173
171
  program
174
172
  .command("start")
175
173
  .description("Start the LeedAB agent and dashboard")
176
- .option("-c, --config <path>", "Path to config file", "leedab.config.json")
174
+ .option("-c, --config <path>", "Path to config file", resolve(STATE_DIR, "config.json"))
177
175
  .option("-p, --dashboard-port <port>", "Dashboard port", "3000")
178
176
  .action(async (opts) => {
179
177
  const spinner = ora("Starting LeedAB agent...").start();
@@ -208,7 +206,7 @@ program
208
206
  program
209
207
  .command("dashboard")
210
208
  .description("Open the setup dashboard (without starting the agent)")
211
- .option("-c, --config <path>", "Path to config file", "leedab.config.json")
209
+ .option("-c, --config <path>", "Path to config file", resolve(STATE_DIR, "config.json"))
212
210
  .option("-p, --port <port>", "Dashboard port", "3000")
213
211
  .action(async (opts) => {
214
212
  try {
@@ -386,7 +384,7 @@ pairing
386
384
  program
387
385
  .command("channels")
388
386
  .description("List configured channels and their status")
389
- .option("-c, --config <path>", "Path to config file", "leedab.config.json")
387
+ .option("-c, --config <path>", "Path to config file", resolve(STATE_DIR, "config.json"))
390
388
  .action(async (opts) => {
391
389
  const config = await loadConfig(opts.config);
392
390
  await setupChannels(config);
@@ -630,8 +628,7 @@ team
630
628
 
631
629
  // Default action: show status if in a project, nudge to onboard if not
632
630
  if (process.argv.length <= 2) {
633
- const hasProject = existsSync(resolve("leedab.config.json")) || existsSync(STATE_DIR);
634
- if (!hasProject) {
631
+ if (!existsSync(STATE_DIR)) {
635
632
  console.log(chalk.bold("\n LeedAB") + chalk.dim(" — Your enterprise AI agent\n"));
636
633
  console.log(chalk.dim(" Get started:\n"));
637
634
  console.log(` ${chalk.cyan("leedab onboard")} Interactive setup wizard`);
@@ -642,7 +639,7 @@ if (process.argv.length <= 2) {
642
639
  // Show project status
643
640
  (async () => {
644
641
  try {
645
- const config = await loadConfig("leedab.config.json");
642
+ const config = await loadConfig(resolve(STATE_DIR, "config.json"));
646
643
  const channels = Object.entries(config.channels)
647
644
  .filter(([_, v]) => v?.enabled)
648
645
  .map(([k]) => k);
package/dist/audit.js CHANGED
@@ -1,6 +1,7 @@
1
1
  import { appendFile, readFile, mkdir } from "node:fs/promises";
2
2
  import { resolve, dirname } from "node:path";
3
- const AUDIT_PATH = resolve(".leedab", "logs", "audit.jsonl");
3
+ import { STATE_DIR } from "./paths.js";
4
+ const AUDIT_PATH = resolve(STATE_DIR, "logs", "audit.jsonl");
4
5
  export async function logAudit(entry) {
5
6
  await mkdir(dirname(AUDIT_PATH), { recursive: true });
6
7
  const line = JSON.stringify({
@@ -5,6 +5,6 @@ export { type LeedABConfig } from "./schema.js";
5
5
  */
6
6
  export declare function loadConfig(configPath?: string): Promise<LeedABConfig>;
7
7
  /**
8
- * Write default config to leedab.config.json.
8
+ * Write default config to ~/.leedab/config.json.
9
9
  */
10
10
  export declare function initConfig(): Promise<void>;
@@ -1,10 +1,12 @@
1
1
  import { readFile, writeFile, access } from "node:fs/promises";
2
2
  import { resolve } from "node:path";
3
+ import { STATE_DIR } from "../paths.js";
3
4
  import { DEFAULT_CONFIG } from "./schema.js";
5
+ const DEFAULT_CONFIG_PATH = resolve(STATE_DIR, "config.json");
4
6
  /**
5
7
  * Load and validate a LeedAB config file, merging with defaults.
6
8
  */
7
- export async function loadConfig(configPath = "leedab.config.json") {
9
+ export async function loadConfig(configPath = DEFAULT_CONFIG_PATH) {
8
10
  const fullPath = resolve(configPath);
9
11
  let userConfig = {};
10
12
  try {
@@ -17,19 +19,18 @@ export async function loadConfig(configPath = "leedab.config.json") {
17
19
  return deepMerge(DEFAULT_CONFIG, userConfig);
18
20
  }
19
21
  /**
20
- * Write default config to leedab.config.json.
22
+ * Write default config to ~/.leedab/config.json.
21
23
  */
22
24
  export async function initConfig() {
23
- const configPath = resolve("leedab.config.json");
24
25
  try {
25
- await access(configPath);
26
- throw new Error("leedab.config.json already exists");
26
+ await access(DEFAULT_CONFIG_PATH);
27
+ throw new Error("config.json already exists");
27
28
  }
28
29
  catch (err) {
29
30
  if (err.code !== "ENOENT")
30
31
  throw err;
31
32
  }
32
- await writeFile(configPath, JSON.stringify(DEFAULT_CONFIG, null, 2) + "\n");
33
+ await writeFile(DEFAULT_CONFIG_PATH, JSON.stringify(DEFAULT_CONFIG, null, 2) + "\n");
33
34
  }
34
35
  function deepMerge(target, source) {
35
36
  const result = { ...target };
@@ -28,7 +28,7 @@ export interface GatewayConfig {
28
28
  };
29
29
  }
30
30
  export interface MemoryConfig {
31
- /** Path to memory directory (default: .leedab/memory) */
31
+ /** Path to memory directory (default: ~/.leedab/memory) */
32
32
  dir: string;
33
33
  /** Enable vector search if embedding provider is set */
34
34
  vectorSearch: boolean;
@@ -1,5 +1,5 @@
1
1
  import { execFile, spawn } from "node:child_process";
2
- import { readFile, writeFile, mkdir, readdir } from "node:fs/promises";
2
+ import { readFile, writeFile, mkdir, readdir, stat } from "node:fs/promises";
3
3
  import { resolve } from "node:path";
4
4
  import { promisify } from "node:util";
5
5
  import { userInfo } from "node:os";
@@ -8,6 +8,7 @@ import { addEntry, removeEntry, listEntries } from "../vault.js";
8
8
  import { readAuditLog } from "../audit.js";
9
9
  import { getAnalytics } from "../analytics.js";
10
10
  import { loadTeam, addMember, removeMember, updateRole } from "../team.js";
11
+ import { STATE_DIR } from "../paths.js";
11
12
  const execFileAsync = promisify(execFile);
12
13
  async function restartGatewayQuietly(stateDir) {
13
14
  try {
@@ -22,7 +23,7 @@ async function restartGatewayQuietly(stateDir) {
22
23
  }
23
24
  export function createRoutes(config) {
24
25
  const bin = resolveOpenClawBin();
25
- const stateDir = resolve(".leedab");
26
+ const stateDir = STATE_DIR;
26
27
  return {
27
28
  /**
28
29
  * POST /api/chat — send a message to the agent, get a response
@@ -73,23 +74,7 @@ export function createRoutes(config) {
73
74
  const session = url.searchParams.get("session") ?? "console";
74
75
  try {
75
76
  const sessionsDir = resolve(stateDir, "agents", "main", "sessions");
76
- // If session is a UUID, use it directly; otherwise search by key
77
- let jsonlPath = resolve(sessionsDir, `${session}.jsonl`);
78
- // Try to find the file — if not found by direct name, scan directory
79
- try {
80
- await readFile(jsonlPath);
81
- }
82
- catch {
83
- // Session param might be the key rather than the UUID filename
84
- // List files and try to match
85
- const files = await readdir(sessionsDir);
86
- const match = files.find(f => f.endsWith(".jsonl"));
87
- if (!match) {
88
- json(res, []);
89
- return;
90
- }
91
- // Fall back to direct path — if it doesn't exist, we'll catch below
92
- }
77
+ const jsonlPath = resolve(sessionsDir, `${session}.jsonl`);
93
78
  const raw = await readFile(jsonlPath, "utf-8");
94
79
  const messages = [];
95
80
  for (const line of raw.split("\n")) {
@@ -229,10 +214,46 @@ export function createRoutes(config) {
229
214
  */
230
215
  "GET /api/sessions": async (_req, res) => {
231
216
  try {
232
- const { stdout } = await execFileAsync(bin, ["sessions", "--json"], { env: openclawEnv(stateDir), timeout: 10000 });
233
- const parsed = JSON.parse(stdout);
234
- const sessions = parsed.sessions ?? (Array.isArray(parsed) ? parsed : []);
235
- json(res, sessions);
217
+ const sessionsDir = resolve(stateDir, "agents", "main", "sessions");
218
+ // Get indexed sessions from openclaw, keyed by sessionId
219
+ const indexed = new Map();
220
+ try {
221
+ const { stdout } = await execFileAsync(bin, ["sessions", "--json"], { env: openclawEnv(stateDir), timeout: 10000 });
222
+ const parsed = JSON.parse(stdout);
223
+ for (const s of parsed.sessions ?? (Array.isArray(parsed) ? parsed : [])) {
224
+ if (s.sessionId)
225
+ indexed.set(s.sessionId, s);
226
+ }
227
+ }
228
+ catch { }
229
+ // Walk JSONL files on disk — this is the source of truth
230
+ const results = [];
231
+ const files = await readdir(sessionsDir);
232
+ for (const f of files) {
233
+ if (!f.endsWith(".jsonl"))
234
+ continue;
235
+ const name = f.replace(/\.jsonl$/, "");
236
+ const entry = indexed.get(name);
237
+ if (entry) {
238
+ results.push(entry);
239
+ }
240
+ else {
241
+ // Unindexed file (e.g. console.jsonl) — build minimal metadata
242
+ try {
243
+ const fstat = await stat(resolve(sessionsDir, f));
244
+ results.push({
245
+ key: `agent:main:${name}`,
246
+ sessionId: name,
247
+ updatedAt: fstat.mtimeMs,
248
+ chatType: "direct",
249
+ });
250
+ }
251
+ catch { }
252
+ }
253
+ }
254
+ // Sort newest first
255
+ results.sort((a, b) => (b.updatedAt ?? 0) - (a.updatedAt ?? 0));
256
+ json(res, results);
236
257
  }
237
258
  catch {
238
259
  json(res, []);
package/dist/gateway.js CHANGED
@@ -6,6 +6,7 @@ import { fileURLToPath } from "node:url";
6
6
  import { promisify } from "node:util";
7
7
  import { resolveOpenClawBin, openclawEnv } from "./openclaw.js";
8
8
  import { ensureLicense } from "./license.js";
9
+ import { STATE_DIR } from "./paths.js";
9
10
  const execFileAsync = promisify(execFile);
10
11
  let gatewayProcess = null;
11
12
  /**
@@ -18,7 +19,7 @@ export async function startGateway(config) {
18
19
  throw new Error("No valid license found. Run `leedab onboard` to enter your license key.");
19
20
  }
20
21
  const bin = resolveOpenClawBin();
21
- const stateDir = resolve(".leedab");
22
+ const stateDir = STATE_DIR;
22
23
  const env = openclawEnv(stateDir);
23
24
  // Seed workspace templates (skip files that already exist)
24
25
  await seedWorkspace();
@@ -64,7 +65,7 @@ export async function startGateway(config) {
64
65
  }
65
66
  export async function stopGateway() {
66
67
  const bin = resolveOpenClawBin();
67
- const stateDir = resolve(".leedab");
68
+ const stateDir = STATE_DIR;
68
69
  try {
69
70
  await execFileAsync(bin, ["gateway", "stop"], {
70
71
  env: openclawEnv(stateDir),
@@ -199,7 +200,7 @@ async function seedWorkspace() {
199
200
  }
200
201
  async function waitForGateway(port, timeoutMs = 20000) {
201
202
  const bin = resolveOpenClawBin();
202
- const stateDir = resolve(".leedab");
203
+ const stateDir = STATE_DIR;
203
204
  const start = Date.now();
204
205
  while (Date.now() - start < timeoutMs) {
205
206
  try {
package/dist/license.js CHANGED
@@ -1,6 +1,7 @@
1
1
  import { readFile, writeFile, mkdir } from "node:fs/promises";
2
2
  import { resolve } from "node:path";
3
- const LICENSE_PATH = resolve(".leedab", "license.json");
3
+ import { STATE_DIR } from "./paths.js";
4
+ const LICENSE_PATH = resolve(STATE_DIR, "license.json");
4
5
  const VERIFY_URL = "https://api.leedab.com/api/v1/licensing/verify";
5
6
  const REVALIDATE_DAYS = 7;
6
7
  /**
@@ -40,7 +41,7 @@ export async function validateLicenseKey(key) {
40
41
  * Save license info to disk.
41
42
  */
42
43
  export async function saveLicense(license) {
43
- await mkdir(resolve(".leedab"), { recursive: true });
44
+ await mkdir(STATE_DIR, { recursive: true });
44
45
  await writeFile(LICENSE_PATH, JSON.stringify(license, null, 2) + "\n");
45
46
  }
46
47
  /**
@@ -12,6 +12,7 @@ import { DEFAULT_CONFIG } from "../config/schema.js";
12
12
  import { initMemory } from "../memory/index.js";
13
13
  import { addEntry, listEntries } from "../vault.js";
14
14
  import { validateLicenseKey, saveLicense, loadLicense } from "../license.js";
15
+ import { STATE_DIR } from "../paths.js";
15
16
  /**
16
17
  * Run the full onboarding wizard. Writes leedab.config.json and .leedab/ secrets.
17
18
  */
@@ -198,15 +199,16 @@ export async function runOnboard() {
198
199
  chalk.cyan("leedab vault add <service>"));
199
200
  }
200
201
  // --- Write everything ---
201
- const configDir = resolve(".leedab");
202
+ const configDir = STATE_DIR;
202
203
  await mkdir(configDir, { recursive: true });
203
204
  // Write config (no secrets)
204
- await writeFile(resolve("leedab.config.json"), JSON.stringify(config, null, 2) + "\n");
205
+ await writeFile(resolve(STATE_DIR, "config.json"), JSON.stringify(config, null, 2) + "\n");
205
206
  // Write secrets separately
206
207
  if (Object.keys(secrets).length > 0) {
207
208
  await writeFile(resolve(configDir, "secrets.json"), JSON.stringify(secrets, null, 2) + "\n");
208
209
  }
209
210
  // Init memory directory
211
+ config.memory.dir = resolve(STATE_DIR, "memory");
210
212
  await initMemory(config.memory.dir);
211
213
  // Summary
212
214
  console.log(chalk.bold("\n Setup complete!\n"));
@@ -229,10 +231,10 @@ export async function runOnboard() {
229
231
  if (vaultEntries.length > 0) {
230
232
  console.log(chalk.green(` Vault: ${vaultEntries.length} service(s) stored`));
231
233
  }
232
- console.log(chalk.dim(`\n Config: ./leedab.config.json`));
233
- console.log(chalk.dim(` Secrets: ./.leedab/secrets.json`));
234
- console.log(chalk.dim(` Vault: ./.leedab/vault.json`));
235
- console.log(chalk.dim(` Memory: ./${config.memory.dir}/`));
234
+ console.log(chalk.dim(`\n Config: ~/.leedab/config.json`));
235
+ console.log(chalk.dim(` Secrets: ~/.leedab/secrets.json`));
236
+ console.log(chalk.dim(` Vault: ~/.leedab/vault.json`));
237
+ console.log(chalk.dim(` Memory: ~/.leedab/memory/`));
236
238
  console.log(chalk.bold(`\n Start your agent: ${chalk.cyan("leedab start")}\n`));
237
239
  }
238
240
  /**
@@ -5,6 +5,7 @@ import { promisify } from "node:util";
5
5
  import inquirer from "inquirer";
6
6
  import chalk from "chalk";
7
7
  import { resolveOpenClawBin, openclawEnv } from "../../openclaw.js";
8
+ import { STATE_DIR } from "../../paths.js";
8
9
  const execFileAsync = promisify(execFile);
9
10
  const PROVIDERS = [
10
11
  {
@@ -164,7 +165,7 @@ async function passKeyToOpenClaw(flag, apiKey) {
164
165
  * Write the chosen model into .leedab/openclaw.json so the gateway actually uses it.
165
166
  */
166
167
  export async function updateOpenClawModel(openclawModel) {
167
- const configPath = resolve(".leedab", "openclaw.json");
168
+ const configPath = resolve(STATE_DIR, "openclaw.json");
168
169
  try {
169
170
  const raw = JSON.parse(await readFile(configPath, "utf-8"));
170
171
  if (!raw.agents)
package/dist/openclaw.js CHANGED
@@ -1,5 +1,6 @@
1
1
  import { createRequire } from "node:module";
2
2
  import { dirname, resolve } from "node:path";
3
+ import { STATE_DIR } from "./paths.js";
3
4
  const require = createRequire(import.meta.url);
4
5
  /**
5
6
  * Resolve the OpenClaw binary path relative to the leedab package,
@@ -15,6 +16,6 @@ export function resolveOpenClawBin() {
15
16
  export function openclawEnv(stateDir) {
16
17
  return {
17
18
  ...process.env,
18
- OPENCLAW_STATE_DIR: stateDir ?? resolve(".leedab"),
19
+ OPENCLAW_STATE_DIR: stateDir ?? STATE_DIR,
19
20
  };
20
21
  }
package/dist/paths.d.ts CHANGED
@@ -1,2 +1,2 @@
1
- /** Global LeedAB state directory - always ~/. leedab */
1
+ /** Global LeedAB state directory - always ~/.leedab */
2
2
  export declare const STATE_DIR: string;
package/dist/paths.js CHANGED
@@ -1,4 +1,4 @@
1
1
  import { resolve } from "node:path";
2
2
  import { homedir } from "node:os";
3
- /** Global LeedAB state directory - always ~/. leedab */
3
+ /** Global LeedAB state directory - always ~/.leedab */
4
4
  export const STATE_DIR = resolve(homedir(), ".leedab");
package/dist/vault.js CHANGED
@@ -1,7 +1,8 @@
1
1
  import { readFile, writeFile, mkdir } from "node:fs/promises";
2
2
  import { resolve, dirname } from "node:path";
3
3
  import { randomBytes, scryptSync, createCipheriv, createDecipheriv } from "node:crypto";
4
- const VAULT_PATH = resolve(".leedab", "vault.json");
4
+ import { STATE_DIR } from "./paths.js";
5
+ const VAULT_PATH = resolve(STATE_DIR, "vault.json");
5
6
  const MAGIC = "LEEDAB_VAULT_V1";
6
7
  const ALGORITHM = "aes-256-gcm";
7
8
  async function ensureDir() {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "leedab",
3
- "version": "0.1.6",
3
+ "version": "0.1.8",
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>",