From be67c1b7532272fbec01646fcf7a9fc4fec3ff4f Mon Sep 17 00:00:00 2001 From: David Snelling Date: Thu, 5 Jun 2025 11:44:18 -0700 Subject: [PATCH] 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. --- README.md | 68 +++++++--- package-lock.json | 151 ++++++++++++--------- package.json | 5 +- src/cli.ts | 299 ++++++++++++++++++++++++++--------------- src/utils/embedding.ts | 15 +++ 5 files changed, 345 insertions(+), 193 deletions(-) diff --git a/README.md b/README.md index 2816f05c..c189ea4a 100644 --- a/README.md +++ b/README.md @@ -30,14 +30,14 @@ Brainy combines three key technologies: Brainy uses a graph-based data model with two primary concepts: 1. **Nouns**: The main entities in your data (nodes in the graph) - - Each noun has a unique ID, vector representation, and metadata - - Nouns can be categorized by type (Person, Place, Thing, Event, Concept, etc.) - - Nouns are automatically vectorized for similarity search + - Each noun has a unique ID, vector representation, and metadata + - Nouns can be categorized by type (Person, Place, Thing, Event, Concept, etc.) + - Nouns are automatically vectorized for similarity search 2. **Verbs**: Relationships between nouns (edges in the graph) - - Each verb connects a source noun to a target noun - - Verbs have types that define the relationship (RelatedTo, Controls, Contains, etc.) - - Verbs can have their own metadata to describe the relationship + - Each verb connects a source noun to a target noun + - Verbs have types that define the relationship (RelatedTo, Controls, Contains, etc.) + - Verbs can have their own metadata to describe the relationship ## What Can You Use It For? @@ -49,13 +49,27 @@ Brainy uses a graph-based data model with two primary concepts: ## Installation +Due to a dependency conflict between TensorFlow.js packages, you need to use the `--legacy-peer-deps` flag when +installing: + ```bash -npm install @soulcraft/brainy +npm install @soulcraft/brainy --legacy-peer-deps ``` +If you encounter an error like this: + +``` +npm error ERESOLVE unable to resolve dependency tree +npm error Found: @tensorflow/tfjs-core@4.22.0 +npm error Could not resolve dependency: peer @tensorflow/tfjs-core@"3.21.0" from @tensorflow/tfjs-converter@3.21.0 +``` + +Use the `--legacy-peer-deps` flag as shown above to resolve it. + ## Command Line Interface -Brainy includes a command-line interface (CLI) that allows you to experiment with the API and data directly from the terminal. +Brainy includes a command-line interface (CLI) that allows you to experiment with the API and data directly from the +terminal. ### Using the CLI during development @@ -83,8 +97,8 @@ npm run cli status ### Installing the CLI globally ```bash -# Install the package globally -npm install -g @soulcraft/brainy +# Install the package globally (with --legacy-peer-deps to resolve TensorFlow.js dependency conflicts) +npm install -g @soulcraft/brainy --legacy-peer-deps # Now you can use the 'brainy' command directly brainy help @@ -102,6 +116,28 @@ brainy add "Some text" '{"noun":"Thing"}' - `addVerb [metadata]` - Add a relationship between nouns - `getVerbs ` - Get all relationships for a noun - `status` - Show database status +- `completion-setup` - Setup shell autocomplete for the Brainy CLI + +### Autocomplete + +The CLI supports autocomplete for commands, noun types, and verb types. To enable autocomplete: + +```bash +# Set up autocomplete for your shell +brainy completion-setup + +# Restart your shell or source your shell configuration file +# For bash: +source ~/.bashrc + +# For zsh: +source ~/.zshrc +``` + +Once enabled, you can use tab completion for: +- Commands (e.g., `brainy a` completes to `brainy add`) +- Noun types in metadata (e.g., when adding nouns) +- Verb types (e.g., when adding relationships) ## Basic Usage @@ -237,8 +273,8 @@ const status = await db.status(); ```typescript // Add a noun (automatically vectorized) const id = await db.add(textOrVector, { - noun: NounType.Thing, - // other metadata... + noun: NounType.Thing, + // other metadata... }); // Retrieve a noun @@ -246,8 +282,8 @@ const noun = await db.get(id); // Update noun metadata await db.updateMetadata(id, { - noun: NounType.Thing, - // updated metadata... + noun: NounType.Thing, + // updated metadata... }); // Delete a noun @@ -266,8 +302,8 @@ const thingNouns = await db.searchByNounTypes([NounType.Thing], numResults); ```typescript // Add a relationship between nouns await db.addVerb(sourceId, targetId, { - verb: VerbType.RelatedTo, - // other metadata... + verb: VerbType.RelatedTo, + // other metadata... }); // Get all relationships diff --git a/package-lock.json b/package-lock.json index 69084a32..e7f006be 100644 --- a/package-lock.json +++ b/package-lock.json @@ -7,6 +7,7 @@ "": { "name": "@soulcraft/brainy", "version": "0.7.0", + "hasInstallScript": true, "license": "MIT", "dependencies": { "@aws-sdk/client-s3": "^3.427.0", @@ -16,6 +17,7 @@ "@tensorflow/tfjs-core": "^4.22.0", "@tensorflow/tfjs-layers": "^4.22.0", "commander": "^14.0.0", + "omelette": "^0.4.17", "uuid": "^9.0.0" }, "bin": { @@ -24,6 +26,7 @@ "devDependencies": { "@types/jest": "^29.5.3", "@types/node": "^20.4.5", + "@types/omelette": "^0.4.5", "@types/uuid": "^10.0.0", "@typescript-eslint/eslint-plugin": "^6.0.0", "@typescript-eslint/parser": "^6.0.0", @@ -253,9 +256,9 @@ } }, "node_modules/@aws-sdk/client-s3": { - "version": "3.823.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-s3/-/client-s3-3.823.0.tgz", - "integrity": "sha512-cTp1Lkyv5NuHr6aUin5FCARY02o2jiOcRKnWnAahEQrlvgzOAe4SrjjOVVTH67wHdcB9zGeNDNCK/132+zaRow==", + "version": "3.824.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-s3/-/client-s3-3.824.0.tgz", + "integrity": "sha512-7neTQIdSVP/F4RTWG5T87LDpB955iQD6lxg9nJ00fdkIPczDcRtAEXow44NjF4fEdpQ1A9jokUtBSVE+GMXZ/A==", "license": "Apache-2.0", "dependencies": { "@aws-crypto/sha1-browser": "5.2.0", @@ -274,7 +277,7 @@ "@aws-sdk/middleware-ssec": "3.821.0", "@aws-sdk/middleware-user-agent": "3.823.0", "@aws-sdk/region-config-resolver": "3.821.0", - "@aws-sdk/signature-v4-multi-region": "3.823.0", + "@aws-sdk/signature-v4-multi-region": "3.824.0", "@aws-sdk/types": "3.821.0", "@aws-sdk/util-endpoints": "3.821.0", "@aws-sdk/util-user-agent-browser": "3.821.0", @@ -770,9 +773,9 @@ } }, "node_modules/@aws-sdk/signature-v4-multi-region": { - "version": "3.823.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/signature-v4-multi-region/-/signature-v4-multi-region-3.823.0.tgz", - "integrity": "sha512-FAvtmR7G0ppNLa4O2yN8koFYUmUmPuL60UBIFrVb3BBeZvBIFLln69lB8EGtTBpAvVbxknudRZCzYtnOhE4QXg==", + "version": "3.824.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/signature-v4-multi-region/-/signature-v4-multi-region-3.824.0.tgz", + "integrity": "sha512-HBjuWeN6Z1pvJjUvGXdMNLwEypKKB4km6zXj9jsbOOwP8NTL6J5rY+JmlX/mfBTmvzmI0kMu2bxlQ4ME2CIRbA==", "license": "Apache-2.0", "dependencies": { "@aws-sdk/middleware-sdk-s3": "3.823.0", @@ -921,9 +924,9 @@ } }, "node_modules/@babel/compat-data": { - "version": "7.27.3", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.27.3.tgz", - "integrity": "sha512-V42wFfx1ymFte+ecf6iXghnnP8kWTO+ZLXIyZq+1LAXHHvTZdVxicn4yiVYdYMGaCO3tmqub11AorKkv+iodqw==", + "version": "7.27.5", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.27.5.tgz", + "integrity": "sha512-KiRAp/VoJaWkkte84TvUd9qjdbZAdiqyvMxrGl1N6vzFogKmaLgoM3L1kgtLicp2HP5fBJS8JrZKLVIZGVJAVg==", "dev": true, "license": "MIT", "engines": { @@ -972,13 +975,13 @@ } }, "node_modules/@babel/generator": { - "version": "7.27.3", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.27.3.tgz", - "integrity": "sha512-xnlJYj5zepml8NXtjkG0WquFUv8RskFqyFcVgTBp5k+NaA/8uw/K+OSVf8AMGw5e9HKP2ETd5xpK5MLZQD6b4Q==", + "version": "7.27.5", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.27.5.tgz", + "integrity": "sha512-ZGhA37l0e/g2s1Cnzdix0O3aLYm66eF8aufiVteOgnwxgnRP8GoyMj7VWsgWnQbVKXyge7hqrFh2K2TQM6t1Hw==", "dev": true, "license": "MIT", "dependencies": { - "@babel/parser": "^7.27.3", + "@babel/parser": "^7.27.5", "@babel/types": "^7.27.3", "@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/trace-mapping": "^0.3.25", @@ -1088,23 +1091,23 @@ } }, "node_modules/@babel/helpers": { - "version": "7.27.4", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.27.4.tgz", - "integrity": "sha512-Y+bO6U+I7ZKaM5G5rDUZiYfUvQPUibYmAFe7EnKdnKBbVXDZxvp+MWOH5gYciY0EPk4EScsuFMQBbEfpdRKSCQ==", + "version": "7.27.6", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.27.6.tgz", + "integrity": "sha512-muE8Tt8M22638HU31A3CgfSUciwz1fhATfoVai05aPXGor//CdWDCbnlY1yvBPo07njuVOCNGCSp/GTt12lIug==", "dev": true, "license": "MIT", "dependencies": { "@babel/template": "^7.27.2", - "@babel/types": "^7.27.3" + "@babel/types": "^7.27.6" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/parser": { - "version": "7.27.4", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.27.4.tgz", - "integrity": "sha512-BRmLHGwpUqLFR2jzx9orBuX/ABDkj2jLKOXrHDTN2aOKL+jFDDKaRNo9nyYsIl9h/UE/7lMKdDjKQQyxKKDZ7g==", + "version": "7.27.5", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.27.5.tgz", + "integrity": "sha512-OsQd175SxWkGlzbny8J3K8TnnDD0N3lrIUtB92xwyRpzaenGZhxDvxN/JgU00U3CDZNj9tPuDJ5H0WS4Nt3vKg==", "dev": true, "license": "MIT", "dependencies": { @@ -1401,9 +1404,9 @@ } }, "node_modules/@babel/types": { - "version": "7.27.3", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.27.3.tgz", - "integrity": "sha512-Y1GkI4ktrtvmawoSq+4FCVHNryea6uR+qUQy0AGxLSsjCX0nVmkYQMBLHDkXZuo5hGx7eYdnIaslsdBFm7zbUw==", + "version": "7.27.6", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.27.6.tgz", + "integrity": "sha512-ETyHEk2VHHvl9b9jZP5IHPavHYk57EhanlRRuae9XCpb/j5bDCbPPMOBfCWhnl/7EDJz0jEMCi/RhccCE8r1+Q==", "dev": true, "license": "MIT", "dependencies": { @@ -2142,9 +2145,9 @@ } }, "node_modules/@smithy/core": { - "version": "3.5.1", - "resolved": "https://registry.npmjs.org/@smithy/core/-/core-3.5.1.tgz", - "integrity": "sha512-xSw7bZEFKwOKrm/iv8e2BLt2ur98YZdrRD6nII8ditQeUsY2Q1JmIQ0rpILOhaLKYxxG2ivnoOpokzr9qLyDWA==", + "version": "3.5.3", + "resolved": "https://registry.npmjs.org/@smithy/core/-/core-3.5.3.tgz", + "integrity": "sha512-xa5byV9fEguZNofCclv6v9ra0FYh5FATQW/da7FQUVTic94DfrN/NvmKZjrMyzbpqfot9ZjBaO8U1UeTbmSLuA==", "license": "Apache-2.0", "dependencies": { "@smithy/middleware-serde": "^4.0.8", @@ -2361,12 +2364,12 @@ } }, "node_modules/@smithy/middleware-endpoint": { - "version": "4.1.9", - "resolved": "https://registry.npmjs.org/@smithy/middleware-endpoint/-/middleware-endpoint-4.1.9.tgz", - "integrity": "sha512-AjDgX4UjORLltD/LZCBQTwjQqEfyrx/GeDTHcYLzIgf87pIT70tMWnN87NQpJru1K4ITirY2htSOxNECZJCBOg==", + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/@smithy/middleware-endpoint/-/middleware-endpoint-4.1.11.tgz", + "integrity": "sha512-zDogwtRLzKl58lVS8wPcARevFZNBOOqnmzWWxVe9XiaXU2CADFjvJ9XfNibgkOWs08sxLuSr81NrpY4mgp9OwQ==", "license": "Apache-2.0", "dependencies": { - "@smithy/core": "^3.5.1", + "@smithy/core": "^3.5.3", "@smithy/middleware-serde": "^4.0.8", "@smithy/node-config-provider": "^4.1.3", "@smithy/shared-ini-file-loader": "^4.0.4", @@ -2380,15 +2383,15 @@ } }, "node_modules/@smithy/middleware-retry": { - "version": "4.1.10", - "resolved": "https://registry.npmjs.org/@smithy/middleware-retry/-/middleware-retry-4.1.10.tgz", - "integrity": "sha512-RyhcA3sZIIvAo6r48b2Nx2qfg0OnyohlaV0fw415xrQyx5HQ2bvHl9vs/WBiDXIP49mCfws5wX4308c9Pi/isw==", + "version": "4.1.12", + "resolved": "https://registry.npmjs.org/@smithy/middleware-retry/-/middleware-retry-4.1.12.tgz", + "integrity": "sha512-wvIH70c4e91NtRxdaLZF+mbLZ/HcC6yg7ySKUiufL6ESp6zJUSnJucZ309AvG9nqCFHSRB5I6T3Ez1Q9wCh0Ww==", "license": "Apache-2.0", "dependencies": { "@smithy/node-config-provider": "^4.1.3", "@smithy/protocol-http": "^5.1.2", "@smithy/service-error-classification": "^4.0.5", - "@smithy/smithy-client": "^4.4.1", + "@smithy/smithy-client": "^4.4.3", "@smithy/types": "^4.3.1", "@smithy/util-middleware": "^4.0.4", "@smithy/util-retry": "^4.0.5", @@ -2555,13 +2558,13 @@ } }, "node_modules/@smithy/smithy-client": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/@smithy/smithy-client/-/smithy-client-4.4.1.tgz", - "integrity": "sha512-XPbcHRfd0iwx8dY5XCBCGyI7uweMW0oezYezxXcG8ANgvZ5YPuC6Ylh+n0bTHpdU3SCMZOnhzgVklYz+p3fIhw==", + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/@smithy/smithy-client/-/smithy-client-4.4.3.tgz", + "integrity": "sha512-xxzNYgA0HD6ETCe5QJubsxP0hQH3QK3kbpJz3QrosBCuIWyEXLR/CO5hFb2OeawEKUxMNhz3a1nuJNN2np2RMA==", "license": "Apache-2.0", "dependencies": { - "@smithy/core": "^3.5.1", - "@smithy/middleware-endpoint": "^4.1.9", + "@smithy/core": "^3.5.3", + "@smithy/middleware-endpoint": "^4.1.11", "@smithy/middleware-stack": "^4.0.4", "@smithy/protocol-http": "^5.1.2", "@smithy/types": "^4.3.1", @@ -2662,13 +2665,13 @@ } }, "node_modules/@smithy/util-defaults-mode-browser": { - "version": "4.0.17", - "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-browser/-/util-defaults-mode-browser-4.0.17.tgz", - "integrity": "sha512-HXq5181qnXmIwB7VrwqwP8rsJybHMoYuJnNoXy4PROs2pfSI4sWDMASF2i+7Lo+u64Y6xowhegcdxczowgJtZg==", + "version": "4.0.19", + "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-browser/-/util-defaults-mode-browser-4.0.19.tgz", + "integrity": "sha512-mvLMh87xSmQrV5XqnUYEPoiFFeEGYeAKIDDKdhE2ahqitm8OHM3aSvhqL6rrK6wm1brIk90JhxDf5lf2hbrLbQ==", "license": "Apache-2.0", "dependencies": { "@smithy/property-provider": "^4.0.4", - "@smithy/smithy-client": "^4.4.1", + "@smithy/smithy-client": "^4.4.3", "@smithy/types": "^4.3.1", "bowser": "^2.11.0", "tslib": "^2.6.2" @@ -2678,16 +2681,16 @@ } }, "node_modules/@smithy/util-defaults-mode-node": { - "version": "4.0.17", - "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-node/-/util-defaults-mode-node-4.0.17.tgz", - "integrity": "sha512-RfU2A5LjFhEHw4Nwl1GZNitK4AUWu5jGtigAUDoQtfDUvYHpQxcuLw2QGAdKDtKRflIiHSZ8wXBDR36H9R2Ang==", + "version": "4.0.19", + "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-node/-/util-defaults-mode-node-4.0.19.tgz", + "integrity": "sha512-8tYnx+LUfj6m+zkUUIrIQJxPM1xVxfRBvoGHua7R/i6qAxOMjqR6CpEpDwKoIs1o0+hOjGvkKE23CafKL0vJ9w==", "license": "Apache-2.0", "dependencies": { "@smithy/config-resolver": "^4.1.4", "@smithy/credential-provider-imds": "^4.0.6", "@smithy/node-config-provider": "^4.1.3", "@smithy/property-provider": "^4.0.4", - "@smithy/smithy-client": "^4.4.1", + "@smithy/smithy-client": "^4.4.3", "@smithy/types": "^4.3.1", "tslib": "^2.6.2" }, @@ -2872,6 +2875,15 @@ "@tensorflow/tfjs-core": "4.22.0" } }, + "node_modules/@tensorflow/tfjs-converter": { + "version": "4.22.0", + "resolved": "https://registry.npmjs.org/@tensorflow/tfjs-converter/-/tfjs-converter-4.22.0.tgz", + "integrity": "sha512-PT43MGlnzIo+YfbsjM79Lxk9lOq6uUwZuCc8rrp0hfpLjF6Jv8jS84u2jFb+WpUeuF4K33ZDNx8CjiYrGQ2trQ==", + "license": "Apache-2.0", + "peerDependencies": { + "@tensorflow/tfjs-core": "4.22.0" + } + }, "node_modules/@tensorflow/tfjs-core": { "version": "4.22.0", "resolved": "https://registry.npmjs.org/@tensorflow/tfjs-core/-/tfjs-core-4.22.0.tgz", @@ -2920,15 +2932,6 @@ "@tensorflow/tfjs-core": "4.22.0" } }, - "node_modules/@tensorflow/tfjs/node_modules/@tensorflow/tfjs-converter": { - "version": "4.22.0", - "resolved": "https://registry.npmjs.org/@tensorflow/tfjs-converter/-/tfjs-converter-4.22.0.tgz", - "integrity": "sha512-PT43MGlnzIo+YfbsjM79Lxk9lOq6uUwZuCc8rrp0hfpLjF6Jv8jS84u2jFb+WpUeuF4K33ZDNx8CjiYrGQ2trQ==", - "license": "Apache-2.0", - "peerDependencies": { - "@tensorflow/tfjs-core": "4.22.0" - } - }, "node_modules/@types/babel__core": { "version": "7.20.5", "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", @@ -3060,6 +3063,13 @@ "integrity": "sha512-esIJx9bQg+QYF0ra8GnvfianIY8qWB0GBx54PK5Eps6m+xTj86KLavHv6qDhzKcu5UUOgNfJ2pWaIIV7TRUd9Q==", "license": "MIT" }, + "node_modules/@types/omelette": { + "version": "0.4.5", + "resolved": "https://registry.npmjs.org/@types/omelette/-/omelette-0.4.5.tgz", + "integrity": "sha512-zUCJpVRwfMcZfkxSCGp73mgd3/xesvPz5tQJIORlfP/zkYEyp9KUfF7IP3RRjyZR3DwxkPs96/IFf70GmYZYHQ==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/seedrandom": { "version": "2.4.34", "resolved": "https://registry.npmjs.org/@types/seedrandom/-/seedrandom-2.4.34.tgz", @@ -3713,9 +3723,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001720", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001720.tgz", - "integrity": "sha512-Ec/2yV2nNPwb4DnTANEV99ZWwm3ZWfdlfkQbWSDDt+PsXEVYwlhPH8tdMaPunYTKKmz7AnHi2oNEi1GcmKCD8g==", + "version": "1.0.30001721", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001721.tgz", + "integrity": "sha512-cOuvmUVtKrtEaoKiO0rSc29jcjwMwX5tOHDy4MgVFEWiUXj4uBMJkwI8MDySkgXidpMiHUcviogAvFi4pA2hDQ==", "dev": true, "funding": [ { @@ -4048,9 +4058,9 @@ } }, "node_modules/electron-to-chromium": { - "version": "1.5.162", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.162.tgz", - "integrity": "sha512-hQA+Zb5QQwoSaXJWEAGEw1zhk//O7qDzib05Z4qTqZfNju/FAkrm5ZInp0JbTp4Z18A6bilopdZWEYrFSsfllA==", + "version": "1.5.165", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.165.tgz", + "integrity": "sha512-naiMx1Z6Nb2TxPU6fiFrUrDTjyPMLdTtaOd2oLmG8zVSg2hCWGkhPyxwk+qRmZ1ytwVqUv0u7ZcDA5+ALhaUtw==", "dev": true, "license": "ISC" }, @@ -4571,14 +4581,15 @@ "license": "ISC" }, "node_modules/form-data": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.2.tgz", - "integrity": "sha512-hGfm/slu0ZabnNt4oaRZ6uREyfCj6P4fT/n6A1rGV+Z0VdGXjfOhVUpkn6qVQONHGIFwmveGXyDs75+nr6FM8w==", + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.3.tgz", + "integrity": "sha512-qsITQPfmvMOSAdeyZ+12I1c+CKSstAFAwu+97zrnWAbIr5u8wfsExUzCesVLC8NgHuRUqNN4Zy6UPWUTRGslcA==", "license": "MIT", "dependencies": { "asynckit": "^0.4.0", "combined-stream": "^1.0.8", "es-set-tostringtag": "^2.1.0", + "hasown": "^2.0.2", "mime-types": "^2.1.12" }, "engines": { @@ -6163,6 +6174,14 @@ "node": ">=8" } }, + "node_modules/omelette": { + "version": "0.4.17", + "resolved": "https://registry.npmjs.org/omelette/-/omelette-0.4.17.tgz", + "integrity": "sha512-UlU69G6Bhu0XFjw3tjFZ0qyiMUjAOR+rdzblA1nLQ8xiqFtxOVlkhM39BlgTpLFx9fxkm6rnxNNRsS5GxE/yww==", + "engines": { + "node": ">=0.8.0" + } + }, "node_modules/once": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", diff --git a/package.json b/package.json index c93eac23..511343e9 100644 --- a/package.json +++ b/package.json @@ -32,7 +32,8 @@ "version:patch": "npm version patch", "version:minor": "npm version minor", "version:major": "npm version major", - "deploy": "npm run build && npm publish" + "deploy": "npm run build && npm publish", + "postinstall": "echo 'Note: If you encounter dependency conflicts with TensorFlow.js packages, please use: npm install --legacy-peer-deps'" }, "bin": { "brainy": "dist/cli.js" @@ -55,6 +56,7 @@ "devDependencies": { "@types/jest": "^29.5.3", "@types/node": "^20.4.5", + "@types/omelette": "^0.4.5", "@types/uuid": "^10.0.0", "@typescript-eslint/eslint-plugin": "^6.0.0", "@typescript-eslint/parser": "^6.0.0", @@ -71,6 +73,7 @@ "@tensorflow/tfjs-core": "^4.22.0", "@tensorflow/tfjs-layers": "^4.22.0", "commander": "^14.0.0", + "omelette": "^0.4.17", "uuid": "^9.0.0" }, "prettier": { diff --git a/src/cli.ts b/src/cli.ts index 4f677621..26dacfef 100644 --- a/src/cli.ts +++ b/src/cli.ts @@ -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 ', '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 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 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 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 + '': { + '': { + _: () => { + // 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() diff --git a/src/utils/embedding.ts b/src/utils/embedding.ts index 7445b121..37cb8c63 100644 --- a/src/utils/embedding.ts +++ b/src/utils/embedding.ts @@ -89,6 +89,18 @@ export class UniversalSentenceEncoder implements EmbeddingModel { */ public async init(): Promise { 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(