**feat(cli, scripts): introduce CLI package and automated publishing process**

- Added a new `@soulcraft/brainy-cli` package to provide a dedicated CLI interface for the Brainy database.
- Implemented a CLI wrapper script (`cli-wrapper.js`) to manage global package execution and version handling.
- Introduced `scripts/publish-cli.js` to automate synchronized publishing of the main package and CLI, ensuring version consistency.
- Created documentation for CLI usage and publishing workflow (`publishing-cli.md`).
- Updated `package.json` to include CLI-related build and deploy scripts.
- Adjusted `README.md` to document the CLI package, usage instructions, and build separation.

This feature introduces a standalone CLI package, improves deployment automation, and enhances documentation to align with the updated architecture.
This commit is contained in:
David Snelling 2025-07-02 11:32:06 -07:00
parent 97839c6ada
commit 7e3e6654a9
11 changed files with 417 additions and 22 deletions

4
.gitignore vendored
View file

@ -46,3 +46,7 @@ Thumbs.db
# Test files
/test-worker.js
/test-node24-worker.js
# Generated files
/encoded-image.html
/encoded-image.txt

View file

@ -6,7 +6,7 @@
[![Node.js](https://img.shields.io/badge/node-%3E%3D24.0.0-brightgreen.svg)](https://nodejs.org/)
[![TypeScript](https://img.shields.io/badge/TypeScript-5.1.6-blue.svg)](https://www.typescriptlang.org/)
[![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg)](CONTRIBUTING.md)
[![npm](https://img.shields.io/badge/npm-v0.9.15-blue.svg)](https://www.npmjs.com/package/@soulcraft/brainy)
[![npm](https://img.shields.io/badge/npm-v0.9.16-blue.svg)](https://www.npmjs.com/package/@soulcraft/brainy)
[//]: # ([![Cartographer](https://img.shields.io/badge/Cartographer-Official%20Standard-brightgreen)](https://github.com/sodal-project/cartographer))
@ -110,8 +110,13 @@ import { BrainyData } from '@soulcraft/brainy'
// Minified version for production
import { BrainyData } from '@soulcraft/brainy/min'
// CLI functionality (only imported when needed)
import '@soulcraft/brainy/cli'
```
> **Note**: The CLI functionality (4MB) is not included in the standard import, reducing the bundle size for browser and Node.js applications. The CLI is only built and loaded when explicitly imported or when using the command-line interface.
### Browser Usage
```html
@ -285,23 +290,30 @@ Brainy uses a modern build system that optimizes for both Node.js and browser en
2. **Environment-Specific Builds**
- **Node.js Build**: Optimized for server environments with full functionality
- **Browser Build**: Optimized for browser environments with reduced bundle size
- **CLI Build**: Separate build for command-line interface functionality
- Conditional exports in package.json for automatic environment detection
3. **Environment Detection**
3. **Modular Architecture**
- Core functionality and CLI are built separately
- CLI (4MB) is only included when explicitly imported or used from command line
- Reduced bundle size for browser and Node.js applications
4. **Environment Detection**
- Automatically detects whether it's running in a browser or Node.js
- Loads appropriate dependencies and functionality based on the environment
- Provides consistent API across all environments
4. **TypeScript**
5. **TypeScript**
- Written in TypeScript for type safety and better developer experience
- Generates type definitions for TypeScript users
- Compiled to ES2020 for modern JavaScript environments
5. **Build Scripts**
- `npm run build`: Builds the Node.js version
6. **Build Scripts**
- `npm run build`: Builds the core library without CLI
- `npm run build:browser`: Builds the browser-optimized version
- `npm run build:cli`: Builds the CLI version
- `npm run demo`: Builds both Node.js and browser versions and starts a demo server
- `npm run build:cli`: Builds the CLI version (only needed for CLI usage)
- `npm run prepare:cli`: Builds the CLI for command-line usage
- `npm run demo`: Builds both core library and browser versions and starts a demo server
- GitHub Actions workflow: Automatically deploys the demo directory to GitHub Pages when pushing to the main branch
### Running the Pipeline
@ -412,11 +424,13 @@ Connections between nouns (edges in the graph):
## Command Line Interface
Brainy includes a powerful CLI for managing your data:
Brainy includes a powerful CLI for managing your data. The CLI is available as a separate package `@soulcraft/brainy-cli` to reduce the bundle size of the main package.
### Installing and Using the CLI
```bash
# Install globally
npm install -g @soulcraft/brainy --legacy-peer-deps
# Install the CLI globally
npm install -g @soulcraft/brainy-cli
# Initialize a database
brainy init
@ -436,6 +450,24 @@ brainy visualize
brainy visualize --root <id> --depth 3
```
### Publishing the CLI Package
If you need to publish the CLI package to npm, please refer to the [CLI Publishing Guide](docs/publishing-cli.md) for detailed instructions.
### Using the CLI in Your Code
If you need to use the CLI functionality in your code, you can import it explicitly:
```typescript
// Import the CLI functionality
import '@soulcraft/brainy/cli'
// Now you can use the CLI programmatically
// ...
```
This will only build and load the CLI when you explicitly import it, keeping your bundle size small when you don't need the CLI.
### Development Usage
```bash

54
cli-package/README.md Normal file
View file

@ -0,0 +1,54 @@
# @soulcraft/brainy-cli
Command-line interface for the [Brainy vector graph database](https://github.com/soulcraft-research/brainy).
## Installation
```bash
# Install globally
npm install -g @soulcraft/brainy-cli
```
## Usage
Once installed, you can use the `brainy` command from anywhere:
```bash
# Show help
brainy --help
# Initialize a new database
brainy init
# Add data
brainy add "Cats are independent pets" '{"noun":"Thing","category":"animal"}'
# Search
brainy search "feline pets" --limit 5
# Add relationships
brainy addVerb id1 id2 RelatedTo '{"description":"Both are pets"}'
# Visualize the graph
brainy visualize
brainy visualize --root <id> --depth 3
# Generate random test data
brainy generate-random-graph --noun-count 20 --verb-count 30 --clear
```
## Features
- Full access to all Brainy database functionality from the command line
- Autocomplete support for commands and options
- Visualization of graph data
- Import/export capabilities
- Augmentation pipeline testing
## Requirements
- Node.js >= 24.0.0
## License
MIT

View file

@ -0,0 +1,62 @@
#!/usr/bin/env node
/**
* CLI Wrapper Script for @soulcraft/brainy-cli
*
* This script serves as a wrapper for the Brainy CLI, ensuring that command-line arguments
* are properly passed to the CLI when invoked through the globally installed package.
*/
import { spawn } from 'child_process'
import { fileURLToPath } from 'url'
import { dirname, join } from 'path'
import fs from 'fs'
// Get the directory of the current module
const __filename = fileURLToPath(import.meta.url)
const __dirname = dirname(__filename)
// Find the main package
const mainPackagePath = join(__dirname, 'node_modules', '@soulcraft', 'brainy')
// Path to the actual CLI script in the main package
const cliPath = join(mainPackagePath, 'dist', 'cli.js')
// Check if the CLI script exists
if (!fs.existsSync(cliPath)) {
console.error(`Error: CLI script not found at ${cliPath}`)
console.error('This is likely because the main package is not installed correctly.')
console.error('Please reinstall the package with:')
console.error('npm uninstall -g @soulcraft/brainy-cli')
console.error('npm install -g @soulcraft/brainy-cli --legacy-peer-deps')
process.exit(1)
}
// Special handling for version flags
if (process.argv.includes('--version') || process.argv.includes('-V')) {
// Read version directly from package.json to ensure it's always correct
try {
const packageJsonPath = join(__dirname, 'package.json')
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'))
console.log(packageJson.version)
process.exit(0)
} catch (error) {
console.error('Error loading version information:', error.message)
process.exit(1)
}
}
// Forward all arguments to the CLI script
const args = process.argv.slice(2)
// Check if npm is passing --force flag
// When npm runs with --force, it sets the npm_config_force environment variable
if (process.env.npm_config_force === 'true' && args.includes('clear') && !args.includes('--force') && !args.includes('-f')) {
args.push('--force')
}
const cli = spawn('node', [cliPath, ...args], { stdio: 'inherit' })
cli.on('close', (code) => {
process.exit(code)
})

43
cli-package/package.json Normal file
View file

@ -0,0 +1,43 @@
{
"name": "@soulcraft/brainy-cli",
"version": "0.9.16",
"description": "Command-line interface for the Brainy vector graph database",
"type": "module",
"bin": {
"brainy": "cli-wrapper.js"
},
"files": [
"cli-wrapper.js",
"README.md"
],
"scripts": {
"postinstall": "node cli-wrapper.js --version"
},
"keywords": [
"vector-database",
"hnsw",
"cli",
"command-line",
"graph-database"
],
"author": "David Snelling (david@soulcraft.com)",
"license": "MIT",
"private": false,
"publishConfig": {
"access": "public"
},
"homepage": "https://github.com/soulcraft-research/brainy",
"bugs": {
"url": "https://github.com/soulcraft-research/brainy/issues"
},
"repository": {
"type": "git",
"url": "https://github.com/soulcraft-research/brainy.git"
},
"dependencies": {
"@soulcraft/brainy": "0.9.16"
},
"engines": {
"node": ">=24.0.0"
}
}

View file

@ -7,7 +7,7 @@
* are properly passed to the CLI when invoked through npm scripts.
*/
import { spawn } from 'child_process'
import { spawn, execSync } from 'child_process'
import { fileURLToPath } from 'url'
import { dirname, join } from 'path'
import fs from 'fs'
@ -21,9 +21,37 @@ const cliPath = join(__dirname, 'dist', 'cli.js')
// Check if the CLI script exists
if (!fs.existsSync(cliPath)) {
console.error(`Error: CLI script not found at ${cliPath}`)
console.error('Make sure you have built the project with "npm run build"')
process.exit(1)
// Check if we're running in a global installation context
const isGlobalInstall = __dirname.includes('node_modules') && !__dirname.includes('node_modules/.')
if (isGlobalInstall) {
console.error(`Error: CLI script not found at ${cliPath}`)
console.error('This is likely because the CLI was not built during package installation.')
console.error('Please reinstall the package with:')
console.error('npm uninstall -g @soulcraft/brainy')
console.error('npm install -g @soulcraft/brainy --legacy-peer-deps')
process.exit(1)
} else {
// In a local development context, try to build the CLI
console.log(`CLI script not found at ${cliPath}. Building CLI...`)
try {
// Run the build:cli script
execSync('npm run build:cli', { stdio: 'inherit' })
// Check again if the CLI script exists after building
if (!fs.existsSync(cliPath)) {
console.error(`Error: Failed to build CLI script at ${cliPath}`)
process.exit(1)
}
console.log('CLI built successfully.')
} catch (error) {
console.error(`Error building CLI: ${error.message}`)
console.error('Make sure you have the necessary dependencies installed.')
process.exit(1)
}
}
}
// Special handling for version flags

84
docs/publishing-cli.md Normal file
View file

@ -0,0 +1,84 @@
# Publishing @soulcraft/brainy and @soulcraft/brainy-cli to npm
This document explains how to publish both the @soulcraft/brainy and @soulcraft/brainy-cli packages to npm.
## Prerequisites
Before publishing, ensure you have:
1. Node.js >= 24.0.0 installed
2. An npm account with access to the @soulcraft organization
3. Logged in to npm using `npm login`
## Publishing Process
The repository is set up to publish both packages together with synchronized versions. To publish the packages, follow these steps:
### 1. Ensure you're in the root directory of the project
```bash
cd /path/to/brainy
```
### 2. Make sure you have the latest version of the code
```bash
git pull
```
### 3. Build and publish both packages
```bash
npm run deploy:both
```
This command will:
- Ensure versions are in sync between both packages
- Build the main package
- Build the CLI
- Verify the CLI was built successfully
- Publish the main package to npm
- Publish the CLI package to npm
### 4. Verify the packages were published successfully
After publishing, you can verify that the packages were published successfully by checking the npm registry:
```bash
npm view @soulcraft/brainy
npm view @soulcraft/brainy-cli
```
## How It Works
The publishing process uses the `scripts/publish-cli.js` script, which:
1. Ensures versions are in sync by running `scripts/generate-version.js`
2. Builds the main package with `npm run build`
3. Builds the CLI with `npm run build:cli`
4. Verifies the CLI was built successfully
5. Publishes the main package with `npm publish` from the root directory
6. Publishes the CLI package with `npm publish` from the cli-package directory
The version synchronization ensures that:
- Both packages always have the same version number
- The CLI package's dependency on the main package is exact (not using the ^ prefix)
- The README.md file is updated with the current version
The CLI package is configured in `cli-package/package.json` with:
- The correct package name: `@soulcraft/brainy-cli`
- `"private": false` to allow publishing
- `"publishConfig": { "access": "public" }` to ensure the scoped package is public
- The necessary files in the `"files"` array
- The correct bin configuration to make the CLI available as `brainy`
## Troubleshooting
If you encounter any issues during the publishing process:
1. Make sure you're logged in to npm with an account that has access to the @soulcraft organization
2. Ensure that the `dist/cli.js` file exists and has been built correctly
3. If you get an error about the package already existing, you may need to update the version in both package.json files:
```bash
npm version patch # This will update both package.json files via the version script
```

View file

@ -22,6 +22,9 @@
"./types/augmentations": {
"import": "./dist/types/augmentations.js",
"types": "./dist/types/augmentations.d.ts"
},
"./cli": {
"import": "./dist/cli.js"
}
},
"engines": {
@ -34,7 +37,6 @@
"build:cli": "BUILD_TYPE=cli rollup -c rollup.config.js",
"start": "node dist/unified.js",
"demo": "npm run build && npm run build:browser && npx http-server -o /index.html",
"cli": "npm run build:cli && node ./cli-wrapper.js",
"version": "node scripts/generate-version.js",
"version:patch": "npm version patch",
"version:minor": "npm version minor",
@ -44,8 +46,9 @@
"format": "prettier --write \"src/**/*.{ts,js}\"",
"check-format": "prettier --check \"src/**/*.{ts,js}\"",
"check-style": "node scripts/check-code-style.js",
"prepare": "npm run build && npm run build:cli",
"prepare": "npm run build",
"deploy": "npm run build && npm publish",
"deploy:both": "node scripts/publish-cli.js",
"deploy:cloud:aws": "cd cloud-wrapper && npm run build && npm run deploy:aws",
"deploy:cloud:gcp": "cd cloud-wrapper && npm run build && npm run deploy:gcp",
"deploy:cloud:cloudflare": "cd cloud-wrapper && npm run build && npm run deploy:cloudflare",
@ -53,9 +56,6 @@
"postinstall": "echo 'Note: If you encounter dependency conflicts with TensorFlow.js packages, please use: npm install --legacy-peer-deps'",
"dry-run": "npm pack --dry-run"
},
"bin": {
"brainy": "cli-wrapper.js"
},
"keywords": [
"vector-database",
"hnsw",
@ -85,7 +85,6 @@
"dist/types/",
"LICENSE",
"README.md",
"cli-wrapper.js",
"brainy.png"
],
"devDependencies": {

View file

@ -7,7 +7,8 @@
* This allows the CLI to access the version without having to read package.json directly,
* which can be problematic when the package is installed globally.
*
* It also updates the version in the README.md file to ensure it stays in sync with package.json.
* It also updates the version in the README.md file and CLI package.json to ensure they
* stay in sync with the main package.json.
*/
import fs from 'fs'
@ -33,6 +34,9 @@ const outputFile = path.join(outputDir, 'version.ts')
// Path to README.md
const readmePath = path.join(rootDir, 'README.md')
// Path to CLI package.json
const cliPackageJsonPath = path.join(rootDir, 'cli-package', 'package.json')
// Read package.json
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'))
const version = packageJson.version
@ -105,3 +109,23 @@ try {
} catch (error) {
console.error('Error updating README.md:', error)
}
// Update CLI package.json with the same version
try {
// Read CLI package.json
const cliPackageJson = JSON.parse(fs.readFileSync(cliPackageJsonPath, 'utf8'))
// Update version
cliPackageJson.version = version
// Update dependency on main package to be exact
if (cliPackageJson.dependencies && cliPackageJson.dependencies['@soulcraft/brainy']) {
cliPackageJson.dependencies['@soulcraft/brainy'] = version
}
// Write the updated CLI package.json back to disk
fs.writeFileSync(cliPackageJsonPath, JSON.stringify(cliPackageJson, null, 2))
console.log(`Updated CLI package.json with version ${version} and exact dependency on main package`)
} catch (error) {
console.error('Error updating CLI package.json:', error)
}

65
scripts/publish-cli.js Executable file
View file

@ -0,0 +1,65 @@
#!/usr/bin/env node
/**
* Script to build and publish both the main package and CLI package
*
* This script:
* 1. Ensures versions are in sync by running generate-version.js
* 2. Builds the main package
* 3. Builds the CLI
* 4. Publishes the main package
* 5. Publishes the CLI package
*
* This ensures both packages are always published together with the same version.
*/
import { execSync } from 'child_process'
import fs from 'fs'
import path from 'path'
import { fileURLToPath } from 'url'
// Get the directory of the current module
const __filename = fileURLToPath(import.meta.url)
const __dirname = path.dirname(__filename)
const rootDir = path.join(__dirname, '..')
const cliPackageDir = path.join(rootDir, 'cli-package')
// Ensure the CLI package directory exists
if (!fs.existsSync(cliPackageDir)) {
console.error(`Error: CLI package directory not found at ${cliPackageDir}`)
process.exit(1)
}
try {
// Step 1: Ensure versions are in sync
console.log('Ensuring versions are in sync...')
execSync('node scripts/generate-version.js', { stdio: 'inherit', cwd: rootDir })
// Step 2: Build the main package
console.log('Building main package...')
execSync('npm run build', { stdio: 'inherit', cwd: rootDir })
// Step 3: Build the CLI
console.log('Building CLI...')
execSync('npm run build:cli', { stdio: 'inherit', cwd: rootDir })
// Step 4: Verify the CLI was built successfully
const cliPath = path.join(rootDir, 'dist', 'cli.js')
if (!fs.existsSync(cliPath)) {
console.error(`Error: CLI build failed. File not found at ${cliPath}`)
process.exit(1)
}
// Step 5: Publish the main package
console.log('Publishing main package...')
execSync('npm publish', { stdio: 'inherit', cwd: rootDir })
// Step 6: Publish the CLI package
console.log('Publishing CLI package...')
execSync('npm publish', { stdio: 'inherit', cwd: cliPackageDir })
console.log('Both packages published successfully!')
} catch (error) {
console.error('Error:', error.message)
process.exit(1)
}

View file

@ -3,4 +3,4 @@
* Do not modify this file directly.
*/
export const VERSION = '0.9.15';
export const VERSION = '0.9.16';