Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

chore: db path resolution #3438

Merged
merged 2 commits into from
Feb 11, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
129 changes: 57 additions & 72 deletions packages/cli/src/commands/character.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
// src/commands/agent.ts
import { MessageExampleSchema } from "@elizaos/core"
import type { MessageExample } from "@elizaos/core";
import { Command } from "commander"
import prompts from "prompts"
import { logger } from "../utils/logger"
import { z } from "zod"
import { getConfig } from "../utils/get-config"
import { handleError } from "../utils/handle-error"
import { Database, SqliteDatabaseAdapter } from "@elizaos-plugins/sqlite"
import { promises as fs } from "node:fs"
import { Database, SqliteDatabaseAdapter } from "@elizaos-plugins/sqlite";
import type { MessageExample, UUID } from "@elizaos/core";
import { MessageExampleSchema } from "@elizaos/core";
import { Command } from "commander";
import fs from "node:fs";
import prompts from "prompts";
import { v4 as uuid } from "uuid";
import type { UUID } from "@elizaos/core";
import { z } from "zod";
import { getConfig } from "../utils/get-config";
import { handleError } from "../utils/handle-error";
import { logger } from "../utils/logger";
import { resolveDatabasePath } from "../utils/resolve-database-path";

const characterSchema = z.object({
id: z.string().uuid(),
Expand Down Expand Up @@ -46,9 +46,10 @@ async function collectCharacterData(
let currentStep = 0;
const steps = ['name', 'bio', 'lore', 'adjectives', 'postExamples', 'messageExamples'];

let response: { value?: string };

while (currentStep < steps.length) {
const field = steps[currentStep];
let response;

switch (field) {
case 'name':
Expand Down Expand Up @@ -140,31 +141,26 @@ character
.description("list all characters")
.action(async () => {
try {
const cwd = process.cwd()
const config = await getConfig(cwd)
if (!config) {
logger.error("No project.json found. Please run init first.")
process.exit(1)
}

const db = new Database((config.database.config as { path: string }).path)
const adapter = new SqliteDatabaseAdapter(db)
await adapter.init()
const dbPath = await resolveDatabasePath({ requiredConfig: false });

const db = new Database(dbPath);
const adapter = new SqliteDatabaseAdapter(db);
await adapter.init();

const characters = await adapter.listCharacters()
const characters = await adapter.listCharacters();

if (characters.length === 0) {
logger.info("No characters found")
logger.info("No characters found");
} else {
logger.info("\nCharacters:")
logger.info("\nCharacters:");
for (const character of characters) {
logger.info(` ${character.name} (${character.id})`)
logger.info(` ${character.name} (${character.id})`);
}
}

await adapter.close()
await adapter.close();
} catch (error) {
handleError(error)
handleError(error);
}
})

Expand All @@ -173,22 +169,15 @@ character
.description("create a new character")
.action(async () => {
try {
const cwd = process.cwd()
const config = await getConfig(cwd)
if (!config) {
logger.error("No project.json found. Please run init first.")
process.exit(1)
}

logger.info("\nCreating new character (type 'back' or 'forward' to navigate)")

const formData = await collectCharacterData()
if (!formData) {
logger.info("Character creation cancelled")
return
}

const db = new Database((config.database.config as { path: string }).path)
const dbPath = await resolveDatabasePath({ requiredConfig: true });
const db = new Database(dbPath);
const adapter = new SqliteDatabaseAdapter(db)
await adapter.init()

Expand All @@ -213,8 +202,8 @@ character

const characterToCreate = {
...characterData,
messageExamples: characterData.messageExamples.map(
(msgArr: any) => msgArr.map((msg: any) => ({
messageExamples: (characterData.messageExamples as MessageExample[][]).map(
(msgArr: MessageExample[]): MessageExample[] => msgArr.map((msg: MessageExample) => ({
user: msg.user || "unknown",
content: msg.content
}))
Expand Down Expand Up @@ -252,21 +241,15 @@ character
.argument("<character-id>", "character ID")
.action(async (characterId) => {
try {
const cwd = process.cwd()
const config = await getConfig(cwd)
if (!config) {
logger.error("No project.json found. Please run init first.")
process.exit(1)
}
const dbPath = await resolveDatabasePath({ requiredConfig: true });
const db = new Database(dbPath);
const adapter = new SqliteDatabaseAdapter(db);
await adapter.init();

const db = new Database((config.database.config as { path: string }).path)
const adapter = new SqliteDatabaseAdapter(db)
await adapter.init()

const existingCharacter = await adapter.getCharacter(characterId)
const existingCharacter = await adapter.getCharacter(characterId);
if (!existingCharacter) {
logger.error(`Character ${characterId} not found`)
process.exit(1)
logger.error(`Character ${characterId} not found`);
process.exit(1);
}

logger.info(`\nEditing character ${existingCharacter.name} (type 'back' or 'forward' to navigate)`)
Expand All @@ -277,8 +260,8 @@ character
lore: existingCharacter.lore || [],
adjectives: existingCharacter.adjectives || [],
postExamples: existingCharacter.postExamples || [],
messageExamples: (existingCharacter.messageExamples || []).map(
(msgArr: any) => msgArr.map((msg: any) => ({
messageExamples: (existingCharacter.messageExamples || [] as MessageExample[][]).map(
(msgArr: MessageExample[]): MessageExample[] => msgArr.map((msg: MessageExample) => ({
user: msg.user ?? "unknown",
content: msg.content
}))
Expand Down Expand Up @@ -320,19 +303,27 @@ character
.command("import")
.description("import a character from file")
.argument("<file>", "JSON file path")
.action(async (file) => {
.action(async (fileArg) => {
try {
const cwd = process.cwd()
const config = await getConfig(cwd)
if (!config) {
logger.error("No project.json found. Please run init first.")
process.exit(1)
// Use the provided argument if available; otherwise, prompt the user.
const filePath: string = fileArg || (await prompts({
type: "text",
name: "file",
message: "Enter the path to the Character JSON file",
})).file;

if (!filePath) {
logger.info("Import cancelled")
return
}

const characterData = JSON.parse(await fs.readFile(file, "utf8"))
const characterData = JSON.parse(await fs.promises.readFile(filePath, "utf8"))
const character = characterSchema.parse(characterData)

const db = new Database((config.database.config as { path: string }).path)
// resolve database path
const dbPath = await resolveDatabasePath({ requiredConfig: true })

const db = new Database(dbPath)
const adapter = new SqliteDatabaseAdapter(db)
await adapter.init()

Expand Down Expand Up @@ -386,7 +377,7 @@ character
}

const outputPath = opts.output || `${character.name}.json`
await fs.writeFile(outputPath, JSON.stringify(character, null, 2))
await fs.promises.writeFile(outputPath, JSON.stringify(character, null, 2))
logger.success(`Exported character to ${outputPath}`)

await adapter.close()
Expand All @@ -401,14 +392,8 @@ character
.argument("<character-id>", "character ID")
.action(async (characterId) => {
try {
const cwd = process.cwd()
const config = await getConfig(cwd)
if (!config) {
logger.error("No project.json found. Please run init first.")
process.exit(1)
}

const db = new Database((config.database.config as { path: string }).path)
const dbPath = await resolveDatabasePath({ requiredConfig: true })
const db = new Database(dbPath)
const adapter = new SqliteDatabaseAdapter(db)
await adapter.init()

Expand Down
81 changes: 81 additions & 0 deletions packages/cli/src/utils/resolve-database-path.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
import { execaCommand } from "execa";
import fs from "node:fs";
import path from "node:path";
import prompts from "prompts";
import { getConfig } from "./get-config";
import { logger } from "./logger";

// Helper function to search for db.sqlite using available shell commands.
async function searchDatabaseFile(): Promise<string> {
const commands = ["find . -name db.sqlite", "dir /s /b db.sqlite"];
for (const cmd of commands) {
try {
const { stdout } = await execaCommand(cmd);
const result = stdout.trim();
if (result) {
return result;
}
} catch (error) {
logger.error(`Error executing command "${cmd}":`, error);
}
}
return "";
}

export async function resolveDatabasePath(options?: { requiredConfig?: boolean }): Promise<string> {
const { requiredConfig = true } = options || {};
const cwd = process.cwd();
const config = await getConfig(cwd);

// If a project config exists, use its database path.
if (config) {
return (config.database.config as { path: string }).path;
}

// For commands that require an initialized project, exit early.
if (requiredConfig) {
logger.error("No project.json found. Please run init first.");
process.exit(1);
}

// Otherwise, try to locate db.sqlite using shell commands.
let dbPath = await searchDatabaseFile();

// If path is resolved, log it.
if (dbPath) {
logger.info(`Resolved database path: ${dbPath}`);
}

// If no database file was found, prompt the user to provide one.
if (!dbPath) {
logger.info("No db.sqlite found. Please provide a path to create a database.");
const dbInput = await prompts({
type: "text",
name: "value",
message: "Enter path to create a database:",
});

// check if path was provided
if (!dbInput.value) {
logger.error("No path provided. Please provide a path to create a database.");
process.exit(1);
}

// check if the path is valid
if (!fs.existsSync(dbInput.value)) {
logger.error("Invalid path. Please provide a valid path or directory.");
process.exit(1);
}
wtfsayo marked this conversation as resolved.
Show resolved Hide resolved

// Use the input directly if it contains ".sqlite", otherwise append it.
dbPath = dbInput.value.includes(".sqlite") ? dbInput.value : path.join(dbInput.value, "db.sqlite");

// Create the file if it does not exist.
if (!fs.existsSync(dbPath)) {
await fs.promises.writeFile(dbPath, "");
logger.success(`Created db.sqlite in ${dbInput.value}`);
}
}

return dbPath;
}
Loading