hana-cli
Version:
HANA Developer Command Line Interface
266 lines (231 loc) • 8.33 kB
JavaScript
// @ts-check
/**
* Install hana-cli agent instructions into a target project
*
* Usage:
* npx hana-cli-agent-instructions [--format <format>] [--target <path>] [--force] [--list]
*
* Formats: copilot, cursor, claude, windsurf, cline, generic, all
*
* Examples:
* npx hana-cli-agent-instructions --format copilot
* npx hana-cli-agent-instructions --format all --target ./my-project
* npx hana-cli-agent-instructions --list
*/
import fs from 'fs'
import path from 'path'
import { fileURLToPath } from 'url'
const __filename = fileURLToPath(import.meta.url)
const __dirname = path.dirname(__filename)
const INSTRUCTIONS_DIR = path.join(__dirname, '..') // Relative to scripts/ or root depending on install
// Parse args
const args = process.argv.slice(2)
/** @param {string} name */
function getArg(name) {
const idx = args.indexOf(`--${name}`)
return idx !== -1 && args[idx + 1] ? args[idx + 1] : null
}
/** @param {string} name */
const hasFlag = (name) => args.includes(`--${name}`)
const FORMAT = getArg('format')
const TARGET = getArg('target') || '.'
const FORCE = hasFlag('force')
const LIST = hasFlag('list')
const HELP = hasFlag('help') || hasFlag('h')
/** @type {Record<string, {name: string, detect: string[], files: {src: string, dest: string}[]}>} */
const FORMATS = {
copilot: {
name: 'GitHub Copilot',
detect: ['.github'],
files: [
{ src: 'copilot/.github/copilot-instructions.md', dest: '.github/copilot-instructions.md' },
{ src: 'copilot/.github/instructions/hana-cli-usage.instructions.md', dest: '.github/instructions/hana-cli-usage.instructions.md' },
{ src: 'copilot/.github/prompts/hana-cli-help.prompt.md', dest: '.github/prompts/hana-cli-help.prompt.md' },
],
},
cursor: {
name: 'Cursor',
detect: ['.cursor', '.cursorrules'],
files: [
{ src: 'cursor/.cursorrules', dest: '.cursorrules' },
],
},
claude: {
name: 'Claude Code',
detect: ['CLAUDE.md'],
files: [
{ src: 'claude/CLAUDE.md', dest: 'CLAUDE.md' },
],
},
windsurf: {
name: 'Windsurf',
detect: ['.windsurf', '.windsurfrules'],
files: [
{ src: 'windsurf/.windsurfrules', dest: '.windsurfrules' },
],
},
cline: {
name: 'Cline',
detect: ['.cline', '.clinerules'],
files: [
{ src: 'cline/.clinerules', dest: '.clinerules' },
],
},
generic: {
name: 'Generic (any agent)',
detect: [],
files: [
{ src: 'generic/AGENT_INSTRUCTIONS.md', dest: 'AGENT_INSTRUCTIONS.md' },
],
},
}
// Shared reference files always copied
const REFERENCE_FILES = [
{ src: 'HANA_CLI_REFERENCE.md', dest: 'HANA_CLI_REFERENCE.md' },
{ src: 'HANA_CLI_QUICKSTART.md', dest: 'HANA_CLI_QUICKSTART.md' },
{ src: 'HANA_CLI_EXAMPLES.md', dest: 'HANA_CLI_EXAMPLES.md' },
{ src: 'HANA_CLI_WORKFLOWS.md', dest: 'HANA_CLI_WORKFLOWS.md' },
{ src: 'llms.txt', dest: 'llms.txt' },
]
function findInstructionsDir() {
// Check several possible locations
const candidates = [
path.join(__dirname, '..'), // scripts/ → root
__dirname, // root directly
path.join(__dirname, '..', 'agent-instructions'), // if run from scripts/
path.join(__dirname, 'agent-instructions'), // if run from root
]
for (const dir of candidates) {
if (fs.existsSync(path.join(dir, 'HANA_CLI_REFERENCE.md'))) {
return dir
}
}
// npm package structure
const npmPkg = path.join(__dirname, '..', 'node_modules', 'hana-cli-agent-instructions')
if (fs.existsSync(path.join(npmPkg, 'HANA_CLI_REFERENCE.md'))) {
return npmPkg
}
return null
}
/** @param {string} targetDir */
function detectFormat(targetDir) {
for (const [format, info] of Object.entries(FORMATS)) {
for (const indicator of info.detect) {
if (fs.existsSync(path.join(targetDir, indicator))) {
return format
}
}
}
return 'generic'
}
/**
* @param {string} srcDir
* @param {string} srcRel
* @param {string} destDir
* @param {string} destRel
*/
function copyFile(srcDir, srcRel, destDir, destRel) {
const srcPath = path.join(srcDir, srcRel)
const destPath = path.join(destDir, destRel)
if (!fs.existsSync(srcPath)) {
console.warn(` ⚠️ Source not found: ${srcRel}`)
return false
}
if (fs.existsSync(destPath) && !FORCE) {
console.log(` ⏩ Exists (use --force to overwrite): ${destRel}`)
return false
}
const destDirPath = path.dirname(destPath)
if (!fs.existsSync(destDirPath)) {
fs.mkdirSync(destDirPath, { recursive: true })
}
fs.copyFileSync(srcPath, destPath)
console.log(` ✅ ${destRel}`)
return true
}
function showHelp() {
console.log(`
hana-cli Agent Instructions Installer
Install hana-cli knowledge into your project so AI coding assistants
can help you work with SAP HANA databases.
Usage:
npx hana-cli-agent-instructions [options]
Options:
--format <fmt> Target format: copilot, cursor, claude, windsurf, cline, generic, all
(auto-detected if not specified)
--target <path> Target directory (default: current directory)
--force Overwrite existing files
--list List available formats and exit
--help Show this help
Examples:
npx hana-cli-agent-instructions # Auto-detect and install
npx hana-cli-agent-instructions --format copilot # Install for GitHub Copilot
npx hana-cli-agent-instructions --format all # Install all formats
npx hana-cli-agent-instructions --format cursor --force # Overwrite existing
`)
}
function showList() {
console.log('\nAvailable formats:\n')
for (const [key, info] of Object.entries(FORMATS)) {
const files = info.files.map(f => f.dest).join(', ')
console.log(` ${key.padEnd(12)} ${info.name}`)
console.log(`${''.padEnd(14)} Files: ${files}`)
}
console.log(`\n all${''.padEnd(9)} Install all formats`)
console.log(`\nShared reference files (always copied):`)
for (const f of REFERENCE_FILES) {
console.log(` ${f.dest}`)
}
}
async function main() {
if (HELP) {
showHelp()
process.exit(0)
}
if (LIST) {
showList()
process.exit(0)
}
const targetDir = path.resolve(TARGET)
const instructionsDir = findInstructionsDir()
if (!instructionsDir) {
console.error('❌ Could not find hana-cli agent instruction files.')
console.error(' Make sure this is run from the hana-cli repo or install hana-cli-agent-instructions package.')
process.exit(1)
}
const format = FORMAT || detectFormat(targetDir)
const formats = format === 'all' ? Object.keys(FORMATS) : [format]
if (format !== 'all' && !FORMATS[format]) {
console.error(`❌ Unknown format: ${format}`)
console.error(` Available: ${Object.keys(FORMATS).join(', ')}, all`)
process.exit(1)
}
console.log(`\n📦 Installing hana-cli agent instructions`)
console.log(` Target: ${targetDir}`)
console.log(` Format: ${format === 'all' ? 'all formats' : FORMATS[format]?.name || format}`)
console.log('')
let copied = 0
// Copy shared reference files
console.log('📖 Reference files:')
for (const ref of REFERENCE_FILES) {
if (copyFile(instructionsDir, ref.src, targetDir, ref.dest)) copied++
}
// Copy format-specific files
for (const fmt of formats) {
const info = FORMATS[fmt]
console.log(`\n🤖 ${info.name} files:`)
for (const file of info.files) {
if (copyFile(instructionsDir, file.src, targetDir, file.dest)) copied++
}
}
console.log(`\n✅ Installed ${copied} files`)
if (!FORCE) {
console.log(` Use --force to overwrite existing files`)
}
process.exit(0)
}
main().catch(err => {
console.error('❌ Installation failed:', err.message)
process.exit(1)
})