feat: rename --from-af to --import and /download to /export (#902)

Co-authored-by: Letta <noreply@letta.com>
This commit is contained in:
Sarah Wooders
2026-02-11 15:39:48 -08:00
committed by GitHub
parent 77b4746dc2
commit 0c317652fd
5 changed files with 32 additions and 21 deletions

View File

@@ -330,6 +330,7 @@ const NON_STATE_COMMANDS = new Set([
"/search",
"/memory",
"/feedback",
"/export",
"/download",
]);
@@ -6752,11 +6753,11 @@ export default function App({
return { submitted: true };
}
// Special handling for /download command - download agent file
if (msg.trim() === "/download") {
// Special handling for /export command (also accepts legacy /download)
if (msg.trim() === "/export" || msg.trim() === "/download") {
const cmd = commandRunner.start(
msg.trim(),
"Downloading agent file...",
"Exporting agent file...",
);
setCommandRunning(true);
@@ -6824,7 +6825,7 @@ export default function App({
writeFileSync(fileName, JSON.stringify(fileContent, null, 2));
// Build success message
let summary = `AgentFile downloaded to ${fileName}`;
let summary = `AgentFile exported to ${fileName}`;
if (skills.length > 0) {
summary += `\n📦 Included ${skills.length} skill(s): ${skills.map((s) => s.name).join(", ")}`;
}

View File

@@ -135,12 +135,12 @@ export const commands: Record<string, Command> = {
return "Updating description...";
},
},
"/download": {
desc: "Download AgentFile (.af)",
"/export": {
desc: "Export AgentFile (.af)",
order: 26,
handler: () => {
// Handled specially in App.tsx to access agent ID and client
return "Downloading agent file...";
return "Exporting agent file...";
},
},
"/toolset": {
@@ -394,6 +394,13 @@ export const commands: Record<string, Command> = {
return "Opening agent browser...";
},
},
"/download": {
desc: "Export AgentFile (.af)",
hidden: true, // Legacy alias for /export
handler: () => {
return "Exporting agent file...";
},
},
};
/**

View File

@@ -78,7 +78,7 @@ OPTIONS
--from-agent <id> Inject agent-to-agent system reminder (headless mode)
--skills <path> Custom path to skills directory (default: .skills in current directory)
--sleeptime Enable sleeptime memory management (only for new agents)
--from-af <path> Create agent from an AgentFile (.af) template
--import <path> Create agent from an AgentFile (.af) template
Use @author/name to import from the agent registry
--memfs Enable memory filesystem for this agent
--no-memfs Disable memory filesystem for this agent
@@ -439,6 +439,7 @@ async function main(): Promise<void> {
skills: { type: "string" },
sleeptime: { type: "boolean" },
"from-af": { type: "string" },
import: { type: "string" },
memfs: { type: "boolean" },
"no-memfs": { type: "boolean" },
@@ -554,7 +555,9 @@ async function main(): Promise<void> {
const sleeptimeFlag = (values.sleeptime as boolean | undefined) ?? undefined;
const memfsFlag = values.memfs as boolean | undefined;
const noMemfsFlag = values["no-memfs"] as boolean | undefined;
const fromAfFile = values["from-af"] as string | undefined;
const fromAfFile =
(values.import as string | undefined) ??
(values["from-af"] as string | undefined);
const isHeadless = values.prompt || values.run || !process.stdin.isTTY;
// Fail if an unknown command/argument is passed (and we're not in headless mode where it might be a prompt)
@@ -694,7 +697,7 @@ async function main(): Promise<void> {
process.exit(1);
}
if (fromAfFile) {
console.error("Error: --conversation cannot be used with --from-af");
console.error("Error: --conversation cannot be used with --import");
process.exit(1);
}
if (shouldResume) {
@@ -723,24 +726,24 @@ async function main(): Promise<void> {
}
}
// Validate --from-af flag
// Validate --import flag (also accepts legacy --from-af)
// Detect if it's a registry handle (e.g., @author/name) or a local file path
let isRegistryImport = false;
if (fromAfFile) {
if (specifiedAgentId) {
console.error("Error: --from-af cannot be used with --agent");
console.error("Error: --import cannot be used with --agent");
process.exit(1);
}
if (specifiedAgentName) {
console.error("Error: --from-af cannot be used with --name");
console.error("Error: --import cannot be used with --name");
process.exit(1);
}
if (shouldResume) {
console.error("Error: --from-af cannot be used with --resume");
console.error("Error: --import cannot be used with --resume");
process.exit(1);
}
if (forceNew) {
console.error("Error: --from-af cannot be used with --new");
console.error("Error: --import cannot be used with --new");
process.exit(1);
}
@@ -753,7 +756,7 @@ async function main(): Promise<void> {
const parts = normalized.split("/");
if (parts.length !== 2 || !parts[0] || !parts[1]) {
console.error(
`Error: Invalid registry handle "${fromAfFile}". Use format: @author/agentname`,
`Error: Invalid registry handle "${fromAfFile}". Use format: letta --import @author/agentname`,
);
process.exit(1);
}

View File

@@ -100,9 +100,9 @@ describe("Startup Flow - Invalid Inputs", () => {
{ timeout: 70000 },
);
test("--from-af with nonexistent file shows error", async () => {
test("--import with nonexistent file shows error", async () => {
const result = await runCli(
["--from-af", "/nonexistent/path/agent.af", "-p", "test"],
["--import", "/nonexistent/path/agent.af", "-p", "test"],
{ expectExit: 1 },
);
expect(result.stderr).toContain("not found");

View File

@@ -104,13 +104,13 @@ describe("Startup Flow - Flag Conflicts", () => {
);
});
test("--conversation conflicts with --from-af", async () => {
test("--conversation conflicts with --import", async () => {
const result = await runCli(
["--conversation", "conv-123", "--from-af", "test.af"],
["--conversation", "conv-123", "--import", "test.af"],
{ expectExit: 1 },
);
expect(result.stderr).toContain(
"--conversation cannot be used with --from-af",
"--conversation cannot be used with --import",
);
});