Latest Threat Research:SANDWORM_MODE: Shai-Hulud-Style npm Worm Hijacks CI Workflows and Poisons AI Toolchains.Details
Socket
Book a DemoInstallSign in
Socket

heyi

Package Overview
Dependencies
Maintainers
1
Versions
6
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

heyi - npm Package Compare versions

Comparing version
3.0.0
to
3.1.0
+35
-4
bin/index.js

@@ -11,3 +11,3 @@ #!/usr/bin/env node

import { buildPrompt } from '../src/utils/prompt.js'
import { replaceVariables } from '../src/utils/variables.js'
import { findUndefinedVariables, promptForVariable, replaceVariables } from '../src/utils/variables.js'

@@ -90,2 +90,8 @@ const DEFAULT_MODEL = 'openai/gpt-4o-mini'

# Interactive variable prompting (will prompt for undefined variables)
$ heyi prompt "Translate {{text}} to {{language}}"
# Variable with description (shows during prompt)
$ heyi prompt "Explain {{topic description='What to explain'}} in simple terms"
# Environment variables

@@ -116,2 +122,6 @@ $ HEYI_MODEL=perplexity/sonar heyi prompt "Explain AI"

# Interactive variable prompting (will prompt for undefined variables)
$ heyi preset file.json
# (prompts for any variables in preset not provided via --var)
# Attach additional context

@@ -181,4 +191,16 @@ $ heyi preset file.json --file additional.txt

// Build the prompt and prefer the argument over stdin
const userPrompt = replaceVariables(prompt ?? stdinContent, options.vars)
// Get the user prompt (prefer argument over stdin)
const rawPrompt = prompt ?? stdinContent
// Find undefined variables in the prompt
const undefinedVars = findUndefinedVariables(rawPrompt, options.vars)
// Prompt user for each undefined variable
for (const varInfo of undefinedVars) {
const value = await promptForVariable(varInfo.name, varInfo.description)
options.vars[varInfo.name] = value
}
// Build the prompt with all variables replaced
const userPrompt = replaceVariables(rawPrompt, options.vars)
const finalPrompt = await buildPrompt(userPrompt, options.files, options.urls, options.crawler)

@@ -214,3 +236,12 @@

// Build the prompt
// Find undefined variables in the prompt
const undefinedVars = findUndefinedVariables(prompt, options.vars)
// Prompt user for each undefined variable
for (const varInfo of undefinedVars) {
const value = await promptForVariable(varInfo.name, varInfo.description)
options.vars[varInfo.name] = value
}
// Build the prompt with all variables replaced
const userPrompt = replaceVariables(prompt, options.vars)

@@ -217,0 +248,0 @@ const finalPrompt = await buildPrompt(userPrompt, options.files, options.urls, options.crawler)

+6
-6
{
"name": "heyi",
"version": "3.0.0",
"version": "3.1.0",
"description": "CLI tool to execute AI prompts with flexible output formatting",

@@ -34,8 +34,8 @@ "keywords": [

"@openrouter/ai-sdk-provider": "^1.5.4",
"ai": "^5.0.121",
"commander": "^14.0.2",
"ai": "^5.0.129",
"commander": "^14.0.3",
"dotenv": "^16.6.1",
"puppeteer": "^24.35.0",
"puppeteer": "^24.37.2",
"sanitize-html": "^2.17.0",
"zod": "^4.3.5"
"zod": "^4.3.6"
},

@@ -46,3 +46,3 @@ "devDependencies": {

"eslint": "^9.39.2",
"prettier": "^3.7.4"
"prettier": "^3.8.1"
},

@@ -49,0 +49,0 @@ "engines": {

@@ -65,2 +65,11 @@ # heyi

# Interactive variable prompting (prompts for undefined variables)
heyi prompt "Translate {{text}} to {{language}}"
# Will interactively prompt: text: [user enters value]
# language: [user enters value]
# Variable with description (shows custom prompt text)
heyi prompt "Explain {{topic description='What to explain'}} in simple terms"
# Will interactively prompt: What to explain (topic): [user enters value]
# Variable replacement with stdin

@@ -218,2 +227,52 @@ echo "Translate to {{language}}" | heyi prompt --var language="Spanish"

## Variables
The tool supports variable replacement in prompts using `{{variable}}` syntax. Variables can be provided via the `--var` flag or through interactive prompting.
### Variable Syntax
**Basic variable:**
```
{{variableName}}
```
**Variable with description (for interactive prompting):**
```
{{variableName description="Description shown to user"}}
```
### Variable Behavior
1. **Provided via --var flag**: Variables are directly replaced with the provided values
2. **Not provided**: The tool will interactively prompt the user to enter the value
3. **With description**: When prompting, the description is shown to help the user understand what to enter
### Variable Examples
```sh
# Provide variables via flag
heyi prompt "Translate {{text}} to {{language}}" --var text="Hello" --var language="Spanish"
# Interactive prompting for undefined variables
heyi prompt "Translate {{text}} to {{language}}"
# Prompts:
# text: [user enters value]
# language: [user enters value]
# Mix provided and interactive variables
heyi prompt "Translate {{text}} to {{language}}" --var language="French"
# Only prompts for 'text' since 'language' is provided
# Use descriptions for better user experience
heyi prompt "Explain {{topic description='Enter a topic to explain'}} in simple terms"
# Prompts:
# Enter a topic to explain (topic): [user enters value]
# Variables work in preset files too
heyi preset translate.json
# Prompts for any undefined variables in the preset's prompt
```
## Crawlers

@@ -220,0 +279,0 @@

@@ -112,4 +112,31 @@ import { readFile } from 'node:fs/promises'

// eslint-disable-next-line unicorn/consistent-function-scoping
const navigateTo = async (page, url) => {
try {
await page.goto(url, { waitUntil: 'networkidle2', timeout: 8000 })
} catch (error) {
// If it's a timeout error, continue with the content that's already loaded instead of failing
if (error.message.includes('Navigation timeout')) {
return
}
throw error
}
}
// eslint-disable-next-line unicorn/consistent-function-scoping
const getContent = async (page) => {
try {
return await page.content()
} catch (error) {
// A client-side navigation might have happened, try to recover by waiting for navigation
if (error.message.includes('Execution context was destroyed, most likely because of a navigation.')) {
await page.waitForNavigation({ waitUntil: 'networkidle2', timeout: 8000 })
return page.content()
}
throw error
}
}
const browser = await launch({
headless: true,
// These args are required for running in containerized environments (e.g., Docker, CI/CD)

@@ -121,21 +148,5 @@ args: ['--no-sandbox', '--disable-setuid-sandbox'],

const page = await browser.newPage()
await navigateTo(page, url)
const html = await getContent(page)
// Wait for network to be idle, with a 10-second timeout to prevent indefinite waiting.
// If timeout occurs, continue with whatever content is available.
// Wait for navigation first in case there are redirects.
try {
await Promise.all([
page.waitForNavigation({ timeout: 10000 }),
page.goto(url, { waitUntil: 'networkidle0', timeout: 10000 }),
])
} catch (error) {
// If it's a timeout error, continue with the content that's already loaded
// For other errors (e.g., network errors), rethrow
if (!error.message.includes('timeout') && !error.message.includes('Navigation timeout')) {
throw error
}
}
const html = await page.content()
// Sanitize HTML to extract only text content and avoid large data

@@ -142,0 +153,0 @@ const cleanText = sanitizeHtml(html, {

@@ -0,5 +1,74 @@

import readline from 'node:readline'
/**
* Extract all variables from a prompt string, including their metadata.
* Supports both {{variable}} and {{variable description="Description"}} syntax.
*
* @param {string} prompt - The prompt with variables
* @returns {Array<{name: string, description: string|null}>} Array of variable metadata
*/
export const extractVariables = (prompt) => {
// Match {{variable}} or {{variable description="..."}} or {{variable description='...'}}
const pattern = /\{\{\s*([a-zA-Z_][a-zA-Z0-9_]*)\s*(?:description\s*=\s*['"]([^'"]*)['"])?\s*\}\}/g
const variables = []
const seen = new Set()
let match
while ((match = pattern.exec(prompt)) !== null) {
const variableName = match[1]
const description = match[2] || null
// Only add each variable once (first occurrence)
if (!seen.has(variableName)) {
seen.add(variableName)
variables.push({
name: variableName,
description,
})
}
}
return variables
}
/**
* Find variables that are used in the prompt but not provided in the variables object.
*
* @param {string} prompt - The prompt with variables
* @param {object} variables - Object with variable names as keys
* @returns {Array<{name: string, description: string|null}>} Array of undefined variable metadata
*/
export const findUndefinedVariables = (prompt, variables = {}) => {
const allVariables = extractVariables(prompt)
return allVariables.filter((v) => !(v.name in variables))
}
/**
* Prompt user for a variable value interactively.
*
* @param {string} variableName - Name of the variable
* @param {string|null} description - Optional description for the variable
* @returns {Promise<string>} The value entered by the user
*/
export const promptForVariable = (variableName, description = null) => {
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout,
})
const prompt = description ? `${description} (${variableName}): ` : `${variableName}: `
return new Promise((resolve) => {
rl.question(prompt, (answer) => {
rl.close()
resolve(answer)
})
})
}
/**
* Replace variables in a prompt string.
* Handles both {{variable}} and {{variable description="Description"}} syntax.
*
* @param {string} prompt - The prompt with variables in {{variable}} format
* @param {string} prompt - The prompt with variables
* @param {object} variables - Object with variable names as keys and replacement values as values

@@ -12,3 +81,4 @@ * @returns {string} The prompt with variables replaced

for (const [variable, value] of Object.entries(variables)) {
const pattern = new RegExp(`{{\\s*${variable}\\s*}}`, 'g')
// Match both {{variable}} and {{variable description="..."}} or {{variable description='...'}}
const pattern = new RegExp(`\\{\\{\\s*${variable}\\s*(?:description\\s*=\\s*['"][^'"]*['"])?\\s*\\}\\}`, 'g')
result = result.replace(pattern, value)

@@ -15,0 +85,0 @@ }