This repository has been archived on 2026-09-03. You can view files and clone it, but you cannot make any changes to it's state, such as pushing and creating new issues, pull requests or comments.
open-brainy/src/cli/commands/core.ts
David Snelling 8393d01209 feat(vfs): fix VFS visibility by removing broken filtering
- Remove includeVFS parameter and broken isVFS filtering logic
- Add excludeVFS parameter for optional VFS entity filtering
- VFS entities now part of knowledge graph by default
- Enable O(1) graph adjacency optimizations for VFS operations
- Update all VFS projections and PathResolver
- Add comprehensive VFS visibility documentation

This fixes the bug where VFS operations returned empty results due to
operator object mismatch in storage adapters. VFS relationships now use
proper graph traversal without metadata filtering.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-27 10:44:06 -07:00

934 lines
No EOL
28 KiB
TypeScript

/**
* Core CLI Commands - TypeScript Implementation
*
* Essential database operations: add, search, get, relate, import, export
*/
import chalk from 'chalk'
import ora from 'ora'
import inquirer from 'inquirer'
import { readFileSync, writeFileSync } from 'node:fs'
import { Brainy } from '../../brainy.js'
import { BrainyTypes, NounType, VerbType } from '../../index.js'
interface CoreOptions {
verbose?: boolean
json?: boolean
pretty?: boolean
}
interface AddOptions extends CoreOptions {
id?: string
metadata?: string
type?: string
confidence?: string
weight?: string
}
interface SearchOptions extends CoreOptions {
limit?: string
offset?: string
threshold?: string
type?: string
where?: string
near?: string
connectedTo?: string
connectedFrom?: string
via?: string
explain?: boolean
includeRelations?: boolean
includeVfs?: boolean
fusion?: string
vectorWeight?: string
graphWeight?: string
fieldWeight?: string
}
interface GetOptions extends CoreOptions {
withConnections?: boolean
}
interface RelateOptions extends CoreOptions {
weight?: string
metadata?: string
}
interface ExportOptions extends CoreOptions {
format?: 'json' | 'csv' | 'jsonl'
}
let brainyInstance: Brainy | null = null
const getBrainy = (): Brainy => {
if (!brainyInstance) {
brainyInstance = new Brainy()
}
return brainyInstance
}
const formatOutput = (data: any, options: CoreOptions): void => {
if (options.json) {
console.log(options.pretty ? JSON.stringify(data, null, 2) : JSON.stringify(data))
}
}
export const coreCommands = {
/**
* Add data to the neural database
*/
async add(text: string | undefined, options: AddOptions) {
let spinner: any = null
try {
// Interactive mode if no text provided
if (!text) {
const answers = await inquirer.prompt([
{
type: 'input',
name: 'content',
message: 'Enter content:',
validate: (input: string) => input.trim().length > 0 || 'Content cannot be empty'
},
{
type: 'input',
name: 'nounType',
message: 'Noun type (optional, press Enter to auto-detect):',
default: ''
},
{
type: 'input',
name: 'metadata',
message: 'Metadata (JSON, optional):',
default: '',
validate: (input: string) => {
if (!input.trim()) return true
try {
JSON.parse(input)
return true
} catch {
return 'Invalid JSON format'
}
}
}
])
text = answers.content
if (answers.nounType) {
options.type = answers.nounType
}
if (answers.metadata) {
options.metadata = answers.metadata
}
}
const spinner = ora('Adding to neural database...').start()
const brain = getBrainy()
let metadata: any = {}
if (options.metadata) {
try {
metadata = JSON.parse(options.metadata)
} catch {
spinner.fail('Invalid metadata JSON')
process.exit(1)
}
}
if (options.id) {
metadata.id = options.id
}
// Determine noun type
let nounType: NounType
if (options.type) {
// Validate provided type
if (!BrainyTypes.isValidNoun(options.type)) {
spinner.fail(`Invalid noun type: ${options.type}`)
console.log(chalk.dim('Run "brainy types --noun" to see valid types'))
process.exit(1)
}
nounType = options.type as NounType
} else {
// Use AI to suggest type
spinner.text = 'Detecting type with AI...'
const suggestion = await BrainyTypes.suggestNoun(
typeof text === 'string' ? { content: text, ...metadata } : text
)
if (suggestion.confidence < 0.6) {
spinner.fail('Could not determine type with confidence')
console.log(chalk.yellow(`Suggestion: ${suggestion.type} (${(suggestion.confidence * 100).toFixed(1)}%)`))
console.log(chalk.dim('Use --type flag to specify explicitly'))
process.exit(1)
}
nounType = suggestion.type as NounType
spinner.text = `Using detected type: ${nounType}`
}
// Add with explicit type
const addParams: any = {
data: text,
type: nounType,
metadata
}
// v4.3.x: Add confidence and weight if provided
if (options.confidence) {
addParams.confidence = parseFloat(options.confidence)
}
if (options.weight) {
addParams.weight = parseFloat(options.weight)
}
const result = await brain.add(addParams)
spinner.succeed('Added successfully')
if (!options.json) {
console.log(chalk.green(`✓ Added with ID: ${result}`))
if (options.type) {
console.log(chalk.dim(` Type: ${options.type}`))
}
if (options.confidence) {
console.log(chalk.dim(` Confidence: ${options.confidence}`))
}
if (options.weight) {
console.log(chalk.dim(` Weight: ${options.weight}`))
}
if (Object.keys(metadata).length > 0) {
console.log(chalk.dim(` Metadata: ${JSON.stringify(metadata)}`))
}
} else {
formatOutput({ id: result, metadata, confidence: addParams.confidence, weight: addParams.weight }, options)
}
} catch (error: any) {
if (spinner) spinner.fail('Failed to add data')
console.error(chalk.red('Failed to add data:', error.message))
process.exit(1)
}
},
/**
* Search the neural database with Triple Intelligence™
*/
async search(query: string | undefined, options: SearchOptions) {
let spinner: any = null
try {
// Interactive mode if no query provided
if (!query) {
const answers = await inquirer.prompt([
{
type: 'input',
name: 'query',
message: 'What are you looking for?',
validate: (input: string) => input.trim().length > 0 || 'Query cannot be empty'
},
{
type: 'number',
name: 'limit',
message: 'Number of results:',
default: 10
},
{
type: 'confirm',
name: 'useAdvanced',
message: 'Use advanced filters?',
default: false
}
])
query = answers.query
if (!options.limit) {
options.limit = answers.limit.toString()
}
// Advanced filters
if (answers.useAdvanced) {
const advancedAnswers = await inquirer.prompt([
{
type: 'input',
name: 'type',
message: 'Filter by type (optional):',
default: ''
},
{
type: 'input',
name: 'threshold',
message: 'Similarity threshold (0-1, default 0.7):',
default: '0.7',
validate: (input: string) => {
const num = parseFloat(input)
return (num >= 0 && num <= 1) || 'Must be between 0 and 1'
}
},
{
type: 'confirm',
name: 'explain',
message: 'Show scoring breakdown?',
default: false
}
])
if (advancedAnswers.type) options.type = advancedAnswers.type
if (advancedAnswers.threshold) options.threshold = advancedAnswers.threshold
options.explain = advancedAnswers.explain
}
}
const spinner = ora('Searching with Triple Intelligence™...').start()
const brain = getBrainy()
// Build comprehensive search params
const searchParams: any = {
query,
limit: options.limit ? parseInt(options.limit) : 10
}
// Pagination
if (options.offset) {
searchParams.offset = parseInt(options.offset)
}
// Vector Intelligence - similarity threshold
if (options.threshold) {
searchParams.near = { threshold: parseFloat(options.threshold) }
}
// Metadata Intelligence - type filtering
if (options.type) {
const types = options.type.split(',').map(t => t.trim())
searchParams.type = types.length === 1 ? types[0] : types
}
// Metadata Intelligence - field filtering
if (options.where) {
try {
searchParams.where = JSON.parse(options.where)
} catch {
spinner.fail('Invalid --where JSON')
console.log(chalk.dim('Example: --where \'{"status":"active","priority":{"$gte":5}}\''))
process.exit(1)
}
}
// Vector Intelligence - proximity search
if (options.near) {
searchParams.near = {
id: options.near,
threshold: options.threshold ? parseFloat(options.threshold) : 0.7
}
}
// Graph Intelligence - connection constraints
if (options.connectedTo || options.connectedFrom || options.via) {
searchParams.connected = {}
if (options.connectedTo) {
searchParams.connected.to = options.connectedTo
}
if (options.connectedFrom) {
searchParams.connected.from = options.connectedFrom
}
if (options.via) {
const vias = options.via.split(',').map(v => v.trim())
searchParams.connected.via = vias.length === 1 ? vias[0] : vias
}
}
// Explanation
if (options.explain) {
searchParams.explain = true
}
// Include relationships
if (options.includeRelations) {
searchParams.includeRelations = true
}
// v4.7.0: VFS is now part of the knowledge graph (included by default)
// Users can exclude VFS with --where vfsType exists:false if needed
// Triple Intelligence Fusion - custom weighting
if (options.fusion || options.vectorWeight || options.graphWeight || options.fieldWeight) {
searchParams.fusion = {
strategy: options.fusion || 'adaptive',
weights: {}
}
if (options.vectorWeight) {
searchParams.fusion.weights.vector = parseFloat(options.vectorWeight)
}
if (options.graphWeight) {
searchParams.fusion.weights.graph = parseFloat(options.graphWeight)
}
if (options.fieldWeight) {
searchParams.fusion.weights.field = parseFloat(options.fieldWeight)
}
}
const results = await brain.find(searchParams)
spinner.succeed(`Found ${results.length} results`)
if (!options.json) {
if (results.length === 0) {
console.log(chalk.yellow('\nNo results found'))
// Show helpful hints
console.log(chalk.dim('\nTips:'))
console.log(chalk.dim(' • Try different search terms'))
console.log(chalk.dim(' • Remove filters (--type, --where, --connected-to)'))
console.log(chalk.dim(' • Lower the --threshold value'))
} else {
console.log(chalk.cyan(`\n📊 Triple Intelligence Results:\n`))
results.forEach((result, i) => {
const entity = result.entity || result
console.log(chalk.bold(`${i + 1}. ${entity.id}`))
// Show score with breakdown
if (result.score !== undefined) {
console.log(chalk.green(` Score: ${(result.score * 100).toFixed(1)}%`))
if (options.explain && (result as any).scores) {
const scores = (result as any).scores
if (scores.vector !== undefined) {
console.log(chalk.dim(` Vector: ${(scores.vector * 100).toFixed(1)}%`))
}
if (scores.graph !== undefined) {
console.log(chalk.dim(` Graph: ${(scores.graph * 100).toFixed(1)}%`))
}
if (scores.field !== undefined) {
console.log(chalk.dim(` Field: ${(scores.field * 100).toFixed(1)}%`))
}
}
}
// Show type
if ((entity as any).type) {
console.log(chalk.dim(` Type: ${(entity as any).type}`))
}
// Show content preview
if ((entity as any).content) {
const preview = (entity as any).content.substring(0, 80)
console.log(chalk.dim(` Content: ${preview}${(entity as any).content.length > 80 ? '...' : ''}`))
}
// Show metadata
if ((entity as any).metadata && Object.keys((entity as any).metadata).length > 0) {
console.log(chalk.dim(` Metadata: ${JSON.stringify((entity as any).metadata)}`))
}
// Show relationships
if (options.includeRelations && (result as any).relations) {
const relations = (result as any).relations
if (relations.length > 0) {
console.log(chalk.dim(` Relations: ${relations.length} connections`))
}
}
console.log()
})
// Show search summary
console.log(chalk.cyan('Search Configuration:'))
if (searchParams.type) {
console.log(chalk.dim(` Type filter: ${Array.isArray(searchParams.type) ? searchParams.type.join(', ') : searchParams.type}`))
}
if (searchParams.where) {
console.log(chalk.dim(` Field filter: ${JSON.stringify(searchParams.where)}`))
}
if (searchParams.connected) {
console.log(chalk.dim(` Graph filter: ${JSON.stringify(searchParams.connected)}`))
}
if (searchParams.fusion) {
console.log(chalk.dim(` Fusion: ${searchParams.fusion.strategy}`))
if (searchParams.fusion.weights && Object.keys(searchParams.fusion.weights).length > 0) {
console.log(chalk.dim(` Weights: ${JSON.stringify(searchParams.fusion.weights)}`))
}
}
}
} else {
formatOutput(results, options)
}
} catch (error: any) {
if (spinner) spinner.fail('Search failed')
console.error(chalk.red('Search failed:', error.message))
if (options.verbose) {
console.error(chalk.dim(error.stack))
}
process.exit(1)
}
},
/**
* Get item by ID
*/
async get(id: string | undefined, options: GetOptions) {
let spinner: any = null
try {
// Interactive mode if no ID provided
if (!id) {
const answers = await inquirer.prompt([
{
type: 'input',
name: 'id',
message: 'Enter item ID:',
validate: (input: string) => input.trim().length > 0 || 'ID cannot be empty'
},
{
type: 'confirm',
name: 'withConnections',
message: 'Include connections?',
default: false
}
])
id = answers.id
options.withConnections = answers.withConnections
}
const spinner = ora('Fetching item...').start()
const brain = getBrainy()
// Try to get the item
const item = await brain.get(id)
if (!item) {
spinner.fail('Item not found')
console.log(chalk.yellow(`No item found with ID: ${id}`))
process.exit(1)
}
spinner.succeed('Item found')
if (!options.json) {
console.log(chalk.cyan('\nItem Details:'))
console.log(` ID: ${item.id}`)
console.log(` Content: ${(item as any).content || 'N/A'}`)
if (item.metadata) {
console.log(` Metadata: ${JSON.stringify(item.metadata, null, 2)}`)
}
if (options.withConnections) {
// Get verbs/relationships
// Get connections if method exists
const connections = (brain as any).getConnections ? await (brain as any).getConnections(id) : []
if (connections && connections.length > 0) {
console.log(chalk.cyan('\nConnections:'))
connections.forEach((conn: any) => {
console.log(` ${conn.source} --[${conn.type}]--> ${conn.target}`)
})
}
}
} else {
formatOutput(item, options)
}
} catch (error: any) {
if (spinner) spinner.fail('Failed to get item')
console.error(chalk.red('Failed to get item:', error.message))
process.exit(1)
}
},
/**
* Create relationship between items
*/
async relate(source: string | undefined, verb: string | undefined, target: string | undefined, options: RelateOptions) {
let spinner: any = null
try {
// Interactive mode if parameters missing
if (!source || !verb || !target) {
const answers = await inquirer.prompt([
{
type: 'input',
name: 'source',
message: 'Source entity ID:',
default: source || '',
validate: (input: string) => input.trim().length > 0 || 'Source ID cannot be empty'
},
{
type: 'input',
name: 'verb',
message: 'Relationship type (verb):',
default: verb || '',
validate: (input: string) => input.trim().length > 0 || 'Verb cannot be empty'
},
{
type: 'input',
name: 'target',
message: 'Target entity ID:',
default: target || '',
validate: (input: string) => input.trim().length > 0 || 'Target ID cannot be empty'
},
{
type: 'input',
name: 'weight',
message: 'Relationship weight (0-1, optional):',
default: '',
validate: (input: string) => {
if (!input.trim()) return true
const num = parseFloat(input)
return (num >= 0 && num <= 1) || 'Must be between 0 and 1'
}
}
])
source = answers.source
verb = answers.verb
target = answers.target
if (answers.weight) {
options.weight = answers.weight
}
}
const spinner = ora('Creating relationship...').start()
const brain = getBrainy()
let metadata: any = {}
if (options.metadata) {
try {
metadata = JSON.parse(options.metadata)
} catch {
spinner.fail('Invalid metadata JSON')
process.exit(1)
}
}
if (options.weight) {
metadata.weight = parseFloat(options.weight)
}
// Create the relationship
const result = await brain.relate({
from: source,
to: target,
type: verb as any,
metadata
})
spinner.succeed('Relationship created')
if (!options.json) {
console.log(chalk.green(`✓ Created relationship with ID: ${result}`))
console.log(chalk.dim(` ${source} --[${verb}]--> ${target}`))
if (metadata.weight) {
console.log(chalk.dim(` Weight: ${metadata.weight}`))
}
} else {
formatOutput({ id: result, source, verb, target, metadata }, options)
}
} catch (error: any) {
if (spinner) spinner.fail('Failed to create relationship')
console.error(chalk.red('Failed to create relationship:', error.message))
process.exit(1)
}
},
/**
* Update an existing entity
*/
async update(id: string | undefined, options: AddOptions & { content?: string }) {
let spinner: any = null
try {
// Interactive mode if no ID provided
if (!id) {
const answers = await inquirer.prompt([
{
type: 'input',
name: 'id',
message: 'Entity ID to update:',
validate: (input: string) => input.trim().length > 0 || 'ID cannot be empty'
},
{
type: 'input',
name: 'content',
message: 'New content (optional, press Enter to skip):',
default: ''
},
{
type: 'input',
name: 'metadata',
message: 'Metadata to merge (JSON, optional):',
default: '',
validate: (input: string) => {
if (!input.trim()) return true
try {
JSON.parse(input)
return true
} catch {
return 'Invalid JSON format'
}
}
}
])
id = answers.id
if (answers.content) {
options.content = answers.content
}
if (answers.metadata) {
options.metadata = answers.metadata
}
}
spinner = ora('Updating entity...').start()
const brain = getBrainy()
// Get existing entity first
const existing = await brain.get(id)
if (!existing) {
spinner.fail('Entity not found')
console.log(chalk.yellow(`No entity found with ID: ${id}`))
process.exit(1)
}
// Build update params
const updateParams: any = { id }
if (options.content) {
updateParams.data = options.content
}
if (options.metadata) {
try {
const newMetadata = JSON.parse(options.metadata)
updateParams.metadata = {
...existing.metadata,
...newMetadata
}
} catch {
spinner.fail('Invalid metadata JSON')
process.exit(1)
}
}
if (options.type) {
updateParams.type = options.type
}
await brain.update(updateParams)
spinner.succeed('Entity updated successfully')
if (!options.json) {
console.log(chalk.green(`✓ Updated entity: ${id}`))
if (options.content) {
console.log(chalk.dim(` New content: ${options.content.substring(0, 80)}...`))
}
if (updateParams.metadata) {
console.log(chalk.dim(` Metadata: ${JSON.stringify(updateParams.metadata)}`))
}
} else {
formatOutput({ id, updated: true }, options)
}
} catch (error: any) {
if (spinner) spinner.fail('Failed to update entity')
console.error(chalk.red('Update failed:', error.message))
process.exit(1)
}
},
/**
* Delete an entity
*/
async deleteEntity(id: string | undefined, options: CoreOptions & { force?: boolean }) {
let spinner: any = null
try {
// Interactive mode if no ID provided
if (!id) {
const answers = await inquirer.prompt([
{
type: 'input',
name: 'id',
message: 'Entity ID to delete:',
validate: (input: string) => input.trim().length > 0 || 'ID cannot be empty'
},
{
type: 'confirm',
name: 'confirm',
message: 'Are you sure? This cannot be undone.',
default: false
}
])
if (!answers.confirm) {
console.log(chalk.yellow('Delete cancelled'))
return
}
id = answers.id
} else if (!options.force) {
// Confirmation for non-interactive mode
const answer = await inquirer.prompt([{
type: 'confirm',
name: 'confirm',
message: `Delete entity ${id}? This cannot be undone.`,
default: false
}])
if (!answer.confirm) {
console.log(chalk.yellow('Delete cancelled'))
return
}
}
spinner = ora('Deleting entity...').start()
const brain = getBrainy()
await brain.delete(id)
spinner.succeed('Entity deleted successfully')
if (!options.json) {
console.log(chalk.green(`✓ Deleted entity: ${id}`))
} else {
formatOutput({ id, deleted: true }, options)
}
} catch (error: any) {
if (spinner) spinner.fail('Failed to delete entity')
console.error(chalk.red('Delete failed:', error.message))
process.exit(1)
}
},
/**
* Remove a relationship
*/
async unrelate(id: string | undefined, options: CoreOptions & { force?: boolean }) {
let spinner: any = null
try {
// Interactive mode if no ID provided
if (!id) {
const answers = await inquirer.prompt([
{
type: 'input',
name: 'id',
message: 'Relationship ID to remove:',
validate: (input: string) => input.trim().length > 0 || 'ID cannot be empty'
},
{
type: 'confirm',
name: 'confirm',
message: 'Remove this relationship?',
default: false
}
])
if (!answers.confirm) {
console.log(chalk.yellow('Operation cancelled'))
return
}
id = answers.id
} else if (!options.force) {
const answer = await inquirer.prompt([{
type: 'confirm',
name: 'confirm',
message: `Remove relationship ${id}?`,
default: false
}])
if (!answer.confirm) {
console.log(chalk.yellow('Operation cancelled'))
return
}
}
spinner = ora('Removing relationship...').start()
const brain = getBrainy()
await brain.unrelate(id)
spinner.succeed('Relationship removed successfully')
if (!options.json) {
console.log(chalk.green(`✓ Removed relationship: ${id}`))
} else {
formatOutput({ id, removed: true }, options)
}
} catch (error: any) {
if (spinner) spinner.fail('Failed to remove relationship')
console.error(chalk.red('Unrelate failed:', error.message))
process.exit(1)
}
},
/**
* Export database
*/
async export(file: string | undefined, options: ExportOptions) {
const spinner = ora('Exporting database...').start()
try {
const brain = getBrainy()
const format = options.format || 'json'
// Export all data
const dataApi = await brain.data()
const data = await dataApi.export({ format: 'json' })
let output = ''
switch (format) {
case 'json':
output = options.pretty
? JSON.stringify(data, null, 2)
: JSON.stringify(data)
break
case 'jsonl':
if (Array.isArray(data)) {
output = data.map(item => JSON.stringify(item)).join('\n')
} else {
output = JSON.stringify(data)
}
break
case 'csv':
if (Array.isArray(data) && data.length > 0) {
// Get all unique keys for headers
const headers = new Set<string>()
data.forEach(item => {
Object.keys(item).forEach(key => headers.add(key))
})
const headerArray = Array.from(headers)
// Create CSV
output = headerArray.join(',') + '\n'
output += data.map(item => {
return headerArray.map(h => {
const value = item[h]
if (typeof value === 'object') {
return JSON.stringify(value)
}
return value || ''
}).join(',')
}).join('\n')
}
break
}
if (file) {
writeFileSync(file, output)
spinner.succeed(`Exported to ${file}`)
if (!options.json) {
console.log(chalk.green(`✓ Successfully exported database to ${file}`))
console.log(chalk.dim(` Format: ${format}`))
console.log(chalk.dim(` Items: ${Array.isArray(data) ? data.length : 1}`))
} else {
formatOutput({ file, format, count: Array.isArray(data) ? data.length : 1 }, options)
}
} else {
spinner.succeed('Export complete')
console.log(output)
}
} catch (error: any) {
spinner.fail('Export failed')
console.error(chalk.red(error.message))
process.exit(1)
}
}
}