feat(8.0): brain.fillSubtypes migration helper + pre-RC1 gap closure
- brain.fillSubtypes(rules): idempotent subtype back-fill for pre-8.0 data.
One rule per NounType/VerbType (literal default or per-entry function);
fills only entries still missing a subtype through the public update()/
updateRelation() paths; returns { scanned, filled, skipped, errors, byType }.
Full unit suite in tests/unit/brainy/fill-subtypes.test.ts.
- Fix getNouns/getVerbs pagination hasMore (peek one past the window) —
was permanently false, silently truncating every multi-page walk.
- find({ near }) without near.id now throws a teaching error instead of an
opaque storage sharding failure; CLI --threshold without --near applies a
plain score floor.
- CLI init/close audit: every one-shot command init()s, close()s, and exits
explicitly; delete the unmaintained interactive REPL; replace the cloud-era
storage subcommands with status/batch-delete; new types/validate commands.
- requireSubtype JSDoc now documents the 8.0 default-on contract; audit()
recommendation points at fillSubtypes.
- Docs: data-storage-architecture rewritten to the real 8.0 on-disk layout;
README storage section reflects filesystem+memory and snapshots; eli5 and
SEMANTIC_VFS /as-of/ semantics corrected; internal tracker IDs and
.strategy references scrubbed from published files.
This commit is contained in:
parent
9b0f4acd5b
commit
c44678390e
30 changed files with 1517 additions and 3226 deletions
|
|
@ -132,6 +132,7 @@ export const coreCommands = {
|
|||
|
||||
const spinner = ora('Adding to neural database...').start()
|
||||
const brain = getBrainy()
|
||||
await brain.init()
|
||||
|
||||
let metadata: any = {}
|
||||
if (options.metadata) {
|
||||
|
|
@ -204,6 +205,12 @@ export const coreCommands = {
|
|||
} else {
|
||||
formatOutput({ id: result, metadata, confidence: addParams.confidence, weight: addParams.weight }, options)
|
||||
}
|
||||
|
||||
// close() releases the writer lock and indexes, but global timers
|
||||
// (UnifiedCache bookkeeping, PathResolver stats) keep the event loop
|
||||
// alive. CLI commands are one-shot — exit explicitly.
|
||||
await brain.close()
|
||||
process.exit(0)
|
||||
} catch (error: any) {
|
||||
if (spinner) spinner.fail('Failed to add data')
|
||||
console.error(chalk.red('Failed to add data:', error.message))
|
||||
|
|
@ -280,6 +287,7 @@ export const coreCommands = {
|
|||
|
||||
const spinner = ora('Searching with Triple Intelligence™...').start()
|
||||
const brain = getBrainy()
|
||||
await brain.init()
|
||||
|
||||
// Build comprehensive search params
|
||||
const searchParams: any = {
|
||||
|
|
@ -292,11 +300,6 @@ export const coreCommands = {
|
|||
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())
|
||||
|
|
@ -314,7 +317,9 @@ export const coreCommands = {
|
|||
}
|
||||
}
|
||||
|
||||
// Vector Intelligence - proximity search
|
||||
// Vector Intelligence - proximity search around an anchor entity.
|
||||
// `near` requires an id; a bare --threshold (no --near) is applied as a
|
||||
// plain score floor on the fused results after find() returns.
|
||||
if (options.near) {
|
||||
searchParams.near = {
|
||||
id: options.near,
|
||||
|
|
@ -371,7 +376,14 @@ export const coreCommands = {
|
|||
}
|
||||
}
|
||||
|
||||
const results = await brain.find(searchParams)
|
||||
let results = await brain.find(searchParams)
|
||||
|
||||
// Without --near there is no proximity anchor; apply --threshold as a
|
||||
// minimum-score filter on the fused results instead.
|
||||
if (!options.near && options.threshold) {
|
||||
const minScore = parseFloat(options.threshold)
|
||||
results = results.filter((r) => r.score === undefined || r.score >= minScore)
|
||||
}
|
||||
|
||||
spinner.succeed(`Found ${results.length} results`)
|
||||
|
||||
|
|
@ -457,6 +469,10 @@ export const coreCommands = {
|
|||
} else {
|
||||
formatOutput(results, options)
|
||||
}
|
||||
|
||||
// One-shot command — see add() for why the explicit close + exit.
|
||||
await brain.close()
|
||||
process.exit(0)
|
||||
} catch (error: any) {
|
||||
if (spinner) spinner.fail('Search failed')
|
||||
console.error(chalk.red('Search failed:', error.message))
|
||||
|
|
@ -496,6 +512,7 @@ export const coreCommands = {
|
|||
|
||||
const spinner = ora('Fetching item...').start()
|
||||
const brain = getBrainy()
|
||||
await brain.init()
|
||||
|
||||
// Try to get the item
|
||||
const item = await brain.get(id)
|
||||
|
|
@ -529,6 +546,10 @@ export const coreCommands = {
|
|||
} else {
|
||||
formatOutput(item, options)
|
||||
}
|
||||
|
||||
// One-shot command — see add() for why the explicit close + exit.
|
||||
await brain.close()
|
||||
process.exit(0)
|
||||
} catch (error: any) {
|
||||
if (spinner) spinner.fail('Failed to get item')
|
||||
console.error(chalk.red('Failed to get item:', error.message))
|
||||
|
|
@ -589,6 +610,7 @@ export const coreCommands = {
|
|||
|
||||
const spinner = ora('Creating relationship...').start()
|
||||
const brain = getBrainy()
|
||||
await brain.init()
|
||||
|
||||
let metadata: any = {}
|
||||
if (options.metadata) {
|
||||
|
|
@ -627,6 +649,10 @@ export const coreCommands = {
|
|||
} else {
|
||||
formatOutput({ id: result, source, verb, target, metadata }, options)
|
||||
}
|
||||
|
||||
// One-shot command — see add() for why the explicit close + exit.
|
||||
await brain.close()
|
||||
process.exit(0)
|
||||
} catch (error: any) {
|
||||
if (spinner) spinner.fail('Failed to create relationship')
|
||||
console.error(chalk.red('Failed to create relationship:', error.message))
|
||||
|
|
@ -683,6 +709,7 @@ export const coreCommands = {
|
|||
|
||||
spinner = ora('Updating entity...').start()
|
||||
const brain = getBrainy()
|
||||
await brain.init()
|
||||
|
||||
// Get existing entity first
|
||||
const existing = await brain.get(id)
|
||||
|
|
@ -731,6 +758,10 @@ export const coreCommands = {
|
|||
} else {
|
||||
formatOutput({ id, updated: true }, options)
|
||||
}
|
||||
|
||||
// One-shot command — see add() for why the explicit close + exit.
|
||||
await brain.close()
|
||||
process.exit(0)
|
||||
} catch (error: any) {
|
||||
if (spinner) spinner.fail('Failed to update entity')
|
||||
console.error(chalk.red('Update failed:', error.message))
|
||||
|
|
@ -784,6 +815,7 @@ export const coreCommands = {
|
|||
|
||||
spinner = ora('Deleting entity...').start()
|
||||
const brain = getBrainy()
|
||||
await brain.init()
|
||||
|
||||
await brain.delete(id)
|
||||
|
||||
|
|
@ -794,6 +826,10 @@ export const coreCommands = {
|
|||
} else {
|
||||
formatOutput({ id, deleted: true }, options)
|
||||
}
|
||||
|
||||
// One-shot command — see add() for why the explicit close + exit.
|
||||
await brain.close()
|
||||
process.exit(0)
|
||||
} catch (error: any) {
|
||||
if (spinner) spinner.fail('Failed to delete entity')
|
||||
console.error(chalk.red('Delete failed:', error.message))
|
||||
|
|
@ -846,6 +882,7 @@ export const coreCommands = {
|
|||
|
||||
spinner = ora('Removing relationship...').start()
|
||||
const brain = getBrainy()
|
||||
await brain.init()
|
||||
|
||||
await brain.unrelate(id)
|
||||
|
||||
|
|
@ -856,6 +893,10 @@ export const coreCommands = {
|
|||
} else {
|
||||
formatOutput({ id, removed: true }, options)
|
||||
}
|
||||
|
||||
// One-shot command — see add() for why the explicit close + exit.
|
||||
await brain.close()
|
||||
process.exit(0)
|
||||
} catch (error: any) {
|
||||
if (spinner) spinner.fail('Failed to remove relationship')
|
||||
console.error(chalk.red('Unrelate failed:', error.message))
|
||||
|
|
@ -875,7 +916,9 @@ export const coreCommands = {
|
|||
|
||||
if (options.json) {
|
||||
formatOutput(diag, options)
|
||||
return
|
||||
// One-shot command — see add() for why the explicit close + exit.
|
||||
await brain.close()
|
||||
process.exit(0)
|
||||
}
|
||||
|
||||
console.log(chalk.bold('\nBrainy Diagnostics'))
|
||||
|
|
@ -907,6 +950,10 @@ export const coreCommands = {
|
|||
console.log(` Metadata: ${diag.indexes.metadata.type} (initialized: ${diag.indexes.metadata.initialized})`)
|
||||
console.log(` Graph: ${diag.indexes.graph.type} (initialized: ${diag.indexes.graph.initialized}, wired: ${diag.indexes.graph.wiredToStorage})`)
|
||||
console.log()
|
||||
|
||||
// One-shot command — see add() for why the explicit close + exit.
|
||||
await brain.close()
|
||||
process.exit(0)
|
||||
} catch (error: any) {
|
||||
console.error(chalk.red('Diagnostics failed: ' + error.message))
|
||||
process.exit(1)
|
||||
|
|
|
|||
|
|
@ -152,6 +152,7 @@ export const importCommands = {
|
|||
|
||||
spinner = ora('Initializing import...').start()
|
||||
const brain = getBrainy()
|
||||
await brain.init()
|
||||
|
||||
// Handle different source types
|
||||
let result: any
|
||||
|
|
@ -401,6 +402,12 @@ export const importCommands = {
|
|||
} else if (options.json) {
|
||||
formatOutput(result, options)
|
||||
}
|
||||
|
||||
// close() releases the writer lock and indexes, but global timers
|
||||
// (UnifiedCache bookkeeping, PathResolver stats) keep the event loop
|
||||
// alive. CLI commands are one-shot — exit explicitly.
|
||||
await brain.close()
|
||||
process.exit(0)
|
||||
} catch (error: any) {
|
||||
if (spinner) spinner.fail('Import failed')
|
||||
console.error(chalk.red('Import failed:', error.message))
|
||||
|
|
@ -472,9 +479,10 @@ export const importCommands = {
|
|||
|
||||
spinner = ora('Initializing VFS import...').start()
|
||||
const brain = getBrainy()
|
||||
await brain.init()
|
||||
|
||||
// Get VFS
|
||||
const vfs = await brain.vfs
|
||||
const vfs = brain.vfs
|
||||
|
||||
// Load DirectoryImporter
|
||||
const { DirectoryImporter } = await import('../../vfs/importers/DirectoryImporter.js')
|
||||
|
|
@ -536,6 +544,10 @@ export const importCommands = {
|
|||
} else if (options.json) {
|
||||
formatOutput(result, options)
|
||||
}
|
||||
|
||||
// One-shot command — see import() for why the explicit close + exit.
|
||||
await brain.close()
|
||||
process.exit(0)
|
||||
} catch (error: any) {
|
||||
if (spinner) spinner.fail('VFS import failed')
|
||||
console.error(chalk.red('VFS import failed:', error.message))
|
||||
|
|
|
|||
|
|
@ -41,6 +41,7 @@ export const insightsCommands = {
|
|||
|
||||
try {
|
||||
const brain = getBrainy()
|
||||
await brain.init()
|
||||
|
||||
// Get insights from Brainy
|
||||
const insights = await brain.insights()
|
||||
|
|
@ -102,6 +103,12 @@ export const insightsCommands = {
|
|||
} else {
|
||||
formatOutput(insights, options)
|
||||
}
|
||||
|
||||
// close() releases the writer lock and indexes, but global timers
|
||||
// (UnifiedCache bookkeeping, PathResolver stats) keep the event loop
|
||||
// alive. CLI commands are one-shot — exit explicitly.
|
||||
await brain.close()
|
||||
process.exit(0)
|
||||
} catch (error: any) {
|
||||
spinner.fail('Failed to get insights')
|
||||
console.error(chalk.red('Insights failed:', error.message))
|
||||
|
|
@ -120,6 +127,7 @@ export const insightsCommands = {
|
|||
|
||||
try {
|
||||
const brain = getBrainy()
|
||||
await brain.init()
|
||||
|
||||
// Get available fields from metadata index
|
||||
const fields = await brain.getAvailableFields()
|
||||
|
|
@ -166,6 +174,10 @@ export const insightsCommands = {
|
|||
}))
|
||||
formatOutput(fieldsWithStats, options)
|
||||
}
|
||||
|
||||
// One-shot command — see insights() for why the explicit close + exit.
|
||||
await brain.close()
|
||||
process.exit(0)
|
||||
} catch (error: any) {
|
||||
spinner.fail('Failed to get fields')
|
||||
console.error(chalk.red('Fields analysis failed:', error.message))
|
||||
|
|
@ -186,6 +198,7 @@ export const insightsCommands = {
|
|||
if (!field) {
|
||||
spinner = ora('Getting available fields...').start()
|
||||
const brain = getBrainy()
|
||||
await brain.init()
|
||||
const availableFields = await brain.getAvailableFields()
|
||||
spinner.stop()
|
||||
|
||||
|
|
@ -202,6 +215,7 @@ export const insightsCommands = {
|
|||
|
||||
spinner = ora(`Getting values for field: ${field}...`).start()
|
||||
const brain = getBrainy()
|
||||
await brain.init()
|
||||
|
||||
const values = await brain.getFieldValues(field)
|
||||
const limit = options.limit ? parseInt(options.limit) : 100
|
||||
|
|
@ -242,6 +256,10 @@ export const insightsCommands = {
|
|||
} else {
|
||||
formatOutput({ field, values, count: values.length }, options)
|
||||
}
|
||||
|
||||
// One-shot command — see insights() for why the explicit close + exit.
|
||||
await brain.close()
|
||||
process.exit(0)
|
||||
} catch (error: any) {
|
||||
if (spinner) spinner.fail('Failed to get field values')
|
||||
console.error(chalk.red('Field values failed:', error.message))
|
||||
|
|
@ -289,6 +307,7 @@ export const insightsCommands = {
|
|||
|
||||
spinner = ora('Analyzing optimal query plan...').start()
|
||||
const brain = getBrainy()
|
||||
await brain.init()
|
||||
|
||||
const plan = await brain.getOptimalQueryPlan(filters)
|
||||
|
||||
|
|
@ -328,6 +347,10 @@ export const insightsCommands = {
|
|||
} else {
|
||||
formatOutput({ filters, plan }, options)
|
||||
}
|
||||
|
||||
// One-shot command — see insights() for why the explicit close + exit.
|
||||
await brain.close()
|
||||
process.exit(0)
|
||||
} catch (error: any) {
|
||||
if (spinner) spinner.fail('Failed to generate query plan')
|
||||
console.error(chalk.red('Query plan failed:', error.message))
|
||||
|
|
|
|||
|
|
@ -1,7 +1,11 @@
|
|||
/**
|
||||
* 🧠 Neural Similarity API Commands
|
||||
*
|
||||
* CLI interface for semantic similarity, clustering, and neural operations
|
||||
* @module cli/commands/neural
|
||||
* @description Neural CLI commands: semantic similarity, clustering,
|
||||
* hierarchy, neighbors, outlier detection, and visualization data export.
|
||||
* Registered in `src/cli/index.ts` as `similar` / `cluster` / `related` /
|
||||
* `hierarchy` / `outliers` / `visualize`. Each command is one-shot: it
|
||||
* initializes the shared Brainy instance, runs the neural operation, then
|
||||
* closes the store and exits explicitly.
|
||||
*/
|
||||
|
||||
import inquirer from 'inquirer'
|
||||
|
|
@ -25,136 +29,13 @@ interface CommandArguments {
|
|||
_: string[];
|
||||
}
|
||||
|
||||
export const neuralCommand = {
|
||||
command: 'neural [action]',
|
||||
describe: '🧠 Neural similarity and clustering operations',
|
||||
|
||||
builder: (yargs: any) => {
|
||||
return yargs
|
||||
.positional('action', {
|
||||
describe: 'Neural operation to perform',
|
||||
type: 'string',
|
||||
choices: ['similar', 'clusters', 'hierarchy', 'neighbors', 'path', 'outliers', 'visualize']
|
||||
})
|
||||
.option('id', {
|
||||
describe: 'Item ID for similarity operations',
|
||||
type: 'string',
|
||||
alias: 'i'
|
||||
})
|
||||
.option('query', {
|
||||
describe: 'Query text for similarity search',
|
||||
type: 'string',
|
||||
alias: 'q'
|
||||
})
|
||||
.option('threshold', {
|
||||
describe: 'Similarity threshold (0-1)',
|
||||
type: 'number',
|
||||
default: 0.7,
|
||||
alias: 't'
|
||||
})
|
||||
.option('format', {
|
||||
describe: 'Output format',
|
||||
type: 'string',
|
||||
choices: ['json', 'table', 'tree', 'graph'],
|
||||
default: 'table',
|
||||
alias: 'f'
|
||||
})
|
||||
.option('output', {
|
||||
describe: 'Output file path',
|
||||
type: 'string',
|
||||
alias: 'o'
|
||||
})
|
||||
.option('limit', {
|
||||
describe: 'Maximum number of results',
|
||||
type: 'number',
|
||||
default: 10,
|
||||
alias: 'l'
|
||||
})
|
||||
.option('algorithm', {
|
||||
describe: 'Clustering algorithm',
|
||||
type: 'string',
|
||||
choices: ['hierarchical', 'kmeans', 'dbscan', 'auto'],
|
||||
default: 'auto',
|
||||
alias: 'a'
|
||||
})
|
||||
.option('dimensions', {
|
||||
describe: 'Visualization dimensions (2 or 3)',
|
||||
type: 'number',
|
||||
choices: [2, 3],
|
||||
default: 2,
|
||||
alias: 'd'
|
||||
})
|
||||
.option('explain', {
|
||||
describe: 'Include detailed explanations',
|
||||
type: 'boolean',
|
||||
default: false,
|
||||
alias: 'e'
|
||||
})
|
||||
},
|
||||
|
||||
handler: async (argv: CommandArguments) => {
|
||||
console.log(chalk.cyan('\n🧠 NEURAL SIMILARITY API'))
|
||||
console.log(chalk.gray('━'.repeat(50)))
|
||||
let brainyInstance: Brainy | null = null
|
||||
|
||||
// Initialize Brainy and Neural API
|
||||
const brain = new Brainy()
|
||||
const neural = brain.neural()
|
||||
|
||||
try {
|
||||
const action = argv.action || await promptForAction()
|
||||
|
||||
switch (action) {
|
||||
case 'similar':
|
||||
await handleSimilarCommand(neural, argv)
|
||||
break
|
||||
case 'clusters':
|
||||
await handleClustersCommand(neural, argv)
|
||||
break
|
||||
case 'hierarchy':
|
||||
await handleHierarchyCommand(neural, argv)
|
||||
break
|
||||
case 'neighbors':
|
||||
await handleNeighborsCommand(neural, argv)
|
||||
break
|
||||
case 'path':
|
||||
console.log(chalk.yellow('\n⚠️ Semantic path finding coming soon'))
|
||||
console.log(chalk.dim('This feature requires implementing graph traversal algorithms'))
|
||||
console.log(chalk.dim('Use "neighbors" and "hierarchy" commands to explore connections'))
|
||||
break
|
||||
case 'outliers':
|
||||
await handleOutliersCommand(neural, argv)
|
||||
break
|
||||
case 'visualize':
|
||||
await handleVisualizeCommand(neural, argv)
|
||||
break
|
||||
default:
|
||||
console.log(chalk.red(`❌ Unknown action: ${action}`))
|
||||
showHelp()
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(chalk.red('💥 Error:'), error instanceof Error ? error.message : error)
|
||||
process.exit(1)
|
||||
}
|
||||
const getBrainy = (): Brainy => {
|
||||
if (!brainyInstance) {
|
||||
brainyInstance = new Brainy()
|
||||
}
|
||||
}
|
||||
|
||||
async function promptForAction(): Promise<string> {
|
||||
const answer = await inquirer.prompt([{
|
||||
type: 'list',
|
||||
name: 'action',
|
||||
message: 'Choose a neural operation:',
|
||||
choices: [
|
||||
{ name: '🔗 Calculate similarity between items', value: 'similar' },
|
||||
{ name: '🎯 Find semantic clusters', value: 'clusters' },
|
||||
{ name: '🌳 Show item hierarchy', value: 'hierarchy' },
|
||||
{ name: '🕸️ Find semantic neighbors', value: 'neighbors' },
|
||||
{ name: '🛣️ Find semantic path between items (coming soon)', value: 'path', disabled: true },
|
||||
{ name: '🚨 Detect outliers', value: 'outliers' },
|
||||
{ name: '📊 Generate visualization data', value: 'visualize' }
|
||||
]
|
||||
}])
|
||||
|
||||
return answer.action
|
||||
return brainyInstance
|
||||
}
|
||||
|
||||
async function handleSimilarCommand(neural: any, argv: CommandArguments): Promise<void> {
|
||||
|
|
@ -436,63 +317,6 @@ async function handleNeighborsCommand(neural: any, argv: CommandArguments): Prom
|
|||
}
|
||||
}
|
||||
|
||||
async function handlePathCommand(neural: any, argv: CommandArguments): Promise<void> {
|
||||
const spinner = ora('🛣️ Finding semantic path...').start()
|
||||
|
||||
try {
|
||||
let fromId: string, toId: string
|
||||
|
||||
if (argv._ && argv._.length >= 3) {
|
||||
fromId = argv._[1]
|
||||
toId = argv._[2]
|
||||
} else {
|
||||
spinner.stop()
|
||||
const answers = await inquirer.prompt([
|
||||
{
|
||||
type: 'input',
|
||||
name: 'from',
|
||||
message: 'From item ID:',
|
||||
validate: (input: string) => input.length > 0
|
||||
},
|
||||
{
|
||||
type: 'input',
|
||||
name: 'to',
|
||||
message: 'To item ID:',
|
||||
validate: (input: string) => input.length > 0
|
||||
}
|
||||
])
|
||||
fromId = answers.from
|
||||
toId = answers.to
|
||||
spinner.start()
|
||||
}
|
||||
|
||||
const path = await neural.semanticPath(fromId, toId)
|
||||
|
||||
if (path.length === 0) {
|
||||
spinner.warn('🚫 No semantic path found')
|
||||
console.log(`No path found between ${chalk.cyan(fromId)} and ${chalk.cyan(toId)}`)
|
||||
} else {
|
||||
spinner.succeed(`✅ Found path with ${path.length} hops`)
|
||||
|
||||
console.log(`\n🛣️ Semantic Path from ${chalk.cyan(fromId)} to ${chalk.cyan(toId)}:`)
|
||||
console.log(`${chalk.cyan(fromId)} (start)`)
|
||||
|
||||
path.forEach((hop, index) => {
|
||||
console.log(`${' '.repeat(index + 1)}↓ ${(hop.similarity * 100).toFixed(1)}%`)
|
||||
console.log(`${' '.repeat(index + 1)}${hop.id} (hop ${hop.hop})`)
|
||||
})
|
||||
}
|
||||
|
||||
if (argv.output) {
|
||||
await saveToFile(argv.output, path, argv.format!)
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
spinner.fail('💥 Failed to find path')
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
async function handleOutliersCommand(neural: any, argv: CommandArguments): Promise<void> {
|
||||
const spinner = ora('🚨 Detecting semantic outliers...').start()
|
||||
|
||||
|
|
@ -592,32 +416,32 @@ function formatAsTable(data: any): string {
|
|||
return JSON.stringify(data, null, 2)
|
||||
}
|
||||
|
||||
function showHelp(): void {
|
||||
console.log('\n🧠 Neural Similarity API Commands:')
|
||||
console.log('')
|
||||
console.log(' brainy neural similar <item1> <item2> Calculate similarity')
|
||||
console.log(' brainy neural clusters Find semantic clusters')
|
||||
console.log(' brainy neural hierarchy <id> Show item hierarchy')
|
||||
console.log(' brainy neural neighbors <id> Find semantic neighbors')
|
||||
console.log(' brainy neural path <from> <to> Find semantic path')
|
||||
console.log(' brainy neural outliers Detect outliers')
|
||||
console.log(' brainy neural visualize Generate visualization data')
|
||||
console.log('')
|
||||
console.log('Options:')
|
||||
console.log(' --threshold, -t Similarity threshold (0-1)')
|
||||
console.log(' --format, -f Output format (json|table|tree|graph)')
|
||||
console.log(' --output, -o Save to file')
|
||||
console.log(' --limit, -l Maximum results')
|
||||
console.log(' --explain, -e Include explanations')
|
||||
console.log('')
|
||||
/**
|
||||
* @description Run a neural CLI handler with the one-shot lifecycle every
|
||||
* Brainy command follows: init → work → `close()` → explicit `process.exit`.
|
||||
* close() releases the writer lock and indexes, but global timers
|
||||
* (UnifiedCache bookkeeping, PathResolver stats) keep the event loop alive,
|
||||
* so one-shot commands must exit explicitly.
|
||||
* @param work - The handler body, given the initialized neural API.
|
||||
*/
|
||||
async function runNeuralCommand(work: (neural: ReturnType<Brainy['neural']>) => Promise<void>): Promise<void> {
|
||||
try {
|
||||
const brain = getBrainy()
|
||||
await brain.init()
|
||||
|
||||
await work(brain.neural())
|
||||
|
||||
await brain.close()
|
||||
process.exit(0)
|
||||
} catch (error) {
|
||||
console.error(chalk.red('💥 Error:'), error instanceof Error ? error.message : error)
|
||||
process.exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
// Commander-compatible wrappers
|
||||
export const neuralCommands = {
|
||||
async similar(a?: string, b?: string, options?: any) {
|
||||
const brain = new Brainy()
|
||||
const neural = brain.neural()
|
||||
|
||||
// Build argv-style object for handler
|
||||
const argv: CommandArguments = {
|
||||
_: ['neural', 'similar', a || '', b || ''].filter(x => x),
|
||||
|
|
@ -627,13 +451,10 @@ export const neuralCommands = {
|
|||
...options
|
||||
}
|
||||
|
||||
await handleSimilarCommand(neural, argv)
|
||||
await runNeuralCommand((neural) => handleSimilarCommand(neural, argv))
|
||||
},
|
||||
|
||||
async cluster(options?: any) {
|
||||
const brain = new Brainy()
|
||||
const neural = brain.neural()
|
||||
|
||||
const argv: CommandArguments = {
|
||||
_: ['neural', 'cluster'],
|
||||
algorithm: options?.algorithm || 'hierarchical',
|
||||
|
|
@ -643,26 +464,20 @@ export const neuralCommands = {
|
|||
...options
|
||||
}
|
||||
|
||||
await handleClustersCommand(neural, argv)
|
||||
await runNeuralCommand((neural) => handleClustersCommand(neural, argv))
|
||||
},
|
||||
|
||||
async hierarchy(id?: string, options?: any) {
|
||||
const brain = new Brainy()
|
||||
const neural = brain.neural()
|
||||
|
||||
const argv: CommandArguments = {
|
||||
_: ['neural', 'hierarchy', id || ''].filter(x => x),
|
||||
id,
|
||||
...options
|
||||
}
|
||||
|
||||
await handleHierarchyCommand(neural, argv)
|
||||
await runNeuralCommand((neural) => handleHierarchyCommand(neural, argv))
|
||||
},
|
||||
|
||||
async related(id?: string, options?: any) {
|
||||
const brain = new Brainy()
|
||||
const neural = brain.neural()
|
||||
|
||||
const argv: CommandArguments = {
|
||||
_: ['neural', 'related', id || ''].filter(x => x),
|
||||
id,
|
||||
|
|
@ -671,13 +486,10 @@ export const neuralCommands = {
|
|||
...options
|
||||
}
|
||||
|
||||
await handleNeighborsCommand(neural, argv)
|
||||
await runNeuralCommand((neural) => handleNeighborsCommand(neural, argv))
|
||||
},
|
||||
|
||||
async outliers(options?: any) {
|
||||
const brain = new Brainy()
|
||||
const neural = brain.neural()
|
||||
|
||||
const argv: CommandArguments = {
|
||||
_: ['neural', 'outliers'],
|
||||
threshold: options?.threshold ? parseFloat(options.threshold) : 0.3,
|
||||
|
|
@ -685,13 +497,10 @@ export const neuralCommands = {
|
|||
...options
|
||||
}
|
||||
|
||||
await handleOutliersCommand(neural, argv)
|
||||
await runNeuralCommand((neural) => handleOutliersCommand(neural, argv))
|
||||
},
|
||||
|
||||
async visualize(options?: any) {
|
||||
const brain = new Brainy()
|
||||
const neural = brain.neural()
|
||||
|
||||
const argv: CommandArguments = {
|
||||
_: ['neural', 'visualize'],
|
||||
format: options?.format || 'json',
|
||||
|
|
@ -701,8 +510,6 @@ export const neuralCommands = {
|
|||
...options
|
||||
}
|
||||
|
||||
await handleVisualizeCommand(neural, argv)
|
||||
await runNeuralCommand((neural) => handleVisualizeCommand(neural, argv))
|
||||
}
|
||||
}
|
||||
|
||||
export default neuralCommand
|
||||
}
|
||||
|
|
@ -62,6 +62,7 @@ export const nlpCommands = {
|
|||
|
||||
spinner = ora('Extracting entities with neural NLP...').start()
|
||||
const brain = getBrainy()
|
||||
await brain.init()
|
||||
|
||||
// Extract entities using Brainy's neural entity extractor
|
||||
const entities = await brain.extract(text)
|
||||
|
|
@ -105,6 +106,12 @@ export const nlpCommands = {
|
|||
} else {
|
||||
formatOutput(entities, options)
|
||||
}
|
||||
|
||||
// close() releases the writer lock and indexes, but global timers
|
||||
// (UnifiedCache bookkeeping, PathResolver stats) keep the event loop
|
||||
// alive. CLI commands are one-shot — exit explicitly.
|
||||
await brain.close()
|
||||
process.exit(0)
|
||||
} catch (error: any) {
|
||||
if (spinner) spinner.fail('Entity extraction failed')
|
||||
console.error(chalk.red('Extraction failed:', error.message))
|
||||
|
|
@ -147,6 +154,7 @@ export const nlpCommands = {
|
|||
|
||||
spinner = ora('Extracting concepts with neural analysis...').start()
|
||||
const brain = getBrainy()
|
||||
await brain.init()
|
||||
|
||||
const confidence = options.threshold ? parseFloat(options.threshold) : 0.5
|
||||
const concepts = await brain.extractConcepts(text, { confidence })
|
||||
|
|
@ -171,6 +179,10 @@ export const nlpCommands = {
|
|||
} else {
|
||||
formatOutput(concepts, options)
|
||||
}
|
||||
|
||||
// One-shot command — see extract() for why the explicit close + exit.
|
||||
await brain.close()
|
||||
process.exit(0)
|
||||
} catch (error: any) {
|
||||
if (spinner) spinner.fail('Concept extraction failed')
|
||||
console.error(chalk.red('Extraction failed:', error.message))
|
||||
|
|
@ -201,6 +213,7 @@ export const nlpCommands = {
|
|||
|
||||
spinner = ora('Analyzing text with neural NLP...').start()
|
||||
const brain = getBrainy()
|
||||
await brain.init()
|
||||
|
||||
// Run both entity extraction and concept extraction
|
||||
const [entities, concepts] = await Promise.all([
|
||||
|
|
@ -265,6 +278,10 @@ export const nlpCommands = {
|
|||
concepts
|
||||
}, options)
|
||||
}
|
||||
|
||||
// One-shot command — see extract() for why the explicit close + exit.
|
||||
await brain.close()
|
||||
process.exit(0)
|
||||
} catch (error: any) {
|
||||
if (spinner) spinner.fail('Analysis failed')
|
||||
console.error(chalk.red('Analysis failed:', error.message))
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -22,8 +22,7 @@ interface StatsOptions extends UtilityOptions {
|
|||
}
|
||||
|
||||
interface CleanOptions extends UtilityOptions {
|
||||
removeOrphans?: boolean
|
||||
rebuildIndex?: boolean
|
||||
force?: boolean
|
||||
}
|
||||
|
||||
interface BenchmarkOptions extends UtilityOptions {
|
||||
|
|
@ -63,6 +62,7 @@ export const utilityCommands = {
|
|||
|
||||
try {
|
||||
const brain = getBrainy()
|
||||
await brain.init()
|
||||
const nounCount = await brain.getNounCount()
|
||||
const verbCount = await brain.getVerbCount()
|
||||
const memUsage = process.memoryUsage()
|
||||
|
|
@ -77,7 +77,11 @@ export const utilityCommands = {
|
|||
|
||||
if (options.json) {
|
||||
formatOutput(stats, options)
|
||||
return
|
||||
// close() releases the writer lock and indexes, but global timers
|
||||
// (UnifiedCache bookkeeping, PathResolver stats) keep the event loop
|
||||
// alive. CLI commands are one-shot — exit explicitly.
|
||||
await brain.close()
|
||||
process.exit(0)
|
||||
}
|
||||
|
||||
console.log(chalk.cyan('\n📊 Database Statistics\n'))
|
||||
|
|
@ -112,7 +116,10 @@ export const utilityCommands = {
|
|||
)
|
||||
|
||||
console.log(memTable.toString())
|
||||
|
||||
|
||||
// One-shot command — see the --json branch for why the explicit exit.
|
||||
await brain.close()
|
||||
process.exit(0)
|
||||
} catch (error: any) {
|
||||
spinner.fail('Failed to gather statistics')
|
||||
console.error(chalk.red(error.message))
|
||||
|
|
@ -121,30 +128,33 @@ export const utilityCommands = {
|
|||
},
|
||||
|
||||
/**
|
||||
* Clean and optimize database
|
||||
* Clear the database (all entities, relationships, and indexes).
|
||||
* Destructive — asks for confirmation unless --force is passed.
|
||||
*/
|
||||
async clean(options: CleanOptions) {
|
||||
const spinner = ora('Cleaning database...').start()
|
||||
let spinner: ReturnType<typeof ora> | null = null
|
||||
|
||||
try {
|
||||
const brain = getBrainy()
|
||||
// Destructive operation — confirm first (skipped with --force).
|
||||
if (!options.force) {
|
||||
const inquirer = (await import('inquirer')).default
|
||||
const { confirm } = await inquirer.prompt([{
|
||||
type: 'confirm',
|
||||
name: 'confirm',
|
||||
message: chalk.yellow('⚠️ Permanently delete ALL data (entities, relationships, indexes)?'),
|
||||
default: false
|
||||
}])
|
||||
|
||||
// For now, only support full clear
|
||||
// removeOrphans and rebuildIndex would require new Brainy APIs
|
||||
if (options.removeOrphans || options.rebuildIndex) {
|
||||
spinner.warn('Advanced cleanup options not yet implemented')
|
||||
console.log(chalk.yellow('\n⚠️ Advanced cleanup features coming soon:'))
|
||||
console.log(chalk.dim(' • --remove-orphans: Remove disconnected items'))
|
||||
console.log(chalk.dim(' • --rebuild-index: Rebuild vector index'))
|
||||
console.log(chalk.dim('\nUse "brainy clean" without options to clear the database'))
|
||||
return
|
||||
if (!confirm) {
|
||||
console.log(chalk.yellow('Clean cancelled'))
|
||||
process.exit(0)
|
||||
}
|
||||
}
|
||||
|
||||
// Show warning before clearing
|
||||
console.log(chalk.yellow('\n⚠️ WARNING: This will permanently delete ALL data!'))
|
||||
const brain = getBrainy()
|
||||
|
||||
// Clear all data (entities, relationships, and every index)
|
||||
spinner.text = 'Clearing all data...'
|
||||
spinner = ora('Clearing all data...').start()
|
||||
await brain.init()
|
||||
await brain.clear()
|
||||
|
||||
|
|
@ -163,7 +173,7 @@ export const utilityCommands = {
|
|||
await brain.close()
|
||||
process.exit(0)
|
||||
} catch (error: any) {
|
||||
spinner.fail('Cleanup failed')
|
||||
if (spinner) spinner.fail('Cleanup failed')
|
||||
console.error(chalk.red(error.message))
|
||||
process.exit(1)
|
||||
}
|
||||
|
|
@ -185,7 +195,8 @@ export const utilityCommands = {
|
|||
|
||||
try {
|
||||
const brain = getBrainy()
|
||||
|
||||
await brain.init()
|
||||
|
||||
// Benchmark different operations
|
||||
const benchmarks = [
|
||||
{ name: 'add', enabled: operations === 'all' || operations.includes('add') },
|
||||
|
|
@ -205,7 +216,8 @@ export const utilityCommands = {
|
|||
|
||||
switch (bench.name) {
|
||||
case 'add':
|
||||
await brain.add({ data: `Test item ${i}`, type: NounType.Thing, metadata: { benchmark: true } })
|
||||
// 8.0 requires a subtype on every write by default.
|
||||
await brain.add({ data: `Test item ${i}`, type: NounType.Thing, subtype: 'benchmark', metadata: { benchmark: true } })
|
||||
break
|
||||
case 'search':
|
||||
await brain.find({ query: 'test', limit: 10 })
|
||||
|
|
@ -284,7 +296,10 @@ export const utilityCommands = {
|
|||
} else {
|
||||
formatOutput(results, options)
|
||||
}
|
||||
|
||||
|
||||
// One-shot command — see stats() for why the explicit close + exit.
|
||||
await brain.close()
|
||||
process.exit(0)
|
||||
} catch (error: any) {
|
||||
console.error(chalk.red('Benchmark failed:'), error.message)
|
||||
process.exit(1)
|
||||
|
|
|
|||
|
|
@ -70,6 +70,12 @@ export const vfsCommands = {
|
|||
} else {
|
||||
formatOutput({ path, content: buffer.toString(), size: buffer.length }, options)
|
||||
}
|
||||
|
||||
// close() releases the writer lock and indexes, but global timers
|
||||
// (UnifiedCache bookkeeping, PathResolver stats) keep the event loop
|
||||
// alive. CLI commands are one-shot — exit explicitly.
|
||||
await brain.close()
|
||||
process.exit(0)
|
||||
} catch (error: any) {
|
||||
spinner.fail('Failed to read file')
|
||||
console.error(chalk.red(error.message))
|
||||
|
|
@ -109,6 +115,10 @@ export const vfsCommands = {
|
|||
} else {
|
||||
formatOutput({ path, size: Buffer.byteLength(data) }, options)
|
||||
}
|
||||
|
||||
// One-shot command — see read() for why the explicit close + exit.
|
||||
await brain.close()
|
||||
process.exit(0)
|
||||
} catch (error: any) {
|
||||
spinner.fail('Failed to write file')
|
||||
console.error(chalk.red(error.message))
|
||||
|
|
@ -132,7 +142,9 @@ export const vfsCommands = {
|
|||
if (!options.json) {
|
||||
if (!Array.isArray(entries) || entries.length === 0) {
|
||||
console.log(chalk.yellow('Directory is empty'))
|
||||
return
|
||||
// One-shot command — see read() for why the explicit close + exit.
|
||||
await brain.close()
|
||||
process.exit(0)
|
||||
}
|
||||
|
||||
if (options.long) {
|
||||
|
|
@ -171,6 +183,10 @@ export const vfsCommands = {
|
|||
} else {
|
||||
formatOutput(entries, options)
|
||||
}
|
||||
|
||||
// One-shot command — see read() for why the explicit close + exit.
|
||||
await brain.close()
|
||||
process.exit(0)
|
||||
} catch (error: any) {
|
||||
spinner.fail('Failed to list directory')
|
||||
console.error(chalk.red(error.message))
|
||||
|
|
@ -202,6 +218,10 @@ export const vfsCommands = {
|
|||
} else {
|
||||
formatOutput(stats, options)
|
||||
}
|
||||
|
||||
// One-shot command — see read() for why the explicit close + exit.
|
||||
await brain.close()
|
||||
process.exit(0)
|
||||
} catch (error: any) {
|
||||
spinner.fail('Failed to get stats')
|
||||
console.error(chalk.red(error.message))
|
||||
|
|
@ -227,6 +247,10 @@ export const vfsCommands = {
|
|||
} else {
|
||||
formatOutput({ path, created: true }, options)
|
||||
}
|
||||
|
||||
// One-shot command — see read() for why the explicit close + exit.
|
||||
await brain.close()
|
||||
process.exit(0)
|
||||
} catch (error: any) {
|
||||
spinner.fail('Failed to create directory')
|
||||
console.error(chalk.red(error.message))
|
||||
|
|
@ -258,12 +282,16 @@ export const vfsCommands = {
|
|||
} else {
|
||||
formatOutput({ path, removed: true }, options)
|
||||
}
|
||||
|
||||
// One-shot command — see read() for why the explicit close + exit.
|
||||
await brain.close()
|
||||
process.exit(0)
|
||||
} catch (error: any) {
|
||||
spinner.fail('Failed to remove')
|
||||
console.error(chalk.red(error.message))
|
||||
if (!options.force) {
|
||||
process.exit(1)
|
||||
}
|
||||
// --force tolerates the failure but the process is still one-shot:
|
||||
// exit cleanly (0) instead of falling off the event loop.
|
||||
process.exit(options.force ? 0 : 1)
|
||||
}
|
||||
},
|
||||
|
||||
|
|
@ -303,6 +331,10 @@ export const vfsCommands = {
|
|||
} else {
|
||||
formatOutput(results, options)
|
||||
}
|
||||
|
||||
// One-shot command — see read() for why the explicit close + exit.
|
||||
await brain.close()
|
||||
process.exit(0)
|
||||
} catch (error: any) {
|
||||
spinner.fail('Search failed')
|
||||
console.error(chalk.red(error.message))
|
||||
|
|
@ -343,6 +375,10 @@ export const vfsCommands = {
|
|||
} else {
|
||||
formatOutput(results, options)
|
||||
}
|
||||
|
||||
// One-shot command — see read() for why the explicit close + exit.
|
||||
await brain.close()
|
||||
process.exit(0)
|
||||
} catch (error: any) {
|
||||
spinner.fail('Failed to find similar files')
|
||||
console.error(chalk.red(error.message))
|
||||
|
|
@ -371,6 +407,10 @@ export const vfsCommands = {
|
|||
} else {
|
||||
formatOutput(tree, options)
|
||||
}
|
||||
|
||||
// One-shot command — see read() for why the explicit close + exit.
|
||||
await brain.close()
|
||||
process.exit(0)
|
||||
} catch (error: any) {
|
||||
spinner.fail('Failed to build tree')
|
||||
console.error(chalk.red(error.message))
|
||||
|
|
|
|||
131
src/cli/index.ts
131
src/cli/index.ts
|
|
@ -19,6 +19,7 @@ import { insightsCommands } from './commands/insights.js'
|
|||
import { importCommands } from './commands/import.js'
|
||||
import { snapshotCommands } from './commands/snapshot.js'
|
||||
import { inspectCommands } from './commands/inspect.js'
|
||||
import { types as typesCommand, validate as validateCommand } from './commands/types.js'
|
||||
import { readFileSync } from 'fs'
|
||||
import { fileURLToPath } from 'url'
|
||||
import { dirname, join } from 'path'
|
||||
|
|
@ -70,14 +71,9 @@ ${chalk.cyan('Examples:')}
|
|||
$ brainy vfs similar /code/Button.tsx
|
||||
|
||||
${chalk.dim('# Storage management')}
|
||||
$ brainy storage status --quota
|
||||
$ brainy storage lifecycle set ${chalk.dim('# Interactive mode')}
|
||||
$ brainy storage cost-estimate
|
||||
$ brainy storage status
|
||||
$ brainy storage batch-delete old-ids.txt
|
||||
|
||||
${chalk.dim('# Interactive mode')}
|
||||
$ brainy interactive
|
||||
|
||||
${chalk.cyan('Documentation:')}
|
||||
${chalk.dim('Full docs:')} https://github.com/soulcraftlabs/brainy
|
||||
${chalk.dim('Report issues:')} https://github.com/soulcraftlabs/brainy/issues
|
||||
|
|
@ -109,7 +105,7 @@ program
|
|||
.description('Advanced search with Triple Intelligence™ (interactive if no query)')
|
||||
.option('-k, --limit <number>', 'Number of results', '10')
|
||||
.option('--offset <number>', 'Skip N results (pagination)')
|
||||
.option('-t, --threshold <number>', 'Similarity threshold (0-1)', '0.7')
|
||||
.option('-t, --threshold <number>', 'Minimum similarity score (0-1); with --near, the proximity threshold')
|
||||
.option('--type <types>', 'Filter by type(s) - comma separated')
|
||||
.option('--where <json>', 'Metadata filters (JSON)')
|
||||
.option('--near <id>', 'Find items near this ID')
|
||||
|
|
@ -181,6 +177,23 @@ program
|
|||
.option('--pretty', 'Pretty print JSON')
|
||||
.action(coreCommands.diagnostics)
|
||||
|
||||
// ===== Type Commands =====
|
||||
|
||||
program
|
||||
.command('types')
|
||||
.description('List all NounType and VerbType values')
|
||||
.option('--noun', 'Show noun types only')
|
||||
.option('--verb', 'Show verb types only')
|
||||
.option('--json', 'Output as JSON')
|
||||
.action(typesCommand)
|
||||
|
||||
program
|
||||
.command('validate [type]')
|
||||
.description('Check whether a type string is a valid NounType or VerbType (interactive if no type)')
|
||||
.option('--verb', 'Validate as a verb type (default: noun type)')
|
||||
.option('--json', 'Output as JSON')
|
||||
.action(validateCommand)
|
||||
|
||||
// ===== Neural Commands =====
|
||||
|
||||
program
|
||||
|
|
@ -222,17 +235,6 @@ program
|
|||
.option('--children-only', 'Show only child hierarchy')
|
||||
.action(neuralCommands.hierarchy)
|
||||
|
||||
program
|
||||
.command('path <from> <to>')
|
||||
.description('Find semantic path between items')
|
||||
.option('--steps', 'Show step-by-step path')
|
||||
.option('--max-hops <number>', 'Maximum path length', '5')
|
||||
.action(() => {
|
||||
console.log(chalk.yellow('\n⚠️ Semantic path finding coming soon'))
|
||||
console.log(chalk.dim('This feature requires implementing graph traversal algorithms'))
|
||||
console.log(chalk.dim('Use "brainy neighbors" and "brainy hierarchy" to explore connections'))
|
||||
})
|
||||
|
||||
program
|
||||
.command('outliers')
|
||||
.alias('anomalies')
|
||||
|
|
@ -457,89 +459,26 @@ program
|
|||
|
||||
program
|
||||
.command('storage')
|
||||
.description('💾 Storage management and cost optimization')
|
||||
.description('💾 Storage management (filesystem + memory backends)')
|
||||
.addCommand(
|
||||
new Command('status')
|
||||
.description('Show storage status and health')
|
||||
.option('--detailed', 'Show detailed information')
|
||||
.option('--quota', 'Show quota information (OPFS)')
|
||||
.description('Show storage backend, counts, root directory, and writer lock')
|
||||
.option('--json', 'Output as JSON')
|
||||
.option('--pretty', 'Pretty-print JSON')
|
||||
.action((options) => {
|
||||
storageCommands.status(options)
|
||||
})
|
||||
)
|
||||
.addCommand(
|
||||
new Command('lifecycle')
|
||||
.description('Lifecycle policy management')
|
||||
.addCommand(
|
||||
new Command('set')
|
||||
.argument('[config-file]', 'Policy configuration file (JSON)')
|
||||
.description('Set lifecycle policy (interactive if no file)')
|
||||
.option('--validate', 'Validate before applying')
|
||||
.action((configFile, options) => {
|
||||
storageCommands.lifecycle.set(configFile, options)
|
||||
})
|
||||
)
|
||||
.addCommand(
|
||||
new Command('get')
|
||||
.description('Get current lifecycle policy')
|
||||
.option('-f, --format <type>', 'Output format (json|yaml)', 'json')
|
||||
.action((options) => {
|
||||
storageCommands.lifecycle.get(options)
|
||||
})
|
||||
)
|
||||
.addCommand(
|
||||
new Command('remove')
|
||||
.description('Remove lifecycle policy')
|
||||
.action((options) => {
|
||||
storageCommands.lifecycle.remove(options)
|
||||
})
|
||||
)
|
||||
)
|
||||
.addCommand(
|
||||
new Command('compression')
|
||||
.description('Compression management (FileSystem)')
|
||||
.addCommand(
|
||||
new Command('enable')
|
||||
.description('Enable gzip compression')
|
||||
.action((options) => {
|
||||
storageCommands.compression.enable(options)
|
||||
})
|
||||
)
|
||||
.addCommand(
|
||||
new Command('disable')
|
||||
.description('Disable compression')
|
||||
.action((options) => {
|
||||
storageCommands.compression.disable(options)
|
||||
})
|
||||
)
|
||||
.addCommand(
|
||||
new Command('status')
|
||||
.description('Show compression status')
|
||||
.action((options) => {
|
||||
storageCommands.compression.status(options)
|
||||
})
|
||||
)
|
||||
)
|
||||
.addCommand(
|
||||
new Command('batch-delete')
|
||||
.argument('<file>', 'File containing entity IDs (one per line)')
|
||||
.description('Batch delete with retry logic')
|
||||
.option('--max-retries <n>', 'Maximum retry attempts', '3')
|
||||
.option('--continue-on-error', 'Continue if some deletes fail')
|
||||
.description('Delete a list of entities through the public delete path (with retry)')
|
||||
.option('--max-retries <n>', 'Maximum retry attempts per ID', '3')
|
||||
.option('--continue-on-error', 'Continue past IDs that still fail after retries')
|
||||
.action((file, options) => {
|
||||
storageCommands.batchDelete(file, options)
|
||||
})
|
||||
)
|
||||
.addCommand(
|
||||
new Command('cost-estimate')
|
||||
.description('Estimate cloud storage costs')
|
||||
.option('--provider <type>', 'Cloud provider (aws|gcs|azure|r2)')
|
||||
.option('--size <gb>', 'Data size in GB')
|
||||
.option('--operations <n>', 'Monthly operations')
|
||||
.action((options) => {
|
||||
storageCommands.costEstimate(options)
|
||||
})
|
||||
)
|
||||
|
||||
// ===== Data Management Commands =====
|
||||
|
||||
|
|
@ -742,9 +681,8 @@ program
|
|||
|
||||
program
|
||||
.command('clean')
|
||||
.description('Clean and optimize database')
|
||||
.option('--remove-orphans', 'Remove orphaned items')
|
||||
.option('--rebuild-index', 'Rebuild search index')
|
||||
.description('Clear the database — ALL entities, relationships, and indexes (asks for confirmation)')
|
||||
.option('-f, --force', 'Skip confirmation prompt')
|
||||
.action(utilityCommands.clean)
|
||||
|
||||
program
|
||||
|
|
@ -785,17 +723,6 @@ program
|
|||
.description('Show the store\'s current generation watermark')
|
||||
.action(snapshotCommands.generation)
|
||||
|
||||
// ===== Interactive Mode =====
|
||||
|
||||
program
|
||||
.command('interactive')
|
||||
.alias('i')
|
||||
.description('Start interactive REPL mode')
|
||||
.action(async () => {
|
||||
const { startInteractiveMode } = await import('./interactive.js')
|
||||
await startInteractiveMode()
|
||||
})
|
||||
|
||||
// ===== Error Handling =====
|
||||
|
||||
program.exitOverride()
|
||||
|
|
|
|||
|
|
@ -1,653 +0,0 @@
|
|||
/**
|
||||
* Professional Interactive CLI System
|
||||
*
|
||||
* Provides consistent, delightful interactive prompts for all commands
|
||||
* with smart defaults, validation, and helpful examples
|
||||
*/
|
||||
|
||||
import chalk from 'chalk'
|
||||
import inquirer from 'inquirer'
|
||||
import ora from 'ora'
|
||||
import { Brainy } from '../brainy.js'
|
||||
import { getBrainyVersion } from '../utils/version.js'
|
||||
|
||||
// Professional color scheme
|
||||
export const colors = {
|
||||
primary: chalk.hex('#3A5F4A'), // Teal (from logo)
|
||||
success: chalk.hex('#2D4A3A'), // Deep teal
|
||||
info: chalk.hex('#4A6B5A'), // Medium teal
|
||||
warning: chalk.hex('#D67441'), // Orange (from logo)
|
||||
error: chalk.hex('#B85C35'), // Deep orange
|
||||
brain: chalk.hex('#D67441'), // Brain orange
|
||||
cream: chalk.hex('#F5E6A3'), // Cream background
|
||||
dim: chalk.dim,
|
||||
bold: chalk.bold,
|
||||
cyan: chalk.cyan,
|
||||
green: chalk.green,
|
||||
yellow: chalk.yellow,
|
||||
red: chalk.red
|
||||
}
|
||||
|
||||
// Icons for consistent visual language
|
||||
export const icons = {
|
||||
brain: '🧠',
|
||||
search: '🔍',
|
||||
add: '➕',
|
||||
delete: '🗑️',
|
||||
update: '🔄',
|
||||
import: '📥',
|
||||
export: '📤',
|
||||
connect: '🔗',
|
||||
question: '❓',
|
||||
success: '✅',
|
||||
error: '❌',
|
||||
warning: '⚠️',
|
||||
info: 'ℹ️',
|
||||
sparkle: '✨',
|
||||
rocket: '🚀',
|
||||
thinking: '🤔',
|
||||
chat: '💬'
|
||||
}
|
||||
|
||||
// Store recent inputs for smart suggestions
|
||||
const recentInputs = {
|
||||
searches: [] as string[],
|
||||
ids: [] as string[],
|
||||
types: [] as string[],
|
||||
formats: [] as string[]
|
||||
}
|
||||
|
||||
/**
|
||||
* Professional prompt wrapper with consistent styling
|
||||
*/
|
||||
export async function prompt(config: any): Promise<any> {
|
||||
// Add consistent styling
|
||||
if (config.message) {
|
||||
config.message = colors.cyan(config.message)
|
||||
}
|
||||
|
||||
// Add prefix with appropriate icon
|
||||
if (!config.prefix) {
|
||||
config.prefix = colors.dim(' › ')
|
||||
}
|
||||
|
||||
return inquirer.prompt([config])
|
||||
}
|
||||
|
||||
/**
|
||||
* Interactive prompt for search query with smart features
|
||||
*/
|
||||
export async function promptSearchQuery(previousSearches?: string[]): Promise<string> {
|
||||
console.log(colors.primary(`\n${icons.search} Smart Search\n`))
|
||||
console.log(colors.dim('Search your neural database with natural language'))
|
||||
console.log(colors.dim('Examples: "meetings last week", "John from Google", "important documents"'))
|
||||
|
||||
const { query } = await prompt({
|
||||
type: 'input',
|
||||
name: 'query',
|
||||
message: 'What would you like to search for?',
|
||||
validate: (input: string) => {
|
||||
if (!input.trim()) {
|
||||
return 'Please enter a search query'
|
||||
}
|
||||
return true
|
||||
},
|
||||
transformer: (input: string) => {
|
||||
// Show live character count
|
||||
const count = input.length
|
||||
if (count > 100) {
|
||||
return colors.warning(input)
|
||||
}
|
||||
return colors.green(input)
|
||||
}
|
||||
})
|
||||
|
||||
// Store for future suggestions
|
||||
if (!recentInputs.searches.includes(query)) {
|
||||
recentInputs.searches.unshift(query)
|
||||
recentInputs.searches = recentInputs.searches.slice(0, 10)
|
||||
}
|
||||
|
||||
return query
|
||||
}
|
||||
|
||||
/**
|
||||
* Interactive prompt for item ID with fuzzy search
|
||||
*/
|
||||
export async function promptItemId(
|
||||
action: string,
|
||||
brain?: Brainy,
|
||||
allowMultiple: boolean = false
|
||||
): Promise<string | string[]> {
|
||||
console.log(colors.primary(`\n${icons.thinking} Select item to ${action}\n`))
|
||||
|
||||
// If we have brain instance, show recent items
|
||||
let choices: any[] = []
|
||||
if (brain) {
|
||||
try {
|
||||
const recent = await brain.find({
|
||||
query: '*',
|
||||
limit: 10
|
||||
})
|
||||
|
||||
choices = recent.map(item => ({
|
||||
name: `${item.id} - ${(item as any).content?.substring(0, 50) || 'No content'}...`,
|
||||
value: item.id,
|
||||
short: item.id
|
||||
}))
|
||||
} catch {
|
||||
// Fallback to manual input
|
||||
}
|
||||
}
|
||||
|
||||
if (choices.length > 0) {
|
||||
choices.push(new inquirer.Separator())
|
||||
choices.push({ name: 'Enter ID manually', value: '__manual__' })
|
||||
|
||||
const { selected } = await prompt({
|
||||
type: allowMultiple ? 'checkbox' : 'list',
|
||||
name: 'selected',
|
||||
message: `Select item(s) to ${action}:`,
|
||||
choices,
|
||||
pageSize: 10
|
||||
})
|
||||
|
||||
if (selected === '__manual__' || (Array.isArray(selected) && selected.includes('__manual__'))) {
|
||||
return promptManualId(action, allowMultiple)
|
||||
}
|
||||
|
||||
return selected
|
||||
} else {
|
||||
return promptManualId(action, allowMultiple)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Manual ID input with validation
|
||||
*/
|
||||
async function promptManualId(action: string, allowMultiple: boolean): Promise<string | string[]> {
|
||||
const { id } = await prompt({
|
||||
type: 'input',
|
||||
name: 'id',
|
||||
message: allowMultiple
|
||||
? `Enter ID(s) to ${action} (comma-separated):`
|
||||
: `Enter ID to ${action}:`,
|
||||
validate: (input: string) => {
|
||||
if (!input.trim()) {
|
||||
return `Please enter at least one ID`
|
||||
}
|
||||
return true
|
||||
}
|
||||
})
|
||||
|
||||
if (allowMultiple) {
|
||||
return id.split(',').map((i: string) => i.trim()).filter(Boolean)
|
||||
}
|
||||
return id.trim()
|
||||
}
|
||||
|
||||
/**
|
||||
* Confirm destructive action with preview
|
||||
*/
|
||||
export async function confirmDestructiveAction(
|
||||
action: string,
|
||||
items: any[],
|
||||
showPreview: boolean = true
|
||||
): Promise<boolean> {
|
||||
console.log(colors.warning(`\n${icons.warning} Confirmation Required\n`))
|
||||
|
||||
if (showPreview && items.length > 0) {
|
||||
console.log(colors.dim(`You are about to ${action}:`))
|
||||
items.slice(0, 5).forEach(item => {
|
||||
console.log(colors.dim(` • ${item.id || item}`))
|
||||
})
|
||||
if (items.length > 5) {
|
||||
console.log(colors.dim(` ... and ${items.length - 5} more`))
|
||||
}
|
||||
console.log()
|
||||
}
|
||||
|
||||
const { confirm } = await prompt({
|
||||
type: 'confirm',
|
||||
name: 'confirm',
|
||||
message: colors.warning(`Are you sure you want to ${action}?`),
|
||||
default: false
|
||||
})
|
||||
|
||||
return confirm
|
||||
}
|
||||
|
||||
/**
|
||||
* Interactive data input with multiline support
|
||||
*/
|
||||
export async function promptDataInput(
|
||||
action: string = 'add',
|
||||
currentValue?: string
|
||||
): Promise<string> {
|
||||
console.log(colors.primary(`\n${icons.add} ${action === 'add' ? 'Add Data' : 'Update Data'}\n`))
|
||||
|
||||
if (currentValue) {
|
||||
console.log(colors.dim('Current value:'))
|
||||
console.log(colors.info(` ${currentValue.substring(0, 100)}${currentValue.length > 100 ? '...' : ''}`))
|
||||
console.log()
|
||||
}
|
||||
|
||||
const { data } = await prompt({
|
||||
type: 'editor',
|
||||
name: 'data',
|
||||
message: 'Enter your data:',
|
||||
default: currentValue || '',
|
||||
postfix: '.md',
|
||||
validate: (input: string) => {
|
||||
if (!input.trim() && action === 'add') {
|
||||
return 'Please enter some data'
|
||||
}
|
||||
return true
|
||||
}
|
||||
})
|
||||
|
||||
return data
|
||||
}
|
||||
|
||||
/**
|
||||
* Interactive metadata input with JSON validation
|
||||
*/
|
||||
export async function promptMetadata(
|
||||
currentMetadata?: any,
|
||||
suggestions?: string[]
|
||||
): Promise<any> {
|
||||
console.log(colors.dim('\nOptional: Add metadata (JSON format)'))
|
||||
|
||||
const { addMetadata } = await prompt({
|
||||
type: 'confirm',
|
||||
name: 'addMetadata',
|
||||
message: 'Would you like to add metadata?',
|
||||
default: false
|
||||
})
|
||||
|
||||
if (!addMetadata) {
|
||||
return {}
|
||||
}
|
||||
|
||||
// Show field suggestions if available
|
||||
if (suggestions && suggestions.length > 0) {
|
||||
console.log(colors.dim('\nAvailable fields:'))
|
||||
suggestions.forEach(field => {
|
||||
console.log(colors.dim(` • ${field}`))
|
||||
})
|
||||
}
|
||||
|
||||
const { metadata } = await prompt({
|
||||
type: 'editor',
|
||||
name: 'metadata',
|
||||
message: 'Enter metadata (JSON):',
|
||||
default: currentMetadata ? JSON.stringify(currentMetadata, null, 2) : '{\n \n}',
|
||||
postfix: '.json',
|
||||
validate: (input: string) => {
|
||||
try {
|
||||
JSON.parse(input)
|
||||
return true
|
||||
} catch (e) {
|
||||
return `Invalid JSON: ${e.message}`
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
return JSON.parse(metadata)
|
||||
}
|
||||
|
||||
/**
|
||||
* Interactive format selector
|
||||
*/
|
||||
export async function promptFormat(
|
||||
availableFormats: string[],
|
||||
defaultFormat: string
|
||||
): Promise<string> {
|
||||
console.log(colors.primary(`\n${icons.export} Select Format\n`))
|
||||
|
||||
const { format } = await prompt({
|
||||
type: 'list',
|
||||
name: 'format',
|
||||
message: 'Choose export format:',
|
||||
choices: availableFormats.map(f => ({
|
||||
name: getFormatDescription(f),
|
||||
value: f,
|
||||
short: f
|
||||
})),
|
||||
default: defaultFormat
|
||||
})
|
||||
|
||||
return format
|
||||
}
|
||||
|
||||
/**
|
||||
* Get friendly format descriptions
|
||||
*/
|
||||
function getFormatDescription(format: string): string {
|
||||
const descriptions: Record<string, string> = {
|
||||
json: 'JSON - Universal data interchange',
|
||||
jsonl: 'JSON Lines - Streaming format',
|
||||
csv: 'CSV - Spreadsheet compatible',
|
||||
graphml: 'GraphML - Graph visualization',
|
||||
dot: 'DOT - Graphviz format',
|
||||
d3: 'D3.js - Web visualization',
|
||||
markdown: 'Markdown - Human readable',
|
||||
yaml: 'YAML - Configuration format'
|
||||
}
|
||||
|
||||
return `${format.toUpperCase()} - ${descriptions[format] || 'Custom format'}`
|
||||
}
|
||||
|
||||
/**
|
||||
* Interactive file/URL input with validation
|
||||
*/
|
||||
export async function promptFileOrUrl(
|
||||
action: string = 'import'
|
||||
): Promise<string> {
|
||||
console.log(colors.primary(`\n${icons.import} ${action === 'import' ? 'Import Source' : 'Export Destination'}\n`))
|
||||
|
||||
const { sourceType } = await prompt({
|
||||
type: 'list',
|
||||
name: 'sourceType',
|
||||
message: 'What type of source?',
|
||||
choices: [
|
||||
{ name: 'Local file', value: 'file' },
|
||||
{ name: 'URL', value: 'url' },
|
||||
{ name: 'Clipboard', value: 'clipboard' },
|
||||
{ name: 'Direct input', value: 'input' }
|
||||
]
|
||||
})
|
||||
|
||||
switch (sourceType) {
|
||||
case 'file':
|
||||
return promptFilePath(action)
|
||||
case 'url':
|
||||
return promptUrl()
|
||||
case 'clipboard':
|
||||
// Would need clipboard integration
|
||||
console.log(colors.warning('Clipboard support coming soon!'))
|
||||
return promptFilePath(action)
|
||||
case 'input':
|
||||
const data = await promptDataInput('import')
|
||||
// Save to temp file and return path
|
||||
const tmpFile = `/tmp/brainy-import-${Date.now()}.json`
|
||||
const { writeFileSync } = await import('node:fs')
|
||||
writeFileSync(tmpFile, data)
|
||||
return tmpFile
|
||||
default:
|
||||
return ''
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* File path input with autocomplete
|
||||
*/
|
||||
async function promptFilePath(action: string): Promise<string> {
|
||||
const { path } = await prompt({
|
||||
type: 'input',
|
||||
name: 'path',
|
||||
message: `Enter file path to ${action}:`,
|
||||
validate: async (input: string) => {
|
||||
if (!input.trim()) {
|
||||
return 'Please enter a file path'
|
||||
}
|
||||
|
||||
const { existsSync } = await import('node:fs')
|
||||
if (action === 'import' && !existsSync(input)) {
|
||||
return `File not found: ${input}`
|
||||
}
|
||||
|
||||
return true
|
||||
},
|
||||
// Add file path autocomplete
|
||||
transformer: (input: string) => {
|
||||
if (input.startsWith('~/')) {
|
||||
const home = process.env.HOME || '~'
|
||||
return colors.green(input.replace('~', home))
|
||||
}
|
||||
return colors.green(input)
|
||||
}
|
||||
})
|
||||
|
||||
return path
|
||||
}
|
||||
|
||||
/**
|
||||
* URL input with validation
|
||||
*/
|
||||
async function promptUrl(): Promise<string> {
|
||||
const { url } = await prompt({
|
||||
type: 'input',
|
||||
name: 'url',
|
||||
message: 'Enter URL:',
|
||||
validate: (input: string) => {
|
||||
try {
|
||||
new URL(input)
|
||||
return true
|
||||
} catch {
|
||||
return 'Please enter a valid URL'
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
return url
|
||||
}
|
||||
|
||||
/**
|
||||
* Interactive relationship builder
|
||||
*/
|
||||
export async function promptRelationship(brain?: Brainy): Promise<{
|
||||
source: string
|
||||
verb: string
|
||||
target: string
|
||||
metadata?: any
|
||||
}> {
|
||||
console.log(colors.primary(`\n${icons.connect} Create Relationship\n`))
|
||||
console.log(colors.dim('Connect two items with a semantic relationship'))
|
||||
|
||||
// Get source
|
||||
const source = await promptItemId('connect from', brain, false) as string
|
||||
|
||||
// Get verb/relationship type
|
||||
const { verb } = await prompt({
|
||||
type: 'list',
|
||||
name: 'verb',
|
||||
message: 'Relationship type:',
|
||||
choices: [
|
||||
{ name: 'Works For', value: 'WorksFor' },
|
||||
{ name: 'Knows', value: 'Knows' },
|
||||
{ name: 'Created By', value: 'CreatedBy' },
|
||||
{ name: 'Belongs To', value: 'BelongsTo' },
|
||||
{ name: 'Uses', value: 'Uses' },
|
||||
{ name: 'Manages', value: 'Manages' },
|
||||
{ name: 'Located In', value: 'LocatedIn' },
|
||||
{ name: 'Related To', value: 'RelatedTo' },
|
||||
new inquirer.Separator(),
|
||||
{ name: 'Custom relationship...', value: '__custom__' }
|
||||
]
|
||||
})
|
||||
|
||||
let finalVerb = verb
|
||||
if (verb === '__custom__') {
|
||||
const { customVerb } = await prompt({
|
||||
type: 'input',
|
||||
name: 'customVerb',
|
||||
message: 'Enter custom relationship:',
|
||||
validate: (input: string) => input.trim() ? true : 'Please enter a relationship'
|
||||
})
|
||||
finalVerb = customVerb
|
||||
}
|
||||
|
||||
// Get target
|
||||
const target = await promptItemId('connect to', brain, false) as string
|
||||
|
||||
// Optional metadata
|
||||
const metadata = await promptMetadata()
|
||||
|
||||
return {
|
||||
source,
|
||||
verb: finalVerb,
|
||||
target,
|
||||
metadata: Object.keys(metadata).length > 0 ? metadata : undefined
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Smart command suggestions when user types wrong command
|
||||
*/
|
||||
export function suggestCommand(input: string, availableCommands: string[]): string[] {
|
||||
// Simple fuzzy matching without external dependency
|
||||
// Filter commands that start with or contain the input
|
||||
const matches = availableCommands
|
||||
.filter(cmd => cmd.toLowerCase().includes(input.toLowerCase()))
|
||||
.sort((a, b) => {
|
||||
// Prefer commands that start with the input
|
||||
const aStarts = a.toLowerCase().startsWith(input.toLowerCase())
|
||||
const bStarts = b.toLowerCase().startsWith(input.toLowerCase())
|
||||
if (aStarts && !bStarts) return -1
|
||||
if (!aStarts && bStarts) return 1
|
||||
return 0
|
||||
})
|
||||
return matches.slice(0, 3)
|
||||
}
|
||||
|
||||
/**
|
||||
* Beautiful error display with helpful context
|
||||
*/
|
||||
export function showError(error: Error, context?: string): void {
|
||||
console.log()
|
||||
console.log(colors.error(`${icons.error} Error`))
|
||||
|
||||
if (context) {
|
||||
console.log(colors.dim(context))
|
||||
}
|
||||
|
||||
console.log(colors.red(error.message))
|
||||
|
||||
// Provide helpful suggestions based on error
|
||||
if (error.message.includes('not found')) {
|
||||
console.log(colors.dim('\nTip: Use "brainy search" to find items'))
|
||||
} else if (error.message.includes('network') || error.message.includes('fetch')) {
|
||||
console.log(colors.dim('\nTip: Check your internet connection'))
|
||||
} else if (error.message.includes('permission')) {
|
||||
console.log(colors.dim('\nTip: Check file permissions or run with appropriate access'))
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Progress indicator for long operations
|
||||
*/
|
||||
export class ProgressTracker {
|
||||
private spinner: any
|
||||
private startTime: number
|
||||
|
||||
constructor(message: string) {
|
||||
this.spinner = ora({
|
||||
text: message,
|
||||
color: 'cyan',
|
||||
spinner: 'dots'
|
||||
}).start()
|
||||
this.startTime = Date.now()
|
||||
}
|
||||
|
||||
update(message: string, count?: number, total?: number): void {
|
||||
if (count && total) {
|
||||
const percent = Math.round((count / total) * 100)
|
||||
const elapsed = ((Date.now() - this.startTime) / 1000).toFixed(1)
|
||||
this.spinner.text = `${message} (${percent}% - ${elapsed}s)`
|
||||
} else {
|
||||
this.spinner.text = message
|
||||
}
|
||||
}
|
||||
|
||||
succeed(message?: string): void {
|
||||
const elapsed = ((Date.now() - this.startTime) / 1000).toFixed(1)
|
||||
this.spinner.succeed(message ? `${message} (${elapsed}s)` : `Done (${elapsed}s)`)
|
||||
}
|
||||
|
||||
fail(message?: string): void {
|
||||
this.spinner.fail(message || 'Failed')
|
||||
}
|
||||
|
||||
stop(): void {
|
||||
this.spinner.stop()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Welcome message for interactive mode
|
||||
*/
|
||||
export function showWelcome(): void {
|
||||
console.clear()
|
||||
console.log(colors.primary(`
|
||||
╔══════════════════════════════════════════════╗
|
||||
║ ║
|
||||
║ ${icons.brain} BRAINY - Neural Intelligence ║
|
||||
║ Your AI-Powered Second Brain ║
|
||||
║ ║
|
||||
╚══════════════════════════════════════════════╝
|
||||
`))
|
||||
console.log(colors.dim(`Version ${getBrainyVersion()} • Type "help" for commands`))
|
||||
console.log()
|
||||
}
|
||||
|
||||
/**
|
||||
* Interactive command selector for beginners
|
||||
*/
|
||||
export async function promptCommand(): Promise<string> {
|
||||
const { command } = await prompt({
|
||||
type: 'list',
|
||||
name: 'command',
|
||||
message: 'What would you like to do?',
|
||||
choices: [
|
||||
{ name: `${icons.add} Add data to your brain`, value: 'add' },
|
||||
{ name: `${icons.search} Search your knowledge`, value: 'search' },
|
||||
{ name: `${icons.chat} Chat with your data`, value: 'chat' },
|
||||
{ name: `${icons.update} Update existing data`, value: 'update' },
|
||||
{ name: `${icons.delete} Delete data`, value: 'delete' },
|
||||
{ name: `${icons.connect} Create relationships`, value: 'relate' },
|
||||
{ name: `${icons.import} Import from file`, value: 'import' },
|
||||
{ name: `${icons.export} Export your brain`, value: 'export' },
|
||||
new inquirer.Separator(),
|
||||
{ name: `${icons.brain} Neural operations`, value: 'neural' },
|
||||
{ name: `${icons.info} View statistics`, value: 'status' },
|
||||
{ name: 'Exit', value: 'exit' }
|
||||
],
|
||||
pageSize: 15
|
||||
})
|
||||
|
||||
return command
|
||||
}
|
||||
|
||||
/**
|
||||
* Start interactive REPL mode
|
||||
*/
|
||||
export async function startInteractiveMode() {
|
||||
console.log(chalk.cyan('\n🧠 Brainy Interactive Mode\n'))
|
||||
console.log(chalk.yellow('Interactive REPL mode coming soon\n'))
|
||||
console.log(chalk.dim('Use specific commands for now: brainy add, brainy search, etc.'))
|
||||
process.exit(0)
|
||||
}
|
||||
|
||||
/**
|
||||
* Export all interactive components
|
||||
*/
|
||||
export default {
|
||||
colors,
|
||||
icons,
|
||||
prompt,
|
||||
promptSearchQuery,
|
||||
promptItemId,
|
||||
confirmDestructiveAction,
|
||||
promptDataInput,
|
||||
promptMetadata,
|
||||
promptFormat,
|
||||
promptFileOrUrl,
|
||||
promptRelationship,
|
||||
suggestCommand,
|
||||
showError,
|
||||
ProgressTracker,
|
||||
showWelcome,
|
||||
promptCommand,
|
||||
startInteractiveMode
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue