Feat: enhance cli.ts with updated typings and improved search interface

- Introduced `SearchResult` interface to standardize search result handling and improve code readability.
- Added TypeScript type annotations for command arguments and options in CLI methods to enforce stricter type safety.
- Updated `.version()` for CLI to align with best practices by removing redundant description parameter.
- Enhanced search logic with default parameter handling for limits and improved output formatting for results.
- Adjusted `tsconfig.json` to disable `noImplicitAny` while retaining strict typing practices.
- Streamlined CLI processes by parsing command-line arguments explicitly with `program.parse(process.argv)`.

Purpose: Improve developer productivity with better type safety, clearer interfaces, and refined CLI functionality.
This commit is contained in:
David Snelling 2025-07-18 12:00:50 -07:00
parent 3fe2a6e7f0
commit e9aaec89d8
2 changed files with 33 additions and 24 deletions

View file

@ -27,12 +27,25 @@ import { fileURLToPath } from 'url'
import { dirname, join } from 'path' import { dirname, join } from 'path'
import fs from 'fs' import fs from 'fs'
import { Command } from 'commander' import { Command } from 'commander'
// @ts-expect-error - Missing type declarations for omelette
import omelette from 'omelette' import omelette from 'omelette'
// Get the directory of the current module // Get the directory of the current module
const __filename = fileURLToPath(import.meta.url) const __filename = fileURLToPath(import.meta.url)
const __dirname = dirname(__filename) const __dirname = dirname(__filename)
// Define interface for search results
interface SearchResult {
id: string;
metadata?: {
noun?: string;
label?: string;
[key: string]: any;
};
vector: number[];
score?: number;
}
// Get version from package.json // Get version from package.json
const packageJsonPath = join(__dirname, '..', 'package.json') const packageJsonPath = join(__dirname, '..', 'package.json')
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8')) const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'))
@ -93,7 +106,7 @@ program
.description( .description(
'A vector database using HNSW indexing with Origin Private File System storage' 'A vector database using HNSW indexing with Origin Private File System storage'
) )
.version(VERSION, '-V, --version', 'Output the current version') .version(VERSION, '-V, --version')
// Create data directory if it doesn't exist // Create data directory if it doesn't exist
const dataDir = join(process.cwd(), 'data') const dataDir = join(process.cwd(), 'data')
@ -128,7 +141,7 @@ program
.description('Add a new noun with the given text and optional metadata') .description('Add a new noun with the given text and optional metadata')
.argument('<text>', 'Text to add as a noun') .argument('<text>', 'Text to add as a noun')
.argument('[metadata]', 'Optional metadata as JSON string') .argument('[metadata]', 'Optional metadata as JSON string')
.action(async (text, metadataStr) => { .action(async (text: string, metadataStr: string) => {
try { try {
const db = createDb() const db = createDb()
await db.init() await db.init()
@ -153,27 +166,22 @@ program
.description('Search for nouns similar to the query') .description('Search for nouns similar to the query')
.argument('<query>', 'Search query text') .argument('<query>', 'Search query text')
.option('-l, --limit <number>', 'Maximum number of results to return', '5') .option('-l, --limit <number>', 'Maximum number of results to return', '5')
.action(async (query, options) => { .action(async (query: string, options: { limit?: string, type?: string }) => {
try { try {
const db = createDb() const db = createDb()
await db.init() await db.init()
const limit = parseInt(options.limit, 10) const limit = parseInt(options.limit || '5', 10)
const results = await db.searchText(query, limit) const results: SearchResult[] = await db.searchText(query, limit)
console.log(`Search results for "${query}":`) console.log(`Search results for "${query}":`)
results.forEach( results.forEach(
( (
result: { result: SearchResult,
id: string
score: number
metadata: any
vector: number[]
},
index: number index: number
) => { ) => {
console.log(`${index + 1}. ID: ${result.id}`) console.log(`${index + 1}. ID: ${result.id}`)
console.log(` Score: ${result.score.toFixed(4)}`) console.log(` Score: ${result.score ? result.score.toFixed(4) : 'N/A'}`)
console.log(` Metadata: ${JSON.stringify(result.metadata)}`) console.log(` Metadata: ${JSON.stringify(result.metadata)}`)
console.log( console.log(
` Vector: [${result.vector ` Vector: [${result.vector
@ -194,7 +202,7 @@ program
.command('get') .command('get')
.description('Get a noun by ID') .description('Get a noun by ID')
.argument('<id>', 'ID of the noun to get') .argument('<id>', 'ID of the noun to get')
.action(async (id) => { .action(async (id: string) => {
try { try {
const db = createDb() const db = createDb()
await db.init() await db.init()
@ -222,7 +230,7 @@ program
.command('delete') .command('delete')
.description('Delete a noun by ID') .description('Delete a noun by ID')
.argument('<id>', 'ID of the noun to delete') .argument('<id>', 'ID of the noun to delete')
.action(async (id) => { .action(async (id: string) => {
try { try {
const db = createDb() const db = createDb()
await db.init() await db.init()
@ -242,7 +250,7 @@ program
.argument('<targetId>', 'ID of the target noun') .argument('<targetId>', 'ID of the target noun')
.argument('<verbType>', 'Type of relationship') .argument('<verbType>', 'Type of relationship')
.argument('[metadata]', 'Optional metadata as JSON string') .argument('[metadata]', 'Optional metadata as JSON string')
.action(async (sourceId, targetId, verbTypeStr, metadataStr) => { .action(async (sourceId: string, targetId: string, verbTypeStr: string, metadataStr: string) => {
try { try {
const db = createDb() const db = createDb()
await db.init() await db.init()
@ -268,7 +276,7 @@ program
.command('getVerbs') .command('getVerbs')
.description('Get all relationships for a noun') .description('Get all relationships for a noun')
.argument('<id>', 'ID of the noun to get relationships for') .argument('<id>', 'ID of the noun to get relationships for')
.action(async (id) => { .action(async (id: string) => {
try { try {
const db = createDb() const db = createDb()
await db.init() await db.init()
@ -367,7 +375,7 @@ program
.command('backup') .command('backup')
.description('Backup all data from the database to a JSON file') .description('Backup all data from the database to a JSON file')
.argument('[filename]', 'Output filename (default: brainy-backup.json)') .argument('[filename]', 'Output filename (default: brainy-backup.json)')
.action(async (filename) => { .action(async (filename: string) => {
try { try {
const db = createDb() const db = createDb()
await db.init() await db.init()
@ -396,7 +404,7 @@ program
.description('Restore data from a JSON file into the database') .description('Restore data from a JSON file into the database')
.argument('<filename>', 'Input JSON file') .argument('<filename>', 'Input JSON file')
.option('-c, --clear', 'Clear existing data before restoring', false) .option('-c, --clear', 'Clear existing data before restoring', false)
.action(async (filename, options) => { .action(async (filename: string, options: { clear?: boolean }) => {
try { try {
const db = createDb() const db = createDb()
await db.init() await db.init()
@ -430,7 +438,7 @@ program
) )
.argument('<filename>', 'Input JSON file') .argument('<filename>', 'Input JSON file')
.option('-c, --clear', 'Clear existing data before importing', false) .option('-c, --clear', 'Clear existing data before importing', false)
.action(async (filename, options) => { .action(async (filename: string, options: { clear?: boolean }) => {
try { try {
const db = createDb() const db = createDb()
await db.init() await db.init()
@ -488,11 +496,11 @@ program
// Get all nouns if no root is specified // Get all nouns if no root is specified
if (!rootId && !nounType) { if (!rootId && !nounType) {
// Get all nouns (limited by the limit option) // Get all nouns (limited by the limit option)
const allNouns = [] const allNouns: SearchResult[] = []
let count = 0 let count = 0
// Since there's no direct method to get all nouns, we'll use search with a high limit // Since there's no direct method to get all nouns, we'll use search with a high limit
const searchResults = await db.search('', 1000, { const searchResults: SearchResult[] = await db.search('', 1000, {
forceEmbed: true forceEmbed: true
}) })
@ -561,12 +569,12 @@ program
console.log(`Visualizing nouns of type: ${nounType}\n`) console.log(`Visualizing nouns of type: ${nounType}\n`)
// Search for nouns of the specified type // Search for nouns of the specified type
const searchResults = await db.search('', 1000, { const searchResults: SearchResult[] = await db.search('', 1000, {
nounTypes: [nounType], nounTypes: [nounType],
forceEmbed: true forceEmbed: true
}) })
const filteredNouns = searchResults.slice(0, limit) const filteredNouns: SearchResult[] = searchResults.slice(0, limit)
if (filteredNouns.length === 0) { if (filteredNouns.length === 0) {
console.log(`No nouns found with type: ${nounType}`) console.log(`No nouns found with type: ${nounType}`)
@ -1470,4 +1478,4 @@ program
}) })
// Parse command line arguments // Parse command line arguments
program.parse() program.parse(process.argv)

View file

@ -5,6 +5,7 @@
"moduleResolution": "node", "moduleResolution": "node",
"esModuleInterop": true, "esModuleInterop": true,
"strict": true, "strict": true,
"noImplicitAny": false,
"outDir": "dist", "outDir": "dist",
"declaration": true, "declaration": true,
"sourceMap": true, "sourceMap": true,