diff --git a/src/agent/client.ts b/src/agent/client.ts index 6b0c331..5d6d34f 100644 --- a/src/agent/client.ts +++ b/src/agent/client.ts @@ -1,5 +1,5 @@ import Letta from "@letta-ai/letta-client"; -import { refreshAccessToken } from "../auth/oauth"; +import { LETTA_CLOUD_API_URL, refreshAccessToken } from "../auth/oauth"; import { settingsManager } from "../settings-manager"; export async function getClient() { @@ -40,14 +40,17 @@ export async function getClient() { } } + // Check if refresh token is missing for Letta Cloud const baseURL = process.env.LETTA_BASE_URL || settings.env?.LETTA_BASE_URL || - "https://api.letta.com"; + LETTA_CLOUD_API_URL; - if (!apiKey && baseURL === "https://api.letta.com") { + if (!apiKey && baseURL === LETTA_CLOUD_API_URL) { console.error("Missing LETTA_API_KEY"); - console.error("Run 'letta setup' to configure authentication"); + console.error( + "Run 'letta setup' to configure authentication or set your LETTA_API_KEY environment variable", + ); process.exit(1); } diff --git a/src/auth/oauth.ts b/src/auth/oauth.ts index 606f064..a23871a 100644 --- a/src/auth/oauth.ts +++ b/src/auth/oauth.ts @@ -5,11 +5,13 @@ import Letta from "@letta-ai/letta-client"; +export const LETTA_CLOUD_API_URL = "https://api.letta.com"; + export const OAUTH_CONFIG = { clientId: "ci-let-724dea7e98f4af6f8f370f4b1466200c", clientSecret: "", // Not needed for device code flow authBaseUrl: "https://app.letta.com", - apiBaseUrl: "https://api.letta.com", + apiBaseUrl: LETTA_CLOUD_API_URL, } as const; export interface DeviceCodeResponse { diff --git a/src/auth/setup-ui.tsx b/src/auth/setup-ui.tsx index 26f5c11..9eca704 100644 --- a/src/auth/setup-ui.tsx +++ b/src/auth/setup-ui.tsx @@ -148,7 +148,7 @@ export function SetupUI({ onComplete }: SetupUIProps) { {asciiLogo} Welcome to Letta Code! - Please choose how you'd like to authenticate: + Let's get you authenticated: diff --git a/src/cli/components/InputRich.tsx b/src/cli/components/InputRich.tsx index b9678a6..abfa6bd 100644 --- a/src/cli/components/InputRich.tsx +++ b/src/cli/components/InputRich.tsx @@ -3,6 +3,7 @@ import { Box, Text, useInput } from "ink"; import SpinnerLib from "ink-spinner"; import type { ComponentType } from "react"; import { useEffect, useRef, useState } from "react"; +import { LETTA_CLOUD_API_URL } from "../../auth/oauth"; import type { PermissionMode } from "../../permissions/mode"; import { permissionMode } from "../../permissions/mode"; import { settingsManager } from "../../settings-manager"; @@ -113,7 +114,7 @@ export function Input({ const serverUrl = process.env.LETTA_BASE_URL || settings.env?.LETTA_BASE_URL || - "https://api.letta.com"; + LETTA_CLOUD_API_URL; // Handle escape key for interrupt (when streaming) or double-escape-to-clear (when not) useInput((_input, key) => { diff --git a/src/index.ts b/src/index.ts index 815a55d..2168f03 100755 --- a/src/index.ts +++ b/src/index.ts @@ -5,6 +5,7 @@ import { getResumeData, type ResumeData } from "./agent/check-approval"; import { getClient } from "./agent/client"; import { initializeLoadedSkillsFlag, setAgentContext } from "./agent/context"; import type { AgentProvenance } from "./agent/create"; +import { LETTA_CLOUD_API_URL } from "./auth/oauth"; import { permissionMode } from "./permissions/mode"; import { settingsManager } from "./settings-manager"; import { loadTools, upsertToolsToServer } from "./tools/manager"; @@ -46,15 +47,15 @@ OPTIONS BEHAVIOR By default, letta auto-resumes the last agent used in the current directory - (stored in .letta/settings.local.json). - + (stored in .letta/settings.local.json). + Memory blocks (persona, human, project, skills) are shared between agents: - Global blocks (persona, human) are shared across all agents - Local blocks (project, skills) are shared within the current directory - + Use --new to create a new agent that reuses your global persona/human blocks. Use --fresh-blocks to create a completely isolated agent with new blocks. - + If no credentials are configured, you'll be prompted to authenticate via Letta Cloud OAuth on first run. @@ -64,10 +65,10 @@ EXAMPLES letta --new # New agent, keeps your persona/human blocks letta --fresh-blocks # New agent, all blocks fresh (full isolation) letta --agent agent_123 - + # inside the interactive session /logout # Clear credentials and exit - + # headless with JSON output (includes stats) letta -p "hello" --output-format json @@ -265,13 +266,28 @@ async function main() { const baseURL = process.env.LETTA_BASE_URL || settings.env?.LETTA_BASE_URL || - "https://api.letta.com"; + LETTA_CLOUD_API_URL; - if (!apiKey && baseURL === "https://api.letta.com") { + // Check if refresh token is missing for Letta Cloud (only when not using env var) + if ( + !isHeadless && + baseURL === LETTA_CLOUD_API_URL && + !settings.refreshToken + ) { + // For interactive mode, show setup flow + const { runSetup } = await import("./auth/setup"); + await runSetup(); + // After setup, restart main flow + return main(); + } + + if (!apiKey && baseURL === LETTA_CLOUD_API_URL) { // For headless mode, error out (assume automation context) if (isHeadless) { console.error("Missing LETTA_API_KEY"); - console.error("Run 'letta' in interactive mode to authenticate"); + console.error( + "Run 'letta' in interactive mode to authenticate or export the missing environment variable", + ); process.exit(1); }