docs: update README.md and suppress TensorFlow.js warnings

Enhanced the installation section with guidance on resolving TensorFlow.js dependency conflicts using `--legacy-peer-deps`. Added CLI autocomplete setup instructions to improve user experience. Suppressed specific TensorFlow.js Node.js backend warnings in the embedding utils. Updated dependencies to the latest versions.
This commit is contained in:
David Snelling 2025-06-05 11:44:18 -07:00
parent bdb0b98b74
commit be67c1b753
5 changed files with 345 additions and 193 deletions

View file

@ -5,86 +5,87 @@
* A command-line interface for interacting with the Brainy vector database
*/
import { BrainyData, NounType, VerbType, FileSystemStorage } from './index.js';
import { fileURLToPath } from 'url';
import { dirname, join } from 'path';
import fs from 'fs';
import { Command } from 'commander';
import { BrainyData, NounType, VerbType, FileSystemStorage } from './index.js'
import { fileURLToPath } from 'url'
import { dirname, join } from 'path'
import fs from 'fs'
import { Command } from 'commander'
import omelette from 'omelette'
// Get the directory of the current module
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
const __filename = fileURLToPath(import.meta.url)
const __dirname = dirname(__filename)
// Import package.json for version info
const packageJson = {
name: '@soulcraft/brainy',
version: '0.6.0',
description: 'A vector database using HNSW indexing with Origin Private File System storage'
};
}
// Helper function to parse JSON safely
function parseJSON(str: string): any {
try {
return JSON.parse(str);
return JSON.parse(str)
} catch (e) {
console.error('Error parsing JSON:', (e as Error).message);
return {};
console.error('Error parsing JSON:', (e as Error).message)
return {}
}
}
// Helper function to resolve noun type
function resolveNounType(type: string | number | undefined): NounType {
if (!type) return NounType.Thing;
if (!type) return NounType.Thing
// If it's a string, try to match it to a NounType
if (typeof type === 'string') {
const nounTypeKey = Object.keys(NounType).find(
key => key.toLowerCase() === type.toLowerCase()
);
return nounTypeKey ? NounType[nounTypeKey as keyof typeof NounType] : NounType.Thing;
)
return nounTypeKey ? NounType[nounTypeKey as keyof typeof NounType] : NounType.Thing
}
// Convert number to string type for safety
return Object.values(NounType)[type as number] || NounType.Thing;
return Object.values(NounType)[type as number] || NounType.Thing
}
// Helper function to resolve verb type
function resolveVerbType(type: string | number | undefined): VerbType {
if (!type) return VerbType.RelatedTo;
if (!type) return VerbType.RelatedTo
// If it's a string, try to match it to a VerbType
if (typeof type === 'string') {
const verbTypeKey = Object.keys(VerbType).find(
key => key.toLowerCase() === type.toLowerCase()
);
return verbTypeKey ? VerbType[verbTypeKey as keyof typeof VerbType] : VerbType.RelatedTo;
)
return verbTypeKey ? VerbType[verbTypeKey as keyof typeof VerbType] : VerbType.RelatedTo
}
// Convert number to string type for safety
return Object.values(VerbType)[type as number] || VerbType.RelatedTo;
return Object.values(VerbType)[type as number] || VerbType.RelatedTo
}
// Create a new Command instance
const program = new Command();
const program = new Command()
// Configure the program
program
.name(packageJson.name)
.description(packageJson.description)
.version(packageJson.version);
.version(packageJson.version)
// Create data directory if it doesn't exist
const dataDir = join(__dirname, '..', 'data');
const dataDir = join(__dirname, '..', 'data')
if (!fs.existsSync(dataDir)) {
fs.mkdirSync(dataDir, { recursive: true });
fs.mkdirSync(dataDir, { recursive: true })
}
// Create a database instance with file system storage
const createDb = () => {
return new BrainyData({
storageAdapter: new FileSystemStorage(dataDir)
});
};
})
}
// Define commands
program
@ -92,14 +93,14 @@ program
.description('Initialize a new database')
.action(async () => {
try {
const db = createDb();
await db.init();
console.log('Database initialized successfully');
const db = createDb()
await db.init()
console.log('Database initialized successfully')
} catch (error) {
console.error('Error:', (error as Error).message);
process.exit(1);
console.error('Error:', (error as Error).message)
process.exit(1)
}
});
})
program
.command('add')
@ -108,23 +109,23 @@ program
.argument('[metadata]', 'Optional metadata as JSON string')
.action(async (text, metadataStr) => {
try {
const db = createDb();
await db.init();
const db = createDb()
await db.init()
const metadata = metadataStr ? parseJSON(metadataStr) : {};
const metadata = metadataStr ? parseJSON(metadataStr) : {}
// Process metadata to handle noun type
if (metadata.noun) {
metadata.noun = resolveNounType(metadata.noun);
metadata.noun = resolveNounType(metadata.noun)
}
const id = await db.add(text, metadata);
console.log(`Added noun with ID: ${id}`);
const id = await db.add(text, metadata)
console.log(`Added noun with ID: ${id}`)
} catch (error) {
console.error('Error:', (error as Error).message);
process.exit(1);
console.error('Error:', (error as Error).message)
process.exit(1)
}
});
})
program
.command('search')
@ -133,25 +134,25 @@ program
.option('-l, --limit <number>', 'Maximum number of results to return', '5')
.action(async (query, options) => {
try {
const db = createDb();
await db.init();
const db = createDb()
await db.init()
const limit = parseInt(options.limit, 10);
const results = await db.searchText(query, limit);
const limit = parseInt(options.limit, 10)
const results = await db.searchText(query, limit)
console.log(`Search results for "${query}":`);
console.log(`Search results for "${query}":`)
results.forEach((result, index) => {
console.log(`${index + 1}. ID: ${result.id}`);
console.log(` Score: ${result.score.toFixed(4)}`);
console.log(` Metadata: ${JSON.stringify(result.metadata)}`);
console.log(` Vector: [${result.vector.slice(0, 3).map(v => v.toFixed(2)).join(', ')}...]`);
console.log();
});
console.log(`${index + 1}. ID: ${result.id}`)
console.log(` Score: ${result.score.toFixed(4)}`)
console.log(` Metadata: ${JSON.stringify(result.metadata)}`)
console.log(` Vector: [${result.vector.slice(0, 3).map(v => v.toFixed(2)).join(', ')}...]`)
console.log()
})
} catch (error) {
console.error('Error:', (error as Error).message);
process.exit(1);
console.error('Error:', (error as Error).message)
process.exit(1)
}
});
})
program
.command('get')
@ -159,22 +160,22 @@ program
.argument('<id>', 'ID of the noun to get')
.action(async (id) => {
try {
const db = createDb();
await db.init();
const db = createDb()
await db.init()
const noun = await db.get(id);
const noun = await db.get(id)
if (noun) {
console.log(`Noun ID: ${noun.id}`);
console.log(`Metadata: ${JSON.stringify(noun.metadata)}`);
console.log(`Vector: [${noun.vector.slice(0, 5).map(v => v.toFixed(2)).join(', ')}...]`);
console.log(`Noun ID: ${noun.id}`)
console.log(`Metadata: ${JSON.stringify(noun.metadata)}`)
console.log(`Vector: [${noun.vector.slice(0, 5).map(v => v.toFixed(2)).join(', ')}...]`)
} else {
console.log(`No noun found with ID: ${id}`);
console.log(`No noun found with ID: ${id}`)
}
} catch (error) {
console.error('Error:', (error as Error).message);
process.exit(1);
console.error('Error:', (error as Error).message)
process.exit(1)
}
});
})
program
.command('delete')
@ -182,16 +183,16 @@ program
.argument('<id>', 'ID of the noun to delete')
.action(async (id) => {
try {
const db = createDb();
await db.init();
const db = createDb()
await db.init()
await db.delete(id);
console.log(`Deleted noun with ID: ${id}`);
await db.delete(id)
console.log(`Deleted noun with ID: ${id}`)
} catch (error) {
console.error('Error:', (error as Error).message);
process.exit(1);
console.error('Error:', (error as Error).message)
process.exit(1)
}
});
})
program
.command('addVerb')
@ -202,22 +203,25 @@ program
.argument('[metadata]', 'Optional metadata as JSON string')
.action(async (sourceId, targetId, verbTypeStr, metadataStr) => {
try {
const db = createDb();
await db.init();
const db = createDb()
await db.init()
const verbType = resolveVerbType(verbTypeStr);
const verbMetadata = metadataStr ? parseJSON(metadataStr) : {};
const verbType = resolveVerbType(verbTypeStr)
const verbMetadata = metadataStr ? parseJSON(metadataStr) : {}
// Add verb type to metadata
verbMetadata.verb = verbType;
verbMetadata.verb = verbType
const verbId = await db.addVerb(sourceId, targetId, verbMetadata);
console.log(`Added verb with ID: ${verbId}`);
const verbId = await db.addVerb(sourceId, targetId, undefined, {
type: verbType,
metadata: verbMetadata
})
console.log(`Added verb with ID: ${verbId}`)
} catch (error) {
console.error('Error:', (error as Error).message);
process.exit(1);
console.error('Error:', (error as Error).message)
process.exit(1)
}
});
})
program
.command('getVerbs')
@ -225,55 +229,55 @@ program
.argument('<id>', 'ID of the noun to get relationships for')
.action(async (id) => {
try {
const db = createDb();
await db.init();
const db = createDb()
await db.init()
const verbs = await db.getVerbsBySource(id);
const verbs = await db.getVerbsBySource(id)
console.log(`Relationships for noun ${id}:`);
console.log(`Relationships for noun ${id}:`)
if (verbs.length === 0) {
console.log('No relationships found');
console.log('No relationships found')
} else {
verbs.forEach((verb, index) => {
console.log(`${index + 1}. ID: ${verb.id}`);
console.log(` Type: ${Object.keys(VerbType).find(key => VerbType[key as keyof typeof VerbType] === verb.metadata.verb) || verb.metadata.verb}`);
console.log(` Target: ${verb.targetId}`);
console.log(` Metadata: ${JSON.stringify(verb.metadata)}`);
console.log();
});
console.log(`${index + 1}. ID: ${verb.id}`)
console.log(` Type: ${Object.keys(VerbType).find(key => VerbType[key as keyof typeof VerbType] === verb.metadata.verb) || verb.metadata.verb}`)
console.log(` Target: ${verb.targetId}`)
console.log(` Metadata: ${JSON.stringify(verb.metadata)}`)
console.log()
})
}
} catch (error) {
console.error('Error:', (error as Error).message);
process.exit(1);
console.error('Error:', (error as Error).message)
process.exit(1)
}
});
})
program
.command('status')
.description('Show database status')
.action(async () => {
try {
const db = createDb();
await db.init();
const db = createDb()
await db.init()
const status = await db.status();
console.log('Database Status:');
console.log(`Storage type: ${status.type}`);
console.log(`Storage used: ${status.used} bytes`);
console.log(`Storage quota: ${status.quota !== null ? `${status.quota} bytes` : 'unlimited'}`);
const status = await db.status()
console.log('Database Status:')
console.log(`Storage type: ${status.type}`)
console.log(`Storage used: ${status.used} bytes`)
console.log(`Storage quota: ${status.quota !== null ? `${status.quota} bytes` : 'unlimited'}`)
// Display additional details if available
if (status.details) {
console.log('Additional details:');
console.log('Additional details:')
Object.entries(status.details).forEach(([key, value]) => {
console.log(` ${key}: ${JSON.stringify(value)}`);
});
console.log(` ${key}: ${JSON.stringify(value)}`)
})
}
} catch (error) {
console.error('Error:', (error as Error).message);
process.exit(1);
console.error('Error:', (error as Error).message)
process.exit(1)
}
});
})
// Add examples to help text
program.addHelpText('after', `
@ -282,7 +286,82 @@ Examples:
$ brainy add "Cats are independent pets" '{"noun":"Thing","category":"animal"}'
$ brainy search "feline pets" --limit 5
$ brainy addVerb id1 id2 RelatedTo '{"description":"Both are pets"}'
`);
`)
// Setup autocomplete
const completion = omelette('brainy')
// Helper function to get all noun types
const getNounTypes = () => Object.keys(NounType)
// Helper function to get all verb types
const getVerbTypes = () => Object.keys(VerbType)
// Define autocomplete handlers
completion.tree({
// First level commands - suggest all available commands
_: () => [
'add',
'addVerb',
'search',
'get',
'delete',
'getVerbs',
'status',
'completion-setup',
'init',
'help'
],
// Command-specific completions
add: {
// For the second argument of 'add' command (metadata)
_: () => {
// Generate templates for each noun type
return getNounTypes().map(type =>
`{"noun":"${type}","category":"example"}`
)
}
},
addVerb: {
// First two arguments are IDs, third is verb type
'<sourceId>': {
'<targetId>': {
_: () => {
// Suggest all available verb types
return getVerbTypes()
}
}
}
},
// Add autocomplete for other commands
search: {},
get: {},
delete: {},
getVerbs: {},
status: {},
'completion-setup': {},
init: {},
help: {}
})
// Initialize autocomplete
completion.init()
// If this script is run with --completion-setup flag, set up the autocomplete
if (process.argv.includes('--completion-setup')) {
completion.setupShellInitFile()
console.log('Autocomplete setup complete. Please restart your shell.')
process.exit(0)
}
// Add a command for setting up autocomplete
program
.command('completion-setup')
.description('Setup shell autocomplete for the Brainy CLI')
.action(() => {
completion.setupShellInitFile()
console.log('Autocomplete setup complete. Please restart your shell.')
})
// Parse command line arguments
program.parse();
program.parse()

View file

@ -89,6 +89,18 @@ export class UniversalSentenceEncoder implements EmbeddingModel {
*/
public async init(): Promise<void> {
try {
// Save original console.warn
const originalWarn = console.warn
// Override console.warn to suppress TensorFlow.js Node.js backend message
console.warn = function(message?: any, ...optionalParams: any[]) {
if (message && typeof message === 'string' &&
message.includes('Hi, looks like you are running TensorFlow.js in Node.js')) {
return // Suppress the specific warning
}
originalWarn(message, ...optionalParams)
}
// Dynamically import TensorFlow.js and Universal Sentence Encoder
// Use type assertions to tell TypeScript these modules exist
this.tf = await import('@tensorflow/tfjs')
@ -97,6 +109,9 @@ export class UniversalSentenceEncoder implements EmbeddingModel {
// Load the model
this.model = await this.use.load()
this.initialized = true
// Restore original console.warn
console.warn = originalWarn
} catch (error) {
console.error('Failed to initialize Universal Sentence Encoder:', error)
throw new Error(