hana-cli
Version:
HANA Developer Command Line Interface
1,034 lines (906 loc) • 40.2 kB
JavaScript
// @ts-check
/**
* Generate portable agent instructions for hana-cli consumers
*
* Reads command metadata from multiple sources and generates:
* - Universal markdown reference files
* - Multi-format agent adapter files (Copilot, Cursor, Claude, Windsurf, Cline, generic)
* - Per-category reference files
* - llms.txt compact reference
*
* Usage: node generate-agent-instructions.js [--force]
*
* Data sources:
* bin/commandMetadata.js → Category mapping for all commands
* mcp-server/src/command-metadata.ts (built) → Enriched metadata, categories, workflows
* mcp-server/src/examples-presets.ts (built) → Concrete examples and presets
* bin/*.js → builder exports for parameter schemas
*
* @module generate-agent-instructions
*/
import fs from 'fs'
import path from 'path'
import { fileURLToPath } from 'url'
const __filename = fileURLToPath(import.meta.url)
const __dirname = path.dirname(__filename)
// Directories
const BIN_DIR = path.join(__dirname, 'bin')
const OUTPUT_DIR = path.join(__dirname, 'agent-instructions')
const CATEGORIES_DIR = path.join(OUTPUT_DIR, 'categories')
// Force flag
const FORCE = process.argv.includes('--force')
// ── Data Loading ────────────────────────────────────────────────────────
/**
* Load command metadata from bin/commandMetadata.js
* @returns {Promise<Record<string, {category: string, relatedCommands?: string[]}>>}
*/
async function loadCommandMetadata() {
const mod = await import('./bin/commandMetadata.js')
return mod.commandMetadata
}
/**
* Load enriched metadata, categories, and workflows from MCP build
*/
async function loadEnrichedMetadata() {
try {
const mod = await import('./mcp-server/build/command-metadata.js')
return {
enriched: mod.COMMAND_METADATA_MAP || {},
categories: mod.CATEGORIES || {},
workflows: mod.WORKFLOWS || {},
}
} catch {
console.warn('⚠️ MCP server not built — run "npm run build" in mcp-server/ first for enriched metadata')
return { enriched: {}, categories: {}, workflows: {} }
}
}
/**
* Load examples and presets from MCP build
*/
async function loadExamplesPresets() {
try {
const mod = await import('./mcp-server/build/examples-presets.js')
return {
examples: mod.COMMAND_EXAMPLES || {},
presets: mod.COMMAND_PRESETS || {},
}
} catch {
console.warn('⚠️ MCP server not built — examples/presets unavailable')
return { examples: {}, presets: {} }
}
}
/**
* Extract builder options from a single command file by parsing source text
* @param {string} filePath
* @returns {Record<string, {type?: string, alias?: string[], default?: any, desc?: string, choices?: string[]}>}
*/
function extractBuilderOptions(filePath) {
try {
const content = fs.readFileSync(filePath, 'utf8')
// Extract command signature
const cmdMatch = content.match(/export\s+const\s+command\s*=\s*['"`](.*?)['"`]/)
// Extract aliases
const aliasMatch = content.match(/export\s+const\s+aliases\s*=\s*\[(.*?)\]/s)
// Extract describe
const descMatch = content.match(/export\s+const\s+describe\s*=\s*(?:baseLite\.bundle\.getText\(['"`](.*?)['"`]\)|['"`](.*?)['"`])/)
// Extract the options object from getBuilder({...}) or yargs.options({...})
const builderBlockMatch = content.match(/(?:getBuilder|\.options)\(\s*\{([\s\S]*?)\}\s*\)/)
/** @type {Record<string, any>} */
const options = {}
if (builderBlockMatch) {
const block = builderBlockMatch[1]
// Match each option: key: { ... }
const optRegex = /(\w+)\s*:\s*\{([^}]*)\}/g
let m
while ((m = optRegex.exec(block)) !== null) {
const optName = m[1]
const optBody = m[2]
const opt = {}
const typeM = optBody.match(/type\s*:\s*['"`](\w+)['"`]/)
if (typeM) opt.type = typeM[1]
const defaultM = optBody.match(/default\s*:\s*([^,\n]+)/)
if (defaultM) {
let val = defaultM[1].trim()
if (val === 'true') val = true
else if (val === 'false') val = false
else if (/^\d+$/.test(val)) val = parseInt(val, 10)
opt.default = val
}
const choicesM = optBody.match(/choices\s*:\s*\[([^\]]+)\]/)
if (choicesM) {
opt.choices = choicesM[1].split(',').map(c => c.trim().replace(/['"]/g, '')).filter(Boolean)
}
const aliasM = optBody.match(/alias\s*:\s*\[([^\]]+)\]/)
if (aliasM) {
opt.alias = aliasM[1].split(',').map(a => a.trim().replace(/['"]/g, '')).filter(Boolean)
}
const descM = optBody.match(/desc\s*:\s*(?:baseLite\.bundle\.getText\(['"`](\w+)['"`]\)|['"`](.*?)['"`])/)
if (descM) opt.descKey = descM[1] || descM[2]
options[optName] = opt
}
}
return {
commandSig: cmdMatch ? cmdMatch[1] : null,
aliases: aliasMatch
? aliasMatch[1].split(',').map(a => a.trim().replace(/['"]/g, '')).filter(Boolean)
: [],
descKey: descMatch ? (descMatch[1] || descMatch[2]) : null,
options,
}
} catch {
return { commandSig: null, aliases: [], descKey: null, options: {} }
}
}
/**
* Load all command file metadata from bin/
*/
function loadAllCommandFiles() {
const files = fs.readdirSync(BIN_DIR)
.filter(f => f.endsWith('.js') && !['index.js', 'cli.js', 'commandMetadata.js'].includes(f))
.sort()
const commands = {}
for (const file of files) {
const name = file.replace('.js', '')
commands[name] = extractBuilderOptions(path.join(BIN_DIR, file))
}
return commands
}
// ── Deduplication: identify canonical commands vs aliases ────────────
/**
* Build a set of canonical commands (not aliases)
* @param {Record<string, any>} commandMeta - from commandMetadata.js
* @param {Record<string, any>} commandFiles - parsed builder info
* @returns {Map<string, {category: string, relatedCommands: string[], aliases: string[], commandSig: string, options: Record<string, any>, enriched: any, examples: any[], presets: any[]}>}
*/
function buildCanonicalCommands(commandMeta, commandFiles, enriched, examples, presets) {
// First pass: identify which file-based commands are aliases of others
// Aliases typically share the same metadata object reference or point to same category with same relatedCommands
const aliasTargets = new Set()
for (const [, info] of Object.entries(commandFiles)) {
if (info.aliases) {
for (const alias of info.aliases) {
aliasTargets.add(alias)
}
}
}
// Filter to only canonical commands (those that have their own file AND are not listed as an alias of another command)
const canonical = new Map()
for (const [name, fileInfo] of Object.entries(commandFiles)) {
// Skip if this file name appears as an alias of another command
if (aliasTargets.has(name)) continue
const meta = commandMeta[name] || {}
const enr = enriched[name] || {}
canonical.set(name, {
category: meta.category || enr.category || 'other',
relatedCommands: meta.relatedCommands || enr.relatedCommands || [],
aliases: fileInfo.aliases || [],
commandSig: fileInfo.commandSig || name,
options: fileInfo.options || {},
tags: enr.tags || [],
useCases: enr.useCases || [],
prerequisites: enr.prerequisites || [],
examples: examples[name] || [],
presets: presets[name] || [],
})
}
return canonical
}
// ── Markdown Generators ─────────────────────────────────────────────
/**
* Get the package version
*/
function getVersion() {
const pkg = JSON.parse(fs.readFileSync(path.join(__dirname, 'package.json'), 'utf8'))
return pkg.version
}
/**
* Format a category name for display
*/
function formatCategory(cat) {
return cat.split('-').map(w => w.charAt(0).toUpperCase() + w.slice(1)).join(' ')
}
/**
* Generate parameter table for a command
*/
function generateParamTable(options) {
if (!options || Object.keys(options).length === 0) return ''
let table = '| Parameter | Type | Default | Description |\n'
table += '|-----------|------|---------|-------------|\n'
for (const [name, opt] of Object.entries(options)) {
const type = opt.type || 'string'
let def = opt.default !== undefined ? `\`${opt.default}\`` : '-'
const desc = opt.descKey || name
const aliases = opt.alias ? opt.alias.map(a => `\`-${a.length === 1 ? a : '-' + a}\``).join(', ') : ''
const aliasStr = aliases ? ` (${aliases})` : ''
table += `| \`--${name}\`${aliasStr} | ${type} | ${def} | ${desc} |\n`
}
return table
}
/**
* Generate the HANA_CLI_REFERENCE.md master document
*/
function generateReference(canonical, categories, workflows, version) {
const lines = []
lines.push(`# SAP HANA CLI (hana-cli) — Complete Command Reference`)
lines.push(``)
lines.push(`> Generated from hana-cli v${version} on ${new Date().toISOString().split('T')[0]}`)
lines.push(`> This file is auto-generated. Do not edit manually.`)
lines.push(``)
lines.push(`## What is hana-cli?`)
lines.push(``)
lines.push(`**hana-cli** is a command-line tool that simplifies SAP HANA database development. It wraps complex multi-step database operations into single, easy-to-use commands. It is a development-time tool — not a replacement for \`hdbsql\` or production administration tools.`)
lines.push(``)
lines.push(`### Installation`)
lines.push(``)
lines.push('```bash')
lines.push(`npm install -g hana-cli`)
lines.push('```')
lines.push(``)
lines.push(`**Requirements:** Node.js ≥ 20.19.0`)
lines.push(``)
lines.push(`### Connection Setup`)
lines.push(``)
lines.push(`Before using most commands, establish a database connection:`)
lines.push(``)
lines.push('```bash')
lines.push(`# Interactive connection wizard`)
lines.push(`hana-cli connect`)
lines.push(``)
lines.push(`# Connect via BTP service key`)
lines.push(`hana-cli connectViaServiceKey`)
lines.push(``)
lines.push(`# Copy connection to default-env.json for CAP/CDS projects`)
lines.push(`hana-cli copy2DefaultEnv`)
lines.push('```')
lines.push(``)
lines.push(`Connection details are stored locally. Use \`hana-cli status\` to verify your connection.`)
lines.push(``)
lines.push(`### Running Commands`)
lines.push(``)
lines.push('```bash')
lines.push(`# Direct CLI mode`)
lines.push(`hana-cli <command> [options]`)
lines.push(``)
lines.push(`# Interactive mode (menu-driven)`)
lines.push(`hana-cli`)
lines.push(``)
lines.push(`# Get help for any command`)
lines.push(`hana-cli <command> --help`)
lines.push('```')
lines.push(``)
lines.push(`### Output Formats`)
lines.push(``)
lines.push(`Many commands support \`--output\` with values: \`table\`, \`json\`, \`csv\`, \`excel\`.`)
lines.push(``)
// ── Command Reference by Category ──
lines.push(`---`)
lines.push(``)
lines.push(`## Command Reference`)
lines.push(``)
// Group canonical commands by category
const byCategory = new Map()
for (const [name, cmd] of canonical) {
const cat = cmd.category
if (!byCategory.has(cat)) byCategory.set(cat, [])
byCategory.get(cat).push({ name, ...cmd })
}
// Sort categories
const sortedCats = [...byCategory.keys()].sort()
for (const cat of sortedCats) {
const catInfo = categories[cat]
const catName = catInfo ? catInfo.name : formatCategory(cat)
const catDesc = catInfo ? catInfo.description : ''
lines.push(`### ${catName}`)
if (catDesc) lines.push(``)
if (catDesc) lines.push(`${catDesc}`)
lines.push(``)
const cmds = byCategory.get(cat).sort((a, b) => a.name.localeCompare(b.name))
for (const cmd of cmds) {
lines.push(`#### \`${cmd.name}\``)
lines.push(``)
if (cmd.aliases.length > 0) {
lines.push(`**Aliases:** ${cmd.aliases.map(a => `\`${a}\``).join(', ')}`)
}
if (cmd.commandSig && cmd.commandSig !== cmd.name) {
lines.push(`**Syntax:** \`hana-cli ${cmd.commandSig}\``)
} else {
lines.push(`**Syntax:** \`hana-cli ${cmd.name}\``)
}
if (cmd.tags.length > 0) {
lines.push(`**Tags:** ${cmd.tags.join(', ')}`)
}
lines.push(``)
if (cmd.useCases.length > 0) {
lines.push(`**Use cases:**`)
for (const uc of cmd.useCases) {
lines.push(`- ${uc}`)
}
lines.push(``)
}
if (cmd.prerequisites.length > 0) {
lines.push(`**Prerequisites:** ${cmd.prerequisites.join(', ')}`)
lines.push(``)
}
// Parameter table
const paramTable = generateParamTable(cmd.options)
if (paramTable) {
lines.push(`**Parameters:**`)
lines.push(``)
lines.push(paramTable)
}
if (cmd.relatedCommands.length > 0) {
lines.push(`**Related:** ${cmd.relatedCommands.map(r => `\`${r}\``).join(', ')}`)
}
lines.push(``)
lines.push(`---`)
lines.push(``)
}
}
// ── Workflows ──
if (Object.keys(workflows).length > 0) {
lines.push(`## Multi-Step Workflows`)
lines.push(``)
lines.push(`These are pre-defined sequences of commands for common tasks.`)
lines.push(``)
for (const [, wf] of Object.entries(workflows)) {
lines.push(`### ${wf.name}`)
lines.push(``)
lines.push(`${wf.description}`)
lines.push(``)
lines.push(`**Goal:** ${wf.goal}`)
if (wf.estimatedTime) lines.push(`**Estimated time:** ${wf.estimatedTime}`)
if (wf.tags) lines.push(`**Tags:** ${wf.tags.join(', ')}`)
lines.push(``)
lines.push(`| Step | Command | Description |`)
lines.push(`|------|---------|-------------|`)
for (const step of wf.steps) {
const params = step.keyParameters ? Object.entries(step.keyParameters).map(([k, v]) => `--${k} ${v}`).join(' ') : ''
lines.push(`| ${step.order} | \`hana-cli ${step.command} ${params}\` | ${step.description} |`)
}
lines.push(``)
}
}
// ── Common Patterns ──
lines.push(`## Common Patterns`)
lines.push(``)
lines.push(`### Explore Before Modify`)
lines.push(`1. \`hana-cli tables\` → find the table`)
lines.push(`2. \`hana-cli inspectTable --table X --schema Y\` → understand structure`)
lines.push(`3. \`hana-cli dataProfile --table X --schema Y\` → understand data`)
lines.push(`4. Then proceed with import/export/modify operations`)
lines.push(``)
lines.push(`### Safe Data Import`)
lines.push(`1. \`hana-cli import --filename data.csv --table X --schema Y --dryRun\` → preview`)
lines.push(`2. Review the dry-run output for errors`)
lines.push(`3. \`hana-cli import --filename data.csv --table X --schema Y\` → actual import`)
lines.push(``)
lines.push(`### Performance Investigation`)
lines.push(`1. \`hana-cli healthCheck\` → overview`)
lines.push(`2. \`hana-cli expensiveStatements --limit 10\` → find slow queries`)
lines.push(`3. \`hana-cli queryPlan --query "SELECT ..."\` → analyze specific query`)
lines.push(``)
lines.push(`### Schema Comparison`)
lines.push(`1. \`hana-cli compareSchema --sourceSchema DEV --targetSchema PROD\` → find differences`)
lines.push(`2. Review differences`)
lines.push(`3. \`hana-cli schemaClone --sourceSchema DEV --targetSchema TEST\` → clone if needed`)
lines.push(``)
lines.push(`### Security Review`)
lines.push(`1. \`hana-cli securityScan\` → comprehensive scan`)
lines.push(`2. \`hana-cli privilegeAnalysis\` → check privilege distribution`)
lines.push(`3. \`hana-cli grantChains --user X\` → trace specific user privileges`)
lines.push(``)
// ── Quick Reference Table ──
lines.push(`## Quick Reference — All Commands`)
lines.push(``)
lines.push(`| Command | Category | Description |`)
lines.push(`|---------|----------|-------------|`)
for (const [name, cmd] of [...canonical].sort((a, b) => a[0].localeCompare(b[0]))) {
const aliasStr = cmd.aliases.length > 0 ? ` (${cmd.aliases.join(', ')})` : ''
const desc = cmd.useCases.length > 0 ? cmd.useCases[0] : formatCategory(cmd.category)
lines.push(`| \`${name}\`${aliasStr} | ${formatCategory(cmd.category)} | ${desc} |`)
}
lines.push(``)
return lines.join('\n')
}
/**
* Generate HANA_CLI_QUICKSTART.md
*/
function generateQuickstart(canonical, version) {
const topCommands = [
'status', 'connect', 'tables', 'inspectTable', 'views',
'import', 'export', 'querySimple', 'healthCheck', 'schemas',
]
const lines = []
lines.push(`# hana-cli Quick Start Guide`)
lines.push(``)
lines.push(`> Generated from hana-cli v${version} on ${new Date().toISOString().split('T')[0]}`)
lines.push(``)
lines.push(`## Install`)
lines.push(``)
lines.push('```bash')
lines.push(`npm install -g hana-cli`)
lines.push('```')
lines.push(``)
lines.push(`## Connect to Your Database`)
lines.push(``)
lines.push('```bash')
lines.push(`# Interactive connection wizard — prompts for host, port, user, password`)
lines.push(`hana-cli connect`)
lines.push(``)
lines.push(`# Or connect using a BTP service key file`)
lines.push(`hana-cli connectViaServiceKey`)
lines.push(``)
lines.push(`# Verify your connection`)
lines.push(`hana-cli status`)
lines.push('```')
lines.push(``)
lines.push(`## Essential Commands`)
lines.push(``)
for (const name of topCommands) {
const cmd = canonical.get(name)
if (!cmd) continue
lines.push(`### \`hana-cli ${name}\``)
if (cmd.useCases.length > 0) {
lines.push(`${cmd.useCases[0]}`)
}
if (cmd.aliases.length > 0) {
lines.push(`Aliases: ${cmd.aliases.map(a => `\`${a}\``).join(', ')}`)
}
lines.push(``)
lines.push('```bash')
if (cmd.commandSig && cmd.commandSig !== name) {
lines.push(`hana-cli ${cmd.commandSig}`)
} else {
lines.push(`hana-cli ${name}`)
}
lines.push('```')
lines.push(``)
}
lines.push(`## Common Workflows`)
lines.push(``)
lines.push(`### Explore a Schema`)
lines.push('```bash')
lines.push(`hana-cli schemas # List schemas`)
lines.push(`hana-cli tables --schema MY_SCHEMA # List tables`)
lines.push(`hana-cli inspectTable --table MY_TABLE --schema MY_SCHEMA # Column details`)
lines.push('```')
lines.push(``)
lines.push(`### Import Data`)
lines.push('```bash')
lines.push(`hana-cli import --filename data.csv --table MY_TABLE --schema MY_SCHEMA --dryRun # Preview`)
lines.push(`hana-cli import --filename data.csv --table MY_TABLE --schema MY_SCHEMA # Execute`)
lines.push('```')
lines.push(``)
lines.push(`### Export Data`)
lines.push('```bash')
lines.push(`hana-cli export --table MY_TABLE --schema MY_SCHEMA --filename export.csv`)
lines.push('```')
lines.push(``)
lines.push(`### Run a Query`)
lines.push('```bash')
lines.push(`hana-cli querySimple --query "SELECT TOP 10 * FROM MY_SCHEMA.MY_TABLE"`)
lines.push('```')
lines.push(``)
lines.push(`### Check System Health`)
lines.push('```bash')
lines.push(`hana-cli healthCheck`)
lines.push(`hana-cli systemInfo`)
lines.push('```')
lines.push(``)
lines.push(`## Getting Help`)
lines.push(``)
lines.push('```bash')
lines.push(`hana-cli --help # List all commands`)
lines.push(`hana-cli <cmd> --help # Help for specific command`)
lines.push(`hana-cli interactive # Interactive menu mode`)
lines.push('```')
lines.push(``)
return lines.join('\n')
}
/**
* Generate HANA_CLI_EXAMPLES.md from examples and presets
*/
function generateExamples(canonical, examples, presets) {
const lines = []
lines.push(`# hana-cli — Real-World Examples`)
lines.push(``)
lines.push(`> Auto-generated from hana-cli command examples and parameter presets.`)
lines.push(``)
// Examples section
const exampleCommands = Object.keys(examples).sort()
if (exampleCommands.length > 0) {
lines.push(`## Command Examples`)
lines.push(``)
for (const cmd of exampleCommands) {
const cmdExamples = examples[cmd]
if (!cmdExamples || cmdExamples.length === 0) continue
lines.push(`### \`${cmd}\``)
lines.push(``)
for (const ex of cmdExamples) {
lines.push(`**${ex.scenario}** — ${ex.description}`)
lines.push(``)
lines.push('```bash')
const params = Object.entries(ex.parameters)
.map(([k, v]) => typeof v === 'boolean' ? (v ? `--${k}` : '') : `--${k} ${JSON.stringify(v)}`)
.filter(Boolean)
.join(' ')
lines.push(`hana-cli ${cmd} ${params}`)
lines.push('```')
if (ex.notes) lines.push(`> ${ex.notes}`)
if (ex.expectedOutput) lines.push(`Expected: ${ex.expectedOutput}`)
lines.push(``)
}
}
}
// Presets section
const presetCommands = Object.keys(presets).sort()
if (presetCommands.length > 0) {
lines.push(`## Parameter Presets`)
lines.push(``)
lines.push(`Pre-configured parameter combinations for common scenarios.`)
lines.push(``)
for (const cmd of presetCommands) {
const cmdPresets = presets[cmd]
if (!cmdPresets || cmdPresets.length === 0) continue
lines.push(`### \`${cmd}\` Presets`)
lines.push(``)
for (const preset of cmdPresets) {
lines.push(`**${preset.name}** — ${preset.description}`)
if (preset.whenToUse) lines.push(`When to use: ${preset.whenToUse}`)
lines.push(``)
lines.push('```bash')
const params = Object.entries(preset.parameters)
.map(([k, v]) => typeof v === 'boolean' ? (v ? `--${k}` : '') : `--${k} ${v}`)
.filter(Boolean)
.join(' ')
lines.push(`hana-cli ${cmd} ${params}`)
lines.push('```')
if (preset.notes) lines.push(`> ${preset.notes}`)
lines.push(``)
}
}
}
return lines.join('\n')
}
/**
* Generate HANA_CLI_WORKFLOWS.md
*/
function generateWorkflows(workflows) {
const lines = []
lines.push(`# hana-cli — Multi-Step Workflows`)
lines.push(``)
lines.push(`> Pre-defined sequences of hana-cli commands for common development tasks.`)
lines.push(``)
for (const [, wf] of Object.entries(workflows)) {
lines.push(`## ${wf.name}`)
lines.push(``)
lines.push(`${wf.description}`)
lines.push(``)
lines.push(`**Goal:** ${wf.goal}`)
if (wf.estimatedTime) lines.push(`**Estimated time:** ${wf.estimatedTime}`)
if (wf.tags) lines.push(`**Tags:** ${wf.tags.join(', ')}`)
lines.push(``)
for (const step of wf.steps) {
lines.push(`### Step ${step.order}: ${step.command}`)
lines.push(``)
lines.push(step.description)
lines.push(``)
if (step.keyParameters && Object.keys(step.keyParameters).length > 0) {
const params = Object.entries(step.keyParameters).map(([k, v]) => `--${k} ${v}`).join(' ')
lines.push('```bash')
lines.push(`hana-cli ${step.command} ${params}`)
lines.push('```')
} else {
lines.push('```bash')
lines.push(`hana-cli ${step.command}`)
lines.push('```')
}
if (step.expectedOutput) {
lines.push(``)
lines.push(`Expected output: ${step.expectedOutput}`)
}
lines.push(``)
}
lines.push(`---`)
lines.push(``)
}
return lines.join('\n')
}
/**
* Generate per-category markdown file
*/
function generateCategoryFile(catKey, catInfo, commands, examples, presets) {
const catName = catInfo ? catInfo.name : formatCategory(catKey)
const catDesc = catInfo ? catInfo.description : ''
const lines = []
lines.push(`# ${catName}`)
lines.push(``)
if (catDesc) lines.push(`${catDesc}`)
lines.push(``)
lines.push(`| Command | Aliases | Description |`)
lines.push(`|---------|---------|-------------|`)
for (const cmd of commands) {
const aliasStr = cmd.aliases.length > 0 ? cmd.aliases.map(a => `\`${a}\``).join(', ') : '-'
const desc = cmd.useCases.length > 0 ? cmd.useCases[0] : '-'
lines.push(`| \`${cmd.name}\` | ${aliasStr} | ${desc} |`)
}
lines.push(``)
for (const cmd of commands) {
lines.push(`## \`${cmd.name}\``)
lines.push(``)
if (cmd.commandSig && cmd.commandSig !== cmd.name) {
lines.push('```bash')
lines.push(`hana-cli ${cmd.commandSig}`)
lines.push('```')
lines.push(``)
}
if (cmd.aliases.length > 0) {
lines.push(`**Aliases:** ${cmd.aliases.map(a => `\`${a}\``).join(', ')}`)
}
if (cmd.tags.length > 0) {
lines.push(`**Tags:** ${cmd.tags.join(', ')}`)
}
if (cmd.useCases.length > 0) {
for (const uc of cmd.useCases) lines.push(`- ${uc}`)
}
if (cmd.prerequisites.length > 0) {
lines.push(`**Prerequisites:** ${cmd.prerequisites.join(', ')}`)
}
lines.push(``)
const paramTable = generateParamTable(cmd.options)
if (paramTable) {
lines.push(`### Parameters`)
lines.push(``)
lines.push(paramTable)
}
// Include examples if available
const cmdExamples = examples[cmd.name]
if (cmdExamples && cmdExamples.length > 0) {
lines.push(`### Examples`)
lines.push(``)
for (const ex of cmdExamples) {
const params = Object.entries(ex.parameters)
.map(([k, v]) => typeof v === 'boolean' ? (v ? `--${k}` : '') : `--${k} ${JSON.stringify(v)}`)
.filter(Boolean)
.join(' ')
lines.push(`**${ex.scenario}:** \`hana-cli ${cmd.name} ${params}\``)
if (ex.notes) lines.push(`> ${ex.notes}`)
lines.push(``)
}
}
if (cmd.relatedCommands.length > 0) {
lines.push(`**Related:** ${cmd.relatedCommands.map(r => `\`${r}\``).join(', ')}`)
}
lines.push(``)
lines.push(`---`)
lines.push(``)
}
return lines.join('\n')
}
// ── Agent Format Generators ─────────────────────────────────────────
/**
* Core context block used by all agent formats
*/
function agentPreamble(version) {
return `# hana-cli — AI Coding Assistant Context
## About hana-cli
hana-cli (npm: hana-cli, install: \`npm install -g hana-cli\`) is a command-line tool for SAP HANA database development. It simplifies complex multi-step database operations into single commands. It is a development tool, not a replacement for hdbsql or production admin tools.
**Version:** ${version}
**Requirements:** Node.js ≥ 20.19.0
**Module:** ESM (\`"type": "module"\`)
## When to Use hana-cli
Use hana-cli when a developer needs to:
- **Explore schemas**: \`hana-cli tables\`, \`hana-cli views\`, \`hana-cli schemas\`
- **Inspect objects**: \`hana-cli inspectTable\`, \`hana-cli inspectView\`, \`hana-cli inspectProcedure\`
- **Import/export data**: \`hana-cli import\`, \`hana-cli export\`
- **Run queries**: \`hana-cli querySimple --query "SQL"\`
- **Check health**: \`hana-cli healthCheck\`, \`hana-cli systemInfo\`
- **Manage connections**: \`hana-cli connect\`, \`hana-cli status\`
- **Profile data**: \`hana-cli dataProfile\`, \`hana-cli dataValidator\`
- **Compare schemas**: \`hana-cli compareSchema\`, \`hana-cli compareData\`
- **Monitor performance**: \`hana-cli expensiveStatements\`, \`hana-cli memoryAnalysis\`
- **Manage security**: \`hana-cli users\`, \`hana-cli roles\`, \`hana-cli securityScan\`
- **Work with HANA Cloud**: \`hana-cli hanaCloudInstances\`, \`hana-cli hanaCloudStart\`
- **Manage HDI**: \`hana-cli containers\`, \`hana-cli adminHDI\`
## Key Patterns
1. **Always verify connection first:** \`hana-cli status\`
2. **Explore before modifying:** Use \`tables\`, \`inspectTable\`, \`dataProfile\` before import/export
3. **Use dry-run for imports:** \`hana-cli import --filename data.csv --table X --schema Y --dryRun\`
4. **Use --output flag:** Many commands support \`--output json|csv|table|excel\`
5. **Use --query flag with querySimple:** \`hana-cli querySimple --query "SELECT ..."\` (not positional args)
6. **Interactive mode:** Run \`hana-cli\` with no arguments for a menu-driven experience
## Connection Setup
\`\`\`bash
hana-cli connect # Interactive wizard
hana-cli connectViaServiceKey # Via BTP service key
hana-cli copy2DefaultEnv # Copy to default-env.json for CAP projects
hana-cli status # Verify connection
\`\`\`
## Reference
For the complete command reference with all parameters, see:
- HANA_CLI_REFERENCE.md (full reference, all commands)
- HANA_CLI_QUICKSTART.md (getting started, top 10 commands)
- HANA_CLI_EXAMPLES.md (real-world scenarios)
- HANA_CLI_WORKFLOWS.md (multi-step workflows)
- categories/*.md (per-category deep dives)
Or run \`hana-cli <command> --help\` for any specific command.
`
}
function generateCopilotInstructions(version) {
return `${agentPreamble(version)}
## Copilot-Specific Notes
When assisting with SAP HANA development in this project:
- Suggest hana-cli commands when the developer needs to interact with HANA databases
- Use \`hana-cli querySimple --query "SQL"\` for ad-hoc queries (always use --query flag)
- Check connection with \`hana-cli status\` before suggesting database operations
- For data loading, recommend the dry-run pattern: import with --dryRun first, then without
- Refer to the HANA_CLI_REFERENCE.md file in this directory for detailed parameter information
`
}
function generateCopilotInstructionFile(version) {
return `---
description: "Use when working with SAP HANA databases, CDS models, HDI containers, or BTP projects. Provides hana-cli command knowledge for database operations."
applyTo: "*.hdbcds,*.hdbtable,*.hdbview,*.hdbprocedure,*.hdbfunction,*.cds,mta.yaml,mta.yml,default-env.json,*.hdbgrants,*.hdbsynonym,*.hdbsequence"
---
${agentPreamble(version)}
`
}
function generateCopilotPrompt() {
return `---
description: "Ask about hana-cli capabilities and get command recommendations"
---
You have access to hana-cli, a comprehensive SAP HANA CLI tool. When the user asks about database operations, suggest appropriate hana-cli commands.
Key commands:
- Schema exploration: tables, views, schemas, objects, inspectTable, inspectView
- Data operations: import, export, querySimple, dataProfile, dataValidator
- Performance: healthCheck, expensiveStatements, memoryAnalysis, tableHotspots
- Security: users, roles, securityScan, privilegeAnalysis
- Connection: connect, status, connectViaServiceKey
Always use \`hana-cli <command> --help\` for detailed parameter information.
Always use \`--query\` flag with querySimple: \`hana-cli querySimple --query "SELECT ..."\`
`
}
function generateCursorRules(version) {
return `${agentPreamble(version)}`
}
function generateClaudeMd(version) {
return `${agentPreamble(version)}`
}
function generateWindsurfRules(version) {
return `${agentPreamble(version)}`
}
function generateClineRules(version) {
return `${agentPreamble(version)}`
}
function generateGenericInstructions(version) {
return `${agentPreamble(version)}
---
This file provides context for any AI coding assistant about the hana-cli tool.
Place it in your project root or in a location your coding agent reads for project context.
`
}
/**
* Generate llms.txt — compact machine-readable format
*/
function generateLlmsTxt(canonical, version) {
const lines = []
lines.push(`# hana-cli`)
lines.push(``)
lines.push(`> SAP HANA Developer CLI Tool — simplifies database development operations`)
lines.push(``)
lines.push(`## Install: npm install -g hana-cli`)
lines.push(`## Version: ${version}`)
lines.push(`## Docs: https://github.com/SAP-samples/hana-developer-cli-tool-example`)
lines.push(``)
lines.push(`## Commands`)
lines.push(``)
for (const [name, cmd] of [...canonical].sort((a, b) => a[0].localeCompare(b[0]))) {
const aliases = cmd.aliases.length > 0 ? ` (${cmd.aliases.join(', ')})` : ''
const desc = cmd.useCases.length > 0 ? cmd.useCases[0] : formatCategory(cmd.category)
lines.push(`- ${name}${aliases}: ${desc}`)
}
lines.push(``)
lines.push(`## Quick Start`)
lines.push(`- hana-cli connect`)
lines.push(`- hana-cli status`)
lines.push(`- hana-cli tables --schema MYSCHEMA`)
lines.push(`- hana-cli inspectTable --table MYTABLE --schema MYSCHEMA`)
lines.push(`- hana-cli querySimple --query "SELECT TOP 10 * FROM MYSCHEMA.MYTABLE"`)
lines.push(`- hana-cli import --filename data.csv --table MYTABLE --schema MYSCHEMA --dryRun`)
lines.push(`- hana-cli healthCheck`)
return lines.join('\n')
}
// ── File Writing ────────────────────────────────────────────────────
function ensureDir(dir) {
if (!fs.existsSync(dir)) {
fs.mkdirSync(dir, { recursive: true })
}
}
function writeFile(filePath, content) {
if (fs.existsSync(filePath) && !FORCE) {
console.log(` ⏩ Skipping (exists): ${path.relative(__dirname, filePath)}`)
return false
}
fs.writeFileSync(filePath, content, 'utf8')
console.log(` ✅ ${path.relative(__dirname, filePath)}`)
return true
}
// ── Main ────────────────────────────────────────────────────────────
async function main() {
console.log('🔧 Generating portable agent instructions for hana-cli...')
console.log('')
// Load data
console.log('📖 Loading data sources...')
const [commandMeta, { enriched, categories, workflows }, { examples, presets }] = await Promise.all([
loadCommandMetadata(),
loadEnrichedMetadata(),
loadExamplesPresets(),
])
const commandFiles = loadAllCommandFiles()
// Build canonical command map
const canonical = buildCanonicalCommands(commandMeta, commandFiles, enriched, examples, presets)
console.log(` Found ${canonical.size} canonical commands`)
const version = getVersion()
let written = 0
// Create directories
ensureDir(OUTPUT_DIR)
ensureDir(CATEGORIES_DIR)
ensureDir(path.join(OUTPUT_DIR, 'copilot', '.github', 'instructions'))
ensureDir(path.join(OUTPUT_DIR, 'copilot', '.github', 'prompts'))
ensureDir(path.join(OUTPUT_DIR, 'cursor'))
ensureDir(path.join(OUTPUT_DIR, 'claude'))
ensureDir(path.join(OUTPUT_DIR, 'windsurf'))
ensureDir(path.join(OUTPUT_DIR, 'cline'))
ensureDir(path.join(OUTPUT_DIR, 'generic'))
// ── Phase 1: Universal reference files ──
console.log('')
console.log('📝 Generating universal reference files...')
if (writeFile(path.join(OUTPUT_DIR, 'HANA_CLI_REFERENCE.md'), generateReference(canonical, categories, workflows, version))) written++
if (writeFile(path.join(OUTPUT_DIR, 'HANA_CLI_QUICKSTART.md'), generateQuickstart(canonical, version))) written++
if (writeFile(path.join(OUTPUT_DIR, 'HANA_CLI_EXAMPLES.md'), generateExamples(canonical, examples, presets))) written++
if (writeFile(path.join(OUTPUT_DIR, 'HANA_CLI_WORKFLOWS.md'), generateWorkflows(workflows))) written++
// ── Phase 1.5: Per-category files ──
console.log('')
console.log('📂 Generating per-category reference files...')
const byCategory = new Map()
for (const [name, cmd] of canonical) {
const cat = cmd.category
if (!byCategory.has(cat)) byCategory.set(cat, [])
byCategory.get(cat).push({ name, ...cmd })
}
for (const [cat, commands] of byCategory) {
const catInfo = categories[cat]
const fileName = `${cat}.md`
commands.sort((a, b) => a.name.localeCompare(b.name))
if (writeFile(path.join(CATEGORIES_DIR, fileName), generateCategoryFile(cat, catInfo, commands, examples, presets))) written++
}
// ── Phase 2: Agent format adapters ──
console.log('')
console.log('🤖 Generating agent format adapters...')
// Copilot
if (writeFile(path.join(OUTPUT_DIR, 'copilot', '.github', 'copilot-instructions.md'), generateCopilotInstructions(version))) written++
if (writeFile(path.join(OUTPUT_DIR, 'copilot', '.github', 'instructions', 'hana-cli-usage.instructions.md'), generateCopilotInstructionFile(version))) written++
if (writeFile(path.join(OUTPUT_DIR, 'copilot', '.github', 'prompts', 'hana-cli-help.prompt.md'), generateCopilotPrompt())) written++
// Cursor
if (writeFile(path.join(OUTPUT_DIR, 'cursor', '.cursorrules'), generateCursorRules(version))) written++
// Claude Code
if (writeFile(path.join(OUTPUT_DIR, 'claude', 'CLAUDE.md'), generateClaudeMd(version))) written++
// Windsurf
if (writeFile(path.join(OUTPUT_DIR, 'windsurf', '.windsurfrules'), generateWindsurfRules(version))) written++
// Cline
if (writeFile(path.join(OUTPUT_DIR, 'cline', '.clinerules'), generateClineRules(version))) written++
// Generic
if (writeFile(path.join(OUTPUT_DIR, 'generic', 'AGENT_INSTRUCTIONS.md'), generateGenericInstructions(version))) written++
// llms.txt
if (writeFile(path.join(OUTPUT_DIR, 'llms.txt'), generateLlmsTxt(canonical, version))) written++
console.log('')
console.log(`✅ Generated ${written} files in agent-instructions/`)
console.log(` Use --force to overwrite existing files`)
process.exit(0)
}
main().catch(err => {
console.error('❌ Generation failed:', err.message)
console.error(err.stack)
process.exit(1)
})