From 3251fdc26ff218c5db6e08eaa6c7fc0fadc91af0 Mon Sep 17 00:00:00 2001 From: Sayo Date: Tue, 11 Feb 2025 15:35:51 +0530 Subject: [PATCH] chore: db path resolution (#3438) * db path resolution * Update resolve-database-path.ts --- packages/cli/src/commands/character.ts | 129 ++++++++---------- .../cli/src/utils/resolve-database-path.ts | 81 +++++++++++ 2 files changed, 138 insertions(+), 72 deletions(-) create mode 100644 packages/cli/src/utils/resolve-database-path.ts diff --git a/packages/cli/src/commands/character.ts b/packages/cli/src/commands/character.ts index 1e336e2c081..81d02cf7692 100644 --- a/packages/cli/src/commands/character.ts +++ b/packages/cli/src/commands/character.ts @@ -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(), @@ -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': @@ -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); } }) @@ -173,14 +169,6 @@ 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) { @@ -188,7 +176,8 @@ character 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() @@ -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 })) @@ -252,21 +241,15 @@ character .argument("", "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)`) @@ -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 })) @@ -320,19 +303,27 @@ character .command("import") .description("import a character from file") .argument("", "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() @@ -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() @@ -401,14 +392,8 @@ character .argument("", "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() diff --git a/packages/cli/src/utils/resolve-database-path.ts b/packages/cli/src/utils/resolve-database-path.ts new file mode 100644 index 00000000000..4831b22f876 --- /dev/null +++ b/packages/cli/src/utils/resolve-database-path.ts @@ -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 { + 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 { + 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); + } + + // 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; +} \ No newline at end of file