From b1f72e0150ae8a715944d83347a2b68009890d14 Mon Sep 17 00:00:00 2001 From: Cameron Date: Mon, 23 Feb 2026 15:04:25 -0800 Subject: [PATCH] fix: auto-resolve LETTABOT_API_KEY from lettabot-api.json (#367) --- src/api/auth.ts | 33 ++++++++++++++++++++++++++++++++- src/cli/message.ts | 10 ++++------ 2 files changed, 36 insertions(+), 7 deletions(-) diff --git a/src/api/auth.ts b/src/api/auth.ts index c70798d..9976ff5 100644 --- a/src/api/auth.ts +++ b/src/api/auth.ts @@ -21,7 +21,38 @@ export function generateApiKey(): string { } /** - * Load API key from file or environment, or generate new one + * Load API key from environment or file. Throws if not found. + * Use this in CLI tools where generating a new key would be incorrect. + */ +export function loadApiKey(): string { + // 1. Check environment variable first + if (process.env.LETTABOT_API_KEY) { + return process.env.LETTABOT_API_KEY; + } + + // 2. Try to load from file + const filePath = path.resolve(process.cwd(), API_KEY_FILE); + if (fs.existsSync(filePath)) { + try { + const data = fs.readFileSync(filePath, 'utf-8'); + const store: ApiKeyStore = JSON.parse(data); + if (store.apiKey && typeof store.apiKey === 'string') { + return store.apiKey; + } + } catch { + // Fall through to error + } + } + + throw new Error( + 'API key not found. Start the lettabot server first (it generates lettabot-api.json), ' + + 'or set LETTABOT_API_KEY environment variable.' + ); +} + +/** + * Load API key from file or environment, or generate new one. + * Use this on the server side where generating a key on first run is expected. */ export function loadOrGenerateApiKey(): string { // 1. Check environment variable first diff --git a/src/cli/message.ts b/src/cli/message.ts index df1284a..fc189d1 100644 --- a/src/cli/message.ts +++ b/src/cli/message.ts @@ -12,6 +12,7 @@ // Config loaded from lettabot.yaml import { loadAppConfigOrExit, applyConfigToEnv } from '../config/index.js'; +import { loadApiKey } from '../api/auth.js'; const config = loadAppConfigOrExit(); applyConfigToEnv(config); import { existsSync, readFileSync } from 'node:fs'; @@ -150,11 +151,8 @@ async function sendViaApi( } ): Promise { const apiUrl = process.env.LETTABOT_API_URL || 'http://localhost:8080'; - const apiKey = process.env.LETTABOT_API_KEY; - - if (!apiKey) { - throw new Error('LETTABOT_API_KEY not set. Check bot server logs for the key.'); - } + // Resolve API key: env var > lettabot-api.json (never generate -- that's the server's job) + const apiKey = loadApiKey(); // Check if file exists if (options.filePath && !existsSync(options.filePath)) { @@ -357,7 +355,7 @@ Environment variables: SLACK_BOT_TOKEN Required for Slack DISCORD_BOT_TOKEN Required for Discord SIGNAL_PHONE_NUMBER Required for Signal (text only, no files) - LETTABOT_API_KEY Required for WhatsApp (text and files) + LETTABOT_API_KEY Override API key (auto-read from lettabot-api.json if not set) LETTABOT_API_URL API server URL (default: http://localhost:8080) SIGNAL_CLI_REST_API_URL Signal daemon URL (default: http://127.0.0.1:8090)