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);
}