heyi 3.0.0 → 3.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +59 -0
- package/bin/index.js +35 -4
- package/package.json +6 -6
- package/src/utils/input.js +30 -19
- package/src/utils/variables.js +72 -2
package/README.md
CHANGED
|
@@ -62,6 +62,15 @@ heyi prompt "Analyze top 3 tech companies" --format array --schema "z.object({na
|
|
|
62
62
|
heyi prompt "Preset in {{language}}" --var language="German"
|
|
63
63
|
heyi prompt "Preset in {{input}} and output in {{output}}" --var input="German" --var output="English"
|
|
64
64
|
|
|
65
|
+
# Interactive variable prompting (prompts for undefined variables)
|
|
66
|
+
heyi prompt "Translate {{text}} to {{language}}"
|
|
67
|
+
# Will interactively prompt: text: [user enters value]
|
|
68
|
+
# language: [user enters value]
|
|
69
|
+
|
|
70
|
+
# Variable with description (shows custom prompt text)
|
|
71
|
+
heyi prompt "Explain {{topic description='What to explain'}} in simple terms"
|
|
72
|
+
# Will interactively prompt: What to explain (topic): [user enters value]
|
|
73
|
+
|
|
65
74
|
# Variable replacement with stdin
|
|
66
75
|
echo "Translate to {{language}}" | heyi prompt --var language="Spanish"
|
|
67
76
|
|
|
@@ -214,6 +223,56 @@ The tool uses Zod schemas to ensure the AI model returns data in the requested f
|
|
|
214
223
|
- Object array: `--format array --schema "z.object({name:z.string(),age:z.number()})"`
|
|
215
224
|
- Single object: `--format object --schema "z.object({total:z.number(),items:z.array(z.string())})"`
|
|
216
225
|
|
|
226
|
+
## Variables
|
|
227
|
+
|
|
228
|
+
The tool supports variable replacement in prompts using `{{variable}}` syntax. Variables can be provided via the `--var` flag or through interactive prompting.
|
|
229
|
+
|
|
230
|
+
### Variable Syntax
|
|
231
|
+
|
|
232
|
+
**Basic variable:**
|
|
233
|
+
|
|
234
|
+
```
|
|
235
|
+
{{variableName}}
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
**Variable with description (for interactive prompting):**
|
|
239
|
+
|
|
240
|
+
```
|
|
241
|
+
{{variableName description="Description shown to user"}}
|
|
242
|
+
```
|
|
243
|
+
|
|
244
|
+
### Variable Behavior
|
|
245
|
+
|
|
246
|
+
1. **Provided via --var flag**: Variables are directly replaced with the provided values
|
|
247
|
+
2. **Not provided**: The tool will interactively prompt the user to enter the value
|
|
248
|
+
3. **With description**: When prompting, the description is shown to help the user understand what to enter
|
|
249
|
+
|
|
250
|
+
### Variable Examples
|
|
251
|
+
|
|
252
|
+
```sh
|
|
253
|
+
# Provide variables via flag
|
|
254
|
+
heyi prompt "Translate {{text}} to {{language}}" --var text="Hello" --var language="Spanish"
|
|
255
|
+
|
|
256
|
+
# Interactive prompting for undefined variables
|
|
257
|
+
heyi prompt "Translate {{text}} to {{language}}"
|
|
258
|
+
# Prompts:
|
|
259
|
+
# text: [user enters value]
|
|
260
|
+
# language: [user enters value]
|
|
261
|
+
|
|
262
|
+
# Mix provided and interactive variables
|
|
263
|
+
heyi prompt "Translate {{text}} to {{language}}" --var language="French"
|
|
264
|
+
# Only prompts for 'text' since 'language' is provided
|
|
265
|
+
|
|
266
|
+
# Use descriptions for better user experience
|
|
267
|
+
heyi prompt "Explain {{topic description='Enter a topic to explain'}} in simple terms"
|
|
268
|
+
# Prompts:
|
|
269
|
+
# Enter a topic to explain (topic): [user enters value]
|
|
270
|
+
|
|
271
|
+
# Variables work in preset files too
|
|
272
|
+
heyi preset translate.json
|
|
273
|
+
# Prompts for any undefined variables in the preset's prompt
|
|
274
|
+
```
|
|
275
|
+
|
|
217
276
|
## Crawlers
|
|
218
277
|
|
|
219
278
|
The tool supports two crawlers for fetching content from URLs:
|
package/bin/index.js
CHANGED
|
@@ -8,7 +8,7 @@ import { hasFlag } from '../src/utils/argv.js'
|
|
|
8
8
|
import { hasStdinData, readStdin } from '../src/utils/input.js'
|
|
9
9
|
import { loadPreset } from '../src/utils/preset.js'
|
|
10
10
|
import { buildPrompt } from '../src/utils/prompt.js'
|
|
11
|
-
import { replaceVariables } from '../src/utils/variables.js'
|
|
11
|
+
import { findUndefinedVariables, promptForVariable, replaceVariables } from '../src/utils/variables.js'
|
|
12
12
|
|
|
13
13
|
const DEFAULT_MODEL = 'openai/gpt-4o-mini'
|
|
14
14
|
const DEFAULT_CRAWLER = 'fetch'
|
|
@@ -86,6 +86,12 @@ Examples:
|
|
|
86
86
|
# Variable replacement
|
|
87
87
|
$ heyi prompt "Preset in {{language}}" --var language="German"
|
|
88
88
|
|
|
89
|
+
# Interactive variable prompting (will prompt for undefined variables)
|
|
90
|
+
$ heyi prompt "Translate {{text}} to {{language}}"
|
|
91
|
+
|
|
92
|
+
# Variable with description (shows during prompt)
|
|
93
|
+
$ heyi prompt "Explain {{topic description='What to explain'}} in simple terms"
|
|
94
|
+
|
|
89
95
|
# Environment variables
|
|
90
96
|
$ HEYI_MODEL=perplexity/sonar heyi prompt "Explain AI"
|
|
91
97
|
$ HEYI_API_KEY=your-key heyi prompt "Hello, AI!"
|
|
@@ -111,6 +117,10 @@ Examples:
|
|
|
111
117
|
# Variable replacement
|
|
112
118
|
$ heyi preset file.json --var language=german
|
|
113
119
|
|
|
120
|
+
# Interactive variable prompting (will prompt for undefined variables)
|
|
121
|
+
$ heyi preset file.json
|
|
122
|
+
# (prompts for any variables in preset not provided via --var)
|
|
123
|
+
|
|
114
124
|
# Attach additional context
|
|
115
125
|
$ heyi preset file.json --file additional.txt
|
|
116
126
|
$ heyi preset file.json --url https://example.com/additional.html
|
|
@@ -175,8 +185,20 @@ const executePromptAction = async (prompt, flags) => {
|
|
|
175
185
|
// Build options from flags
|
|
176
186
|
const options = flagsToOptions(flags)
|
|
177
187
|
|
|
178
|
-
//
|
|
179
|
-
const
|
|
188
|
+
// Get the user prompt (prefer argument over stdin)
|
|
189
|
+
const rawPrompt = prompt ?? stdinContent
|
|
190
|
+
|
|
191
|
+
// Find undefined variables in the prompt
|
|
192
|
+
const undefinedVars = findUndefinedVariables(rawPrompt, options.vars)
|
|
193
|
+
|
|
194
|
+
// Prompt user for each undefined variable
|
|
195
|
+
for (const varInfo of undefinedVars) {
|
|
196
|
+
const value = await promptForVariable(varInfo.name, varInfo.description)
|
|
197
|
+
options.vars[varInfo.name] = value
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
// Build the prompt with all variables replaced
|
|
201
|
+
const userPrompt = replaceVariables(rawPrompt, options.vars)
|
|
180
202
|
const finalPrompt = await buildPrompt(userPrompt, options.files, options.urls, options.crawler)
|
|
181
203
|
|
|
182
204
|
const result = await executePrompt(finalPrompt, {
|
|
@@ -207,7 +229,16 @@ const executePresetAction = async (preset, flags) => {
|
|
|
207
229
|
// Build options from flags and merge with preset
|
|
208
230
|
const options = mergeOptionsWithPreset(flagsToOptions(flags), presetContent)
|
|
209
231
|
|
|
210
|
-
//
|
|
232
|
+
// Find undefined variables in the prompt
|
|
233
|
+
const undefinedVars = findUndefinedVariables(prompt, options.vars)
|
|
234
|
+
|
|
235
|
+
// Prompt user for each undefined variable
|
|
236
|
+
for (const varInfo of undefinedVars) {
|
|
237
|
+
const value = await promptForVariable(varInfo.name, varInfo.description)
|
|
238
|
+
options.vars[varInfo.name] = value
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
// Build the prompt with all variables replaced
|
|
211
242
|
const userPrompt = replaceVariables(prompt, options.vars)
|
|
212
243
|
const finalPrompt = await buildPrompt(userPrompt, options.files, options.urls, options.crawler)
|
|
213
244
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "heyi",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.1.0",
|
|
4
4
|
"description": "CLI tool to execute AI prompts with flexible output formatting",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"ai",
|
|
@@ -31,18 +31,18 @@
|
|
|
31
31
|
},
|
|
32
32
|
"dependencies": {
|
|
33
33
|
"@openrouter/ai-sdk-provider": "^1.5.4",
|
|
34
|
-
"ai": "^5.0.
|
|
35
|
-
"commander": "^14.0.
|
|
34
|
+
"ai": "^5.0.129",
|
|
35
|
+
"commander": "^14.0.3",
|
|
36
36
|
"dotenv": "^16.6.1",
|
|
37
|
-
"puppeteer": "^24.
|
|
37
|
+
"puppeteer": "^24.37.2",
|
|
38
38
|
"sanitize-html": "^2.17.0",
|
|
39
|
-
"zod": "^4.3.
|
|
39
|
+
"zod": "^4.3.6"
|
|
40
40
|
},
|
|
41
41
|
"devDependencies": {
|
|
42
42
|
"@electerious/eslint-config": "^5.2.1",
|
|
43
43
|
"@electerious/prettier-config": "^4.0.0",
|
|
44
44
|
"eslint": "^9.39.2",
|
|
45
|
-
"prettier": "^3.
|
|
45
|
+
"prettier": "^3.8.1"
|
|
46
46
|
},
|
|
47
47
|
"engines": {
|
|
48
48
|
"node": ">=22"
|
package/src/utils/input.js
CHANGED
|
@@ -109,32 +109,43 @@ const fetchUrlContentWithFetch = async (url) => {
|
|
|
109
109
|
const fetchUrlContentWithChrome = async (url) => {
|
|
110
110
|
validateUrl(url)
|
|
111
111
|
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
112
|
+
// eslint-disable-next-line unicorn/consistent-function-scoping
|
|
113
|
+
const navigateTo = async (page, url) => {
|
|
114
|
+
try {
|
|
115
|
+
await page.goto(url, { waitUntil: 'networkidle2', timeout: 8000 })
|
|
116
|
+
} catch (error) {
|
|
117
|
+
// If it's a timeout error, continue with the content that's already loaded instead of failing
|
|
118
|
+
if (error.message.includes('Navigation timeout')) {
|
|
119
|
+
return
|
|
120
|
+
}
|
|
117
121
|
|
|
118
|
-
|
|
119
|
-
|
|
122
|
+
throw error
|
|
123
|
+
}
|
|
124
|
+
}
|
|
120
125
|
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
// Wait for navigation first in case there are redirects.
|
|
126
|
+
// eslint-disable-next-line unicorn/consistent-function-scoping
|
|
127
|
+
const getContent = async (page) => {
|
|
124
128
|
try {
|
|
125
|
-
await
|
|
126
|
-
page.waitForNavigation({ timeout: 10000 }),
|
|
127
|
-
page.goto(url, { waitUntil: 'networkidle0', timeout: 10000 }),
|
|
128
|
-
])
|
|
129
|
+
return await page.content()
|
|
129
130
|
} catch (error) {
|
|
130
|
-
//
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
131
|
+
// A client-side navigation might have happened, try to recover by waiting for navigation
|
|
132
|
+
if (error.message.includes('Execution context was destroyed, most likely because of a navigation.')) {
|
|
133
|
+
await page.waitForNavigation({ waitUntil: 'networkidle2', timeout: 8000 })
|
|
134
|
+
return page.content()
|
|
134
135
|
}
|
|
136
|
+
throw error
|
|
135
137
|
}
|
|
138
|
+
}
|
|
136
139
|
|
|
137
|
-
|
|
140
|
+
const browser = await launch({
|
|
141
|
+
// These args are required for running in containerized environments (e.g., Docker, CI/CD)
|
|
142
|
+
args: ['--no-sandbox', '--disable-setuid-sandbox'],
|
|
143
|
+
})
|
|
144
|
+
|
|
145
|
+
try {
|
|
146
|
+
const page = await browser.newPage()
|
|
147
|
+
await navigateTo(page, url)
|
|
148
|
+
const html = await getContent(page)
|
|
138
149
|
|
|
139
150
|
// Sanitize HTML to extract only text content and avoid large data
|
|
140
151
|
const cleanText = sanitizeHtml(html, {
|
package/src/utils/variables.js
CHANGED
|
@@ -1,7 +1,76 @@
|
|
|
1
|
+
import readline from 'node:readline'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Extract all variables from a prompt string, including their metadata.
|
|
5
|
+
* Supports both {{variable}} and {{variable description="Description"}} syntax.
|
|
6
|
+
*
|
|
7
|
+
* @param {string} prompt - The prompt with variables
|
|
8
|
+
* @returns {Array<{name: string, description: string|null}>} Array of variable metadata
|
|
9
|
+
*/
|
|
10
|
+
export const extractVariables = (prompt) => {
|
|
11
|
+
// Match {{variable}} or {{variable description="..."}} or {{variable description='...'}}
|
|
12
|
+
const pattern = /\{\{\s*([a-zA-Z_][a-zA-Z0-9_]*)\s*(?:description\s*=\s*['"]([^'"]*)['"])?\s*\}\}/g
|
|
13
|
+
const variables = []
|
|
14
|
+
const seen = new Set()
|
|
15
|
+
|
|
16
|
+
let match
|
|
17
|
+
while ((match = pattern.exec(prompt)) !== null) {
|
|
18
|
+
const variableName = match[1]
|
|
19
|
+
const description = match[2] || null
|
|
20
|
+
|
|
21
|
+
// Only add each variable once (first occurrence)
|
|
22
|
+
if (!seen.has(variableName)) {
|
|
23
|
+
seen.add(variableName)
|
|
24
|
+
variables.push({
|
|
25
|
+
name: variableName,
|
|
26
|
+
description,
|
|
27
|
+
})
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
return variables
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Find variables that are used in the prompt but not provided in the variables object.
|
|
36
|
+
*
|
|
37
|
+
* @param {string} prompt - The prompt with variables
|
|
38
|
+
* @param {object} variables - Object with variable names as keys
|
|
39
|
+
* @returns {Array<{name: string, description: string|null}>} Array of undefined variable metadata
|
|
40
|
+
*/
|
|
41
|
+
export const findUndefinedVariables = (prompt, variables = {}) => {
|
|
42
|
+
const allVariables = extractVariables(prompt)
|
|
43
|
+
return allVariables.filter((v) => !(v.name in variables))
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Prompt user for a variable value interactively.
|
|
48
|
+
*
|
|
49
|
+
* @param {string} variableName - Name of the variable
|
|
50
|
+
* @param {string|null} description - Optional description for the variable
|
|
51
|
+
* @returns {Promise<string>} The value entered by the user
|
|
52
|
+
*/
|
|
53
|
+
export const promptForVariable = (variableName, description = null) => {
|
|
54
|
+
const rl = readline.createInterface({
|
|
55
|
+
input: process.stdin,
|
|
56
|
+
output: process.stdout,
|
|
57
|
+
})
|
|
58
|
+
|
|
59
|
+
const prompt = description ? `${description} (${variableName}): ` : `${variableName}: `
|
|
60
|
+
|
|
61
|
+
return new Promise((resolve) => {
|
|
62
|
+
rl.question(prompt, (answer) => {
|
|
63
|
+
rl.close()
|
|
64
|
+
resolve(answer)
|
|
65
|
+
})
|
|
66
|
+
})
|
|
67
|
+
}
|
|
68
|
+
|
|
1
69
|
/**
|
|
2
70
|
* Replace variables in a prompt string.
|
|
71
|
+
* Handles both {{variable}} and {{variable description="Description"}} syntax.
|
|
3
72
|
*
|
|
4
|
-
* @param {string} prompt - The prompt with variables
|
|
73
|
+
* @param {string} prompt - The prompt with variables
|
|
5
74
|
* @param {object} variables - Object with variable names as keys and replacement values as values
|
|
6
75
|
* @returns {string} The prompt with variables replaced
|
|
7
76
|
*/
|
|
@@ -9,7 +78,8 @@ export const replaceVariables = (prompt, variables = {}) => {
|
|
|
9
78
|
let result = prompt
|
|
10
79
|
|
|
11
80
|
for (const [variable, value] of Object.entries(variables)) {
|
|
12
|
-
|
|
81
|
+
// Match both {{variable}} and {{variable description="..."}} or {{variable description='...'}}
|
|
82
|
+
const pattern = new RegExp(`\\{\\{\\s*${variable}\\s*(?:description\\s*=\\s*['"][^'"]*['"])?\\s*\\}\\}`, 'g')
|
|
13
83
|
result = result.replace(pattern, value)
|
|
14
84
|
}
|
|
15
85
|
|