leedab 0.1.0

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.
Files changed (71) hide show
  1. package/LICENSE +6 -0
  2. package/README.md +85 -0
  3. package/bin/leedab.js +626 -0
  4. package/dist/analytics.d.ts +20 -0
  5. package/dist/analytics.js +57 -0
  6. package/dist/audit.d.ts +15 -0
  7. package/dist/audit.js +46 -0
  8. package/dist/brand.d.ts +9 -0
  9. package/dist/brand.js +57 -0
  10. package/dist/channels/index.d.ts +5 -0
  11. package/dist/channels/index.js +47 -0
  12. package/dist/config/index.d.ts +10 -0
  13. package/dist/config/index.js +49 -0
  14. package/dist/config/schema.d.ts +58 -0
  15. package/dist/config/schema.js +21 -0
  16. package/dist/dashboard/routes.d.ts +5 -0
  17. package/dist/dashboard/routes.js +410 -0
  18. package/dist/dashboard/server.d.ts +2 -0
  19. package/dist/dashboard/server.js +80 -0
  20. package/dist/dashboard/static/app.js +351 -0
  21. package/dist/dashboard/static/console.html +252 -0
  22. package/dist/dashboard/static/favicon.png +0 -0
  23. package/dist/dashboard/static/index.html +815 -0
  24. package/dist/dashboard/static/logo-dark.png +0 -0
  25. package/dist/dashboard/static/logo-light.png +0 -0
  26. package/dist/dashboard/static/sessions.html +182 -0
  27. package/dist/dashboard/static/settings.html +274 -0
  28. package/dist/dashboard/static/style.css +493 -0
  29. package/dist/dashboard/static/team.html +215 -0
  30. package/dist/gateway.d.ts +8 -0
  31. package/dist/gateway.js +213 -0
  32. package/dist/index.d.ts +6 -0
  33. package/dist/index.js +5 -0
  34. package/dist/license.d.ts +27 -0
  35. package/dist/license.js +92 -0
  36. package/dist/memory/index.d.ts +9 -0
  37. package/dist/memory/index.js +41 -0
  38. package/dist/onboard/index.d.ts +4 -0
  39. package/dist/onboard/index.js +263 -0
  40. package/dist/onboard/oauth-server.d.ts +13 -0
  41. package/dist/onboard/oauth-server.js +73 -0
  42. package/dist/onboard/steps/google.d.ts +12 -0
  43. package/dist/onboard/steps/google.js +178 -0
  44. package/dist/onboard/steps/provider.d.ts +10 -0
  45. package/dist/onboard/steps/provider.js +292 -0
  46. package/dist/onboard/steps/teams.d.ts +5 -0
  47. package/dist/onboard/steps/teams.js +51 -0
  48. package/dist/onboard/steps/telegram.d.ts +6 -0
  49. package/dist/onboard/steps/telegram.js +88 -0
  50. package/dist/onboard/steps/welcome.d.ts +1 -0
  51. package/dist/onboard/steps/welcome.js +10 -0
  52. package/dist/onboard/steps/whatsapp.d.ts +2 -0
  53. package/dist/onboard/steps/whatsapp.js +76 -0
  54. package/dist/openclaw.d.ts +9 -0
  55. package/dist/openclaw.js +20 -0
  56. package/dist/team.d.ts +13 -0
  57. package/dist/team.js +49 -0
  58. package/dist/templates/verticals/supply-chain/HEARTBEAT.md +12 -0
  59. package/dist/templates/verticals/supply-chain/SOUL.md +49 -0
  60. package/dist/templates/verticals/supply-chain/WORKFLOWS.md +148 -0
  61. package/dist/templates/verticals/supply-chain/vault-template.json +18 -0
  62. package/dist/templates/workspace/AGENTS.md +181 -0
  63. package/dist/templates/workspace/BOOTSTRAP.md +32 -0
  64. package/dist/templates/workspace/HEARTBEAT.md +9 -0
  65. package/dist/templates/workspace/IDENTITY.md +14 -0
  66. package/dist/templates/workspace/SOUL.md +32 -0
  67. package/dist/templates/workspace/TOOLS.md +40 -0
  68. package/dist/templates/workspace/USER.md +26 -0
  69. package/dist/vault.d.ts +24 -0
  70. package/dist/vault.js +123 -0
  71. package/package.json +58 -0
@@ -0,0 +1,213 @@
1
+ import { spawn, execFile } from "node:child_process";
2
+ import { createWriteStream } from "node:fs";
3
+ import { resolve, dirname, join } from "node:path";
4
+ import { readFile, writeFile, readdir, copyFile, mkdir, access } from "node:fs/promises";
5
+ import { fileURLToPath } from "node:url";
6
+ import { promisify } from "node:util";
7
+ import { resolveOpenClawBin, openclawEnv } from "./openclaw.js";
8
+ const execFileAsync = promisify(execFile);
9
+ let gatewayProcess = null;
10
+ /**
11
+ * Start the LeedAB agent gateway.
12
+ */
13
+ export async function startGateway(config) {
14
+ const bin = resolveOpenClawBin();
15
+ const stateDir = resolve(".leedab");
16
+ const env = openclawEnv(stateDir);
17
+ // Seed workspace templates (skip files that already exist)
18
+ await seedWorkspace();
19
+ // Auto-approve all tool executions (enterprise local deployment)
20
+ await ensureAutoApprove(bin, env);
21
+ // Register channels before starting gateway
22
+ await registerChannels(bin, stateDir, config);
23
+ // Start gateway, pipe output to log file instead of terminal
24
+ const logDir = resolve(stateDir, "logs");
25
+ await mkdir(logDir, { recursive: true });
26
+ const today = new Date().toISOString().slice(0, 10);
27
+ const logPath = resolve(logDir, `gateway-${today}.log`);
28
+ const logStream = createWriteStream(logPath, { flags: "a" });
29
+ gatewayProcess = spawn(bin, ["gateway", "run", "--force"], {
30
+ env,
31
+ stdio: ["inherit", "pipe", "pipe"],
32
+ });
33
+ if (gatewayProcess.stdout)
34
+ gatewayProcess.stdout.pipe(logStream);
35
+ if (gatewayProcess.stderr)
36
+ gatewayProcess.stderr.pipe(logStream);
37
+ // Expose log path so callers can display it
38
+ startGateway.logPath = logPath;
39
+ gatewayProcess.on("error", (err) => {
40
+ console.error("Gateway process error:", err.message);
41
+ process.exit(1);
42
+ });
43
+ // Clean shutdown on SIGTERM/SIGINT
44
+ const cleanup = () => {
45
+ if (gatewayProcess) {
46
+ gatewayProcess.kill("SIGTERM");
47
+ gatewayProcess = null;
48
+ }
49
+ process.exit(0);
50
+ };
51
+ process.on("SIGTERM", cleanup);
52
+ process.on("SIGINT", cleanup);
53
+ await waitForGateway(config.gateway.port);
54
+ // Push approvals to running gateway (local set happens before start,
55
+ // but the gateway may load its own copy on boot)
56
+ await ensureAutoApprove(bin, env, true);
57
+ return { logPath };
58
+ }
59
+ export async function stopGateway() {
60
+ const bin = resolveOpenClawBin();
61
+ const stateDir = resolve(".leedab");
62
+ try {
63
+ await execFileAsync(bin, ["gateway", "stop"], {
64
+ env: openclawEnv(stateDir),
65
+ });
66
+ }
67
+ catch {
68
+ if (gatewayProcess) {
69
+ gatewayProcess.kill("SIGTERM");
70
+ }
71
+ }
72
+ gatewayProcess = null;
73
+ }
74
+ async function registerChannels(bin, stateDir, config) {
75
+ let secrets = {};
76
+ try {
77
+ const raw = await readFile(resolve(stateDir, "secrets.json"), "utf-8");
78
+ secrets = JSON.parse(raw);
79
+ }
80
+ catch {
81
+ // No secrets file yet
82
+ }
83
+ const env = openclawEnv(stateDir);
84
+ if (config.channels.whatsapp?.enabled && secrets.whatsapp) {
85
+ try {
86
+ await execFileAsync(bin, ["channels", "add", "--channel", "whatsapp"], { env });
87
+ }
88
+ catch {
89
+ // Already exists — fine
90
+ }
91
+ }
92
+ if (config.channels.telegram?.enabled && secrets.telegram?.token) {
93
+ try {
94
+ await execFileAsync(bin, ["channels", "add", "--channel", "telegram", "--token", secrets.telegram.token], { env });
95
+ }
96
+ catch {
97
+ // Already exists — fine
98
+ }
99
+ // Apply allowlist if present
100
+ const allowlist = secrets.telegram?.allowlist ?? [];
101
+ if (allowlist.length > 0) {
102
+ try {
103
+ const configPath = resolve(stateDir, "openclaw.json");
104
+ const ocConfig = JSON.parse(await readFile(configPath, "utf-8"));
105
+ if (ocConfig.channels?.telegram) {
106
+ ocConfig.channels.telegram.allowFrom = allowlist;
107
+ ocConfig.channels.telegram.dmPolicy = "allowlist";
108
+ await writeFile(configPath, JSON.stringify(ocConfig, null, 2) + "\n");
109
+ }
110
+ }
111
+ catch {
112
+ // Config not ready yet
113
+ }
114
+ }
115
+ }
116
+ if (config.channels.teams?.enabled && secrets.teams?.appSecret) {
117
+ try {
118
+ await execFileAsync(bin, ["channels", "add", "--channel", "teams"], { env });
119
+ }
120
+ catch {
121
+ // Already exists — fine
122
+ }
123
+ }
124
+ }
125
+ /**
126
+ * Set up exec-approvals so the agent can run tools without prompting.
127
+ * Safe for local enterprise deployments where the admin controls the box.
128
+ */
129
+ async function ensureAutoApprove(bin, env, gateway = false) {
130
+ try {
131
+ // security: "full" skips all approval prompts (equivalent to elevated mode).
132
+ // ask: "off" disables interactive prompts entirely.
133
+ // askFallback: "full" auto-allows if no UI is reachable.
134
+ const approvals = JSON.stringify({
135
+ version: 1,
136
+ defaults: {
137
+ security: "full",
138
+ ask: "off",
139
+ askFallback: "full",
140
+ },
141
+ agents: {
142
+ "*": {
143
+ security: "full",
144
+ ask: "off",
145
+ askFallback: "full",
146
+ },
147
+ main: {
148
+ security: "full",
149
+ ask: "off",
150
+ askFallback: "full",
151
+ },
152
+ },
153
+ });
154
+ const args = ["approvals", "set", "--stdin"];
155
+ if (gateway)
156
+ args.push("--gateway");
157
+ const proc = spawn(bin, args, { env, stdio: ["pipe", "pipe", "pipe"] });
158
+ proc.stdin.write(approvals);
159
+ proc.stdin.end();
160
+ await new Promise((resolve, reject) => {
161
+ proc.on("close", (code) => (code === 0 ? resolve() : reject(new Error(`exit ${code}`))));
162
+ proc.on("error", reject);
163
+ });
164
+ }
165
+ catch (err) {
166
+ console.warn("[leedab] auto-approve setup failed:", err);
167
+ }
168
+ }
169
+ /**
170
+ * Copy default workspace files (AGENTS.md, SOUL.md, etc.) into workspace/
171
+ * on first run. Existing files are never overwritten.
172
+ */
173
+ async function seedWorkspace() {
174
+ const srcDir = join(dirname(fileURLToPath(import.meta.url)), "templates", "workspace");
175
+ const destDir = resolve("workspace");
176
+ await mkdir(destDir, { recursive: true });
177
+ try {
178
+ const files = await readdir(srcDir);
179
+ for (const file of files) {
180
+ const dest = join(destDir, file);
181
+ try {
182
+ await access(dest);
183
+ // Already exists — don't overwrite
184
+ }
185
+ catch {
186
+ await copyFile(join(srcDir, file), dest);
187
+ }
188
+ }
189
+ }
190
+ catch (err) {
191
+ console.warn("[leedab] workspace seed failed:", err);
192
+ }
193
+ }
194
+ async function waitForGateway(port, timeoutMs = 20000) {
195
+ const bin = resolveOpenClawBin();
196
+ const stateDir = resolve(".leedab");
197
+ const start = Date.now();
198
+ while (Date.now() - start < timeoutMs) {
199
+ try {
200
+ // Use openclaw's own health check which knows the WS protocol
201
+ await execFileAsync(bin, ["gateway", "health"], {
202
+ env: openclawEnv(stateDir),
203
+ timeout: 5000,
204
+ });
205
+ return;
206
+ }
207
+ catch {
208
+ // Not ready yet
209
+ }
210
+ await new Promise((r) => setTimeout(r, 1000));
211
+ }
212
+ throw new Error(`Gateway did not start within ${timeoutMs}ms`);
213
+ }
@@ -0,0 +1,6 @@
1
+ export { startGateway, stopGateway } from "./gateway.js";
2
+ export { loadConfig, initConfig } from "./config/index.js";
3
+ export { setupChannels } from "./channels/index.js";
4
+ export { runOnboard } from "./onboard/index.js";
5
+ export { startDashboard } from "./dashboard/server.js";
6
+ export type { LeedABConfig } from "./config/schema.js";
package/dist/index.js ADDED
@@ -0,0 +1,5 @@
1
+ export { startGateway, stopGateway } from "./gateway.js";
2
+ export { loadConfig, initConfig } from "./config/index.js";
3
+ export { setupChannels } from "./channels/index.js";
4
+ export { runOnboard } from "./onboard/index.js";
5
+ export { startDashboard } from "./dashboard/server.js";
@@ -0,0 +1,27 @@
1
+ export interface LicenseInfo {
2
+ key: string;
3
+ valid: boolean;
4
+ tier: string;
5
+ status: string;
6
+ seatsUsed: number;
7
+ maxSeats: number;
8
+ validatedAt: string;
9
+ }
10
+ /**
11
+ * Validate a license key against the API.
12
+ */
13
+ export declare function validateLicenseKey(key: string): Promise<LicenseInfo>;
14
+ /**
15
+ * Save license info to disk.
16
+ */
17
+ export declare function saveLicense(license: LicenseInfo): Promise<void>;
18
+ /**
19
+ * Load cached license from disk. Returns null if not found.
20
+ */
21
+ export declare function loadLicense(): Promise<LicenseInfo | null>;
22
+ /**
23
+ * Ensure we have a valid license. Returns the license if valid.
24
+ * Revalidates if the cached license is stale.
25
+ * Returns null if no license or license is invalid.
26
+ */
27
+ export declare function ensureLicense(): Promise<LicenseInfo | null>;
@@ -0,0 +1,92 @@
1
+ import { readFile, writeFile, mkdir } from "node:fs/promises";
2
+ import { resolve } from "node:path";
3
+ const LICENSE_PATH = resolve(".leedab", "license.json");
4
+ const VERIFY_URL = "https://api.leedab.com/api/v1/licensing/verify";
5
+ const REVALIDATE_DAYS = 7;
6
+ /**
7
+ * Validate a license key against the API.
8
+ */
9
+ export async function validateLicenseKey(key) {
10
+ const res = await fetch(VERIFY_URL, {
11
+ method: "POST",
12
+ headers: {
13
+ Authorization: `Bearer ${key}`,
14
+ "Content-Type": "application/json",
15
+ },
16
+ });
17
+ if (!res.ok) {
18
+ return {
19
+ key,
20
+ valid: false,
21
+ tier: "none",
22
+ status: "invalid",
23
+ seatsUsed: 0,
24
+ maxSeats: 0,
25
+ validatedAt: new Date().toISOString(),
26
+ };
27
+ }
28
+ const data = await res.json();
29
+ return {
30
+ key,
31
+ valid: data.valid === true,
32
+ tier: data.tier ?? "starter",
33
+ status: data.status ?? "unknown",
34
+ seatsUsed: data.seats_used ?? 0,
35
+ maxSeats: data.max_seats ?? 1,
36
+ validatedAt: new Date().toISOString(),
37
+ };
38
+ }
39
+ /**
40
+ * Save license info to disk.
41
+ */
42
+ export async function saveLicense(license) {
43
+ await mkdir(resolve(".leedab"), { recursive: true });
44
+ await writeFile(LICENSE_PATH, JSON.stringify(license, null, 2) + "\n");
45
+ }
46
+ /**
47
+ * Load cached license from disk. Returns null if not found.
48
+ */
49
+ export async function loadLicense() {
50
+ try {
51
+ const raw = await readFile(LICENSE_PATH, "utf-8");
52
+ return JSON.parse(raw);
53
+ }
54
+ catch {
55
+ return null;
56
+ }
57
+ }
58
+ /**
59
+ * Check if the cached license needs revalidation (older than REVALIDATE_DAYS).
60
+ */
61
+ function isStale(license) {
62
+ const validated = new Date(license.validatedAt).getTime();
63
+ const now = Date.now();
64
+ const daysSince = (now - validated) / (1000 * 60 * 60 * 24);
65
+ return daysSince > REVALIDATE_DAYS;
66
+ }
67
+ /**
68
+ * Ensure we have a valid license. Returns the license if valid.
69
+ * Revalidates if the cached license is stale.
70
+ * Returns null if no license or license is invalid.
71
+ */
72
+ export async function ensureLicense() {
73
+ const cached = await loadLicense();
74
+ if (!cached)
75
+ return null;
76
+ // If cached and fresh, trust it
77
+ if (cached.valid && !isStale(cached)) {
78
+ return cached;
79
+ }
80
+ // Revalidate
81
+ try {
82
+ const fresh = await validateLicenseKey(cached.key);
83
+ await saveLicense(fresh);
84
+ return fresh.valid ? fresh : null;
85
+ }
86
+ catch {
87
+ // Offline — trust cache if it was valid (grace period)
88
+ if (cached.valid)
89
+ return cached;
90
+ return null;
91
+ }
92
+ }
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Initialize the memory directory with default templates.
3
+ * The agent's built-in memory system will use these files.
4
+ */
5
+ export declare function initMemory(memoryDir: string): Promise<void>;
6
+ /**
7
+ * Ensure today's daily note exists.
8
+ */
9
+ export declare function ensureDailyNote(memoryDir: string): Promise<string>;
@@ -0,0 +1,41 @@
1
+ import { mkdir, writeFile, access } from "node:fs/promises";
2
+ import { resolve } from "node:path";
3
+ const MEMORY_TEMPLATE = `# Memory
4
+
5
+ Long-term memory for this LeedAB deployment. Durable facts, preferences, and decisions are stored here.
6
+ `;
7
+ const DAILY_TEMPLATE = `# Daily Notes — {{date}}
8
+
9
+ Running context and observations for today.
10
+ `;
11
+ /**
12
+ * Initialize the memory directory with default templates.
13
+ * The agent's built-in memory system will use these files.
14
+ */
15
+ export async function initMemory(memoryDir) {
16
+ const dir = resolve(memoryDir);
17
+ await mkdir(dir, { recursive: true });
18
+ const memoryPath = resolve(dir, "Memory.md");
19
+ try {
20
+ await access(memoryPath);
21
+ }
22
+ catch {
23
+ await writeFile(memoryPath, MEMORY_TEMPLATE);
24
+ }
25
+ }
26
+ /**
27
+ * Ensure today's daily note exists.
28
+ */
29
+ export async function ensureDailyNote(memoryDir) {
30
+ const dir = resolve(memoryDir);
31
+ const today = new Date().toISOString().split("T")[0];
32
+ const dailyPath = resolve(dir, `${today}.md`);
33
+ try {
34
+ await access(dailyPath);
35
+ }
36
+ catch {
37
+ const content = DAILY_TEMPLATE.replace("{{date}}", today);
38
+ await writeFile(dailyPath, content);
39
+ }
40
+ return dailyPath;
41
+ }
@@ -0,0 +1,4 @@
1
+ /**
2
+ * Run the full onboarding wizard. Writes leedab.config.json and .leedab/ secrets.
3
+ */
4
+ export declare function runOnboard(): Promise<void>;
@@ -0,0 +1,263 @@
1
+ import { writeFile, readFile, mkdir, readdir, copyFile } from "node:fs/promises";
2
+ import { resolve, join, dirname } from "node:path";
3
+ import { fileURLToPath } from "node:url";
4
+ import chalk from "chalk";
5
+ import inquirer from "inquirer";
6
+ import { printWelcome } from "./steps/welcome.js";
7
+ import { onboardProvider } from "./steps/provider.js";
8
+ import { onboardWhatsApp } from "./steps/whatsapp.js";
9
+ import { onboardTelegram } from "./steps/telegram.js";
10
+ import { onboardTeams } from "./steps/teams.js";
11
+ import { DEFAULT_CONFIG } from "../config/schema.js";
12
+ import { initMemory } from "../memory/index.js";
13
+ import { addEntry, listEntries } from "../vault.js";
14
+ import { validateLicenseKey, saveLicense, loadLicense } from "../license.js";
15
+ /**
16
+ * Run the full onboarding wizard. Writes leedab.config.json and .leedab/ secrets.
17
+ */
18
+ export async function runOnboard() {
19
+ printWelcome();
20
+ // --- License key (required) ---
21
+ const existingLicense = await loadLicense();
22
+ if (existingLicense?.valid) {
23
+ console.log(chalk.green(` License: ${existingLicense.tier} plan (${existingLicense.key.slice(0, 12)}...)\n`));
24
+ }
25
+ else {
26
+ console.log(chalk.bold(" License Key\n"));
27
+ console.log(chalk.dim(" Get your key at ") + chalk.cyan("https://leedab.com") + "\n");
28
+ let licensed = false;
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
+ ]);
39
+ process.stdout.write(chalk.dim(" Validating..."));
40
+ const result = await validateLicenseKey(licenseKey.trim());
41
+ if (result.valid) {
42
+ await saveLicense(result);
43
+ console.log(chalk.green(` valid (${result.tier} plan)`));
44
+ licensed = true;
45
+ }
46
+ else {
47
+ console.log(chalk.red(" invalid key."));
48
+ const { retry } = await inquirer.prompt([
49
+ { type: "confirm", name: "retry", message: "Try again?", default: true },
50
+ ]);
51
+ if (!retry) {
52
+ console.log(chalk.red("\n A valid license key is required to use LeedAB."));
53
+ console.log(chalk.dim(" Get one at ") + chalk.cyan("https://leedab.com\n"));
54
+ process.exit(1);
55
+ }
56
+ }
57
+ }
58
+ }
59
+ // Agent name
60
+ const { agentName } = await inquirer.prompt([
61
+ {
62
+ type: "input",
63
+ name: "agentName",
64
+ message: "Agent display name:",
65
+ default: "LeedAB",
66
+ },
67
+ ]);
68
+ // Vertical selection
69
+ const { vertical } = await inquirer.prompt([
70
+ {
71
+ type: "list",
72
+ name: "vertical",
73
+ message: "What type of operations?",
74
+ choices: [
75
+ { name: "General purpose", value: "general" },
76
+ { name: "Supply chain & logistics", value: "supply-chain" },
77
+ ],
78
+ },
79
+ ]);
80
+ const config = {
81
+ ...DEFAULT_CONFIG,
82
+ agent: { ...DEFAULT_CONFIG.agent, name: agentName, vertical },
83
+ };
84
+ // Secrets go to .leedab/secrets.json (gitignored)
85
+ const secrets = {};
86
+ // --- Seed vertical templates ---
87
+ if (vertical !== "general") {
88
+ await seedVerticalTemplates(vertical);
89
+ await seedVerticalVault(vertical);
90
+ console.log(chalk.green(`\n Loaded ${vertical} templates and vault entries.`));
91
+ }
92
+ // --- LLM Provider ---
93
+ const provider = await onboardProvider();
94
+ if (provider) {
95
+ config.agent.model = provider.model;
96
+ }
97
+ // --- Telegram (recommended) ---
98
+ const telegram = await onboardTelegram();
99
+ if (telegram) {
100
+ config.channels.telegram = telegram.config;
101
+ secrets.telegram = { token: telegram.token, allowlist: telegram.allowlist };
102
+ }
103
+ // --- WhatsApp ---
104
+ const whatsapp = await onboardWhatsApp();
105
+ if (whatsapp) {
106
+ const { _businessAccountId, _accessToken, ...channelConfig } = whatsapp;
107
+ config.channels.whatsapp = channelConfig;
108
+ secrets.whatsapp = {
109
+ businessAccountId: _businessAccountId,
110
+ accessToken: _accessToken,
111
+ };
112
+ }
113
+ // --- Teams ---
114
+ const teams = await onboardTeams();
115
+ if (teams) {
116
+ config.channels.teams = teams.config;
117
+ secrets.teams = { appSecret: teams.appSecret };
118
+ }
119
+ // --- Credential Vault ---
120
+ console.log(chalk.bold("\n Credential Vault\n"));
121
+ console.log(chalk.dim(" The agent uses stored credentials to access services via browser."));
122
+ console.log(chalk.dim(" (Gmail, Google Drive, CRM, carrier portals, etc.)\n"));
123
+ const { addCreds } = await inquirer.prompt([
124
+ {
125
+ type: "confirm",
126
+ name: "addCreds",
127
+ message: "Store credentials for services the agent should access?",
128
+ default: true,
129
+ },
130
+ ]);
131
+ if (addCreds) {
132
+ let more = true;
133
+ while (more) {
134
+ const cred = await inquirer.prompt([
135
+ {
136
+ type: "input",
137
+ name: "service",
138
+ message: "Service name (e.g. gmail, salesforce):",
139
+ validate: (v) => v.trim().length > 0 || "Required",
140
+ },
141
+ {
142
+ type: "input",
143
+ name: "url",
144
+ message: "Login URL:",
145
+ },
146
+ {
147
+ type: "input",
148
+ name: "username",
149
+ message: "Username / email:",
150
+ },
151
+ {
152
+ type: "password",
153
+ name: "password",
154
+ message: "Password:",
155
+ mask: "*",
156
+ },
157
+ {
158
+ type: "input",
159
+ name: "notes",
160
+ message: "Notes for agent (optional):",
161
+ },
162
+ ]);
163
+ await addEntry(cred.service.trim(), {
164
+ url: cred.url || undefined,
165
+ username: cred.username || undefined,
166
+ password: cred.password || undefined,
167
+ notes: cred.notes || undefined,
168
+ });
169
+ console.log(chalk.green(` Added ${cred.service.trim()} to vault.`));
170
+ const { another } = await inquirer.prompt([
171
+ {
172
+ type: "confirm",
173
+ name: "another",
174
+ message: "Add another service?",
175
+ default: false,
176
+ },
177
+ ]);
178
+ more = another;
179
+ }
180
+ }
181
+ else {
182
+ console.log(chalk.dim(" Add later with ") +
183
+ chalk.cyan("leedab vault add <service>"));
184
+ }
185
+ // --- Write everything ---
186
+ const configDir = resolve(".leedab");
187
+ await mkdir(configDir, { recursive: true });
188
+ // Write config (no secrets)
189
+ await writeFile(resolve("leedab.config.json"), JSON.stringify(config, null, 2) + "\n");
190
+ // Write secrets separately
191
+ if (Object.keys(secrets).length > 0) {
192
+ await writeFile(resolve(configDir, "secrets.json"), JSON.stringify(secrets, null, 2) + "\n");
193
+ }
194
+ // Init memory directory
195
+ await initMemory(config.memory.dir);
196
+ // Summary
197
+ console.log(chalk.bold("\n Setup complete!\n"));
198
+ const channels = [];
199
+ if (whatsapp)
200
+ channels.push("WhatsApp");
201
+ if (telegram)
202
+ channels.push("Telegram");
203
+ if (teams)
204
+ channels.push("Teams");
205
+ if (channels.length > 0) {
206
+ console.log(chalk.green(` Channels: ${channels.join(", ")}`));
207
+ }
208
+ else {
209
+ console.log(chalk.yellow(" No channels connected. Run " +
210
+ chalk.cyan("leedab onboard") +
211
+ " anytime to add them."));
212
+ }
213
+ const vaultEntries = await listEntries();
214
+ if (vaultEntries.length > 0) {
215
+ console.log(chalk.green(` Vault: ${vaultEntries.length} service(s) stored`));
216
+ }
217
+ console.log(chalk.dim(`\n Config: ./leedab.config.json`));
218
+ console.log(chalk.dim(` Secrets: ./.leedab/secrets.json`));
219
+ console.log(chalk.dim(` Vault: ./.leedab/vault.json`));
220
+ console.log(chalk.dim(` Memory: ./${config.memory.dir}/`));
221
+ console.log(chalk.bold(`\n Start your agent: ${chalk.cyan("leedab start")}\n`));
222
+ }
223
+ /**
224
+ * Copy vertical-specific workspace templates (SOUL.md, HEARTBEAT.md, etc.)
225
+ * into workspace/, overwriting defaults.
226
+ */
227
+ async function seedVerticalTemplates(vertical) {
228
+ const srcDir = join(dirname(fileURLToPath(import.meta.url)), "..", "templates", "verticals", vertical);
229
+ const destDir = resolve("workspace");
230
+ await mkdir(destDir, { recursive: true });
231
+ try {
232
+ const files = await readdir(srcDir);
233
+ for (const file of files) {
234
+ if (file.endsWith(".json"))
235
+ continue; // Skip vault-template.json
236
+ await copyFile(join(srcDir, file), join(destDir, file));
237
+ }
238
+ }
239
+ catch {
240
+ // Templates directory may not exist for this vertical
241
+ }
242
+ }
243
+ /**
244
+ * Pre-populate vault with entries from the vertical's vault-template.json.
245
+ * Only adds entries that don't already exist (won't overwrite user credentials).
246
+ */
247
+ async function seedVerticalVault(vertical) {
248
+ const templatePath = join(dirname(fileURLToPath(import.meta.url)), "..", "templates", "verticals", vertical, "vault-template.json");
249
+ try {
250
+ const raw = await readFile(templatePath, "utf-8");
251
+ const template = JSON.parse(raw);
252
+ for (const [service, entry] of Object.entries(template)) {
253
+ // Only add if service doesn't already exist in vault
254
+ const existing = await listEntries();
255
+ if (!existing.some((e) => e.service === service)) {
256
+ await addEntry(service, entry);
257
+ }
258
+ }
259
+ }
260
+ catch {
261
+ // No vault template for this vertical
262
+ }
263
+ }
@@ -0,0 +1,13 @@
1
+ export interface OAuthResult {
2
+ code: string;
3
+ state?: string;
4
+ }
5
+ /**
6
+ * Spin up a temporary local HTTP server to receive OAuth callbacks.
7
+ * Returns a promise that resolves with the authorization code.
8
+ */
9
+ export declare function createOAuthCallbackServer(port?: number): {
10
+ url: string;
11
+ waitForCallback: () => Promise<OAuthResult>;
12
+ close: () => void;
13
+ };