Merge pull request #118 from freeflowuniverse/development_heroprompt
Development heroprompt
This commit is contained in:
@@ -8,7 +8,51 @@ module codewalker
|
||||
// - Lines starting with '#' are comments; empty lines ignored
|
||||
// No negation support for simplicity
|
||||
|
||||
const default_gitignore = '__pycache__/\n*.py[cod]\n*.so\n.Python\nbuild/\ndevelop-eggs/\ndownloads/\neggs/\n.eggs/\nlib/\nlib64/\nparts/\nsdist/\nvar/\nwheels/\n*.egg-info/\n.installed.cfg\n*.egg\n.env\n.venv\nvenv/\n.tox/\n.nox/\n.coverage\n.coveragerc\ncoverage.xml\n*.cover\n*.gem\n*.pyc\n.cache\n.pytest_cache/\n.mypy_cache/\n.hypothesis/\n'
|
||||
const default_gitignore = '
|
||||
.git/
|
||||
.svn/
|
||||
.hg/
|
||||
.bzr/
|
||||
node_modules/
|
||||
__pycache__/
|
||||
*.py[cod]
|
||||
*.so
|
||||
.Python
|
||||
build/
|
||||
develop-eggs/
|
||||
downloads/
|
||||
eggs/
|
||||
.eggs/
|
||||
lib/
|
||||
lib64/
|
||||
parts/
|
||||
sdist/
|
||||
var/
|
||||
wheels/
|
||||
*.egg-info/
|
||||
.installed.cfg
|
||||
*.egg
|
||||
.env
|
||||
.venv
|
||||
venv/
|
||||
.tox/
|
||||
.nox/
|
||||
.coverage
|
||||
.coveragerc
|
||||
coverage.xml
|
||||
*.cover
|
||||
*.gem
|
||||
*.pyc
|
||||
.cache
|
||||
.pytest_cache/
|
||||
.mypy_cache/
|
||||
.hypothesis/
|
||||
.DS_Store
|
||||
Thumbs.db
|
||||
*.tmp
|
||||
*.temp
|
||||
*.log
|
||||
'
|
||||
|
||||
struct IgnoreRule {
|
||||
base string // relative dir from source root where the ignore file lives ('' means global)
|
||||
|
||||
244
lib/develop/codewalker/tree.v
Normal file
244
lib/develop/codewalker/tree.v
Normal file
@@ -0,0 +1,244 @@
|
||||
module codewalker
|
||||
|
||||
import os
|
||||
|
||||
// build_selected_tree renders a minimal tree of the given file paths.
|
||||
// - files: absolute or relative file paths
|
||||
// - base_root: if provided and files are absolute, the tree is rendered relative to this root
|
||||
// The output marks files with a trailing " *" like the existing map convention.
|
||||
pub fn build_selected_tree(files []string, base_root string) string {
|
||||
mut rels := []string{}
|
||||
for p in files {
|
||||
mut rp := p
|
||||
if base_root.len > 0 && rp.starts_with(base_root) {
|
||||
rp = rp[base_root.len..]
|
||||
if rp.len > 0 && rp.starts_with('/') {
|
||||
rp = rp[1..]
|
||||
}
|
||||
}
|
||||
rels << rp
|
||||
}
|
||||
rels.sort()
|
||||
return tree_from_rel_paths(rels, '')
|
||||
}
|
||||
|
||||
fn tree_from_rel_paths(paths []string, prefix string) string {
|
||||
mut out := ''
|
||||
// group into directories and files at the current level
|
||||
mut dir_children := map[string][]string{}
|
||||
mut files := []string{}
|
||||
for p in paths {
|
||||
parts := p.split('/')
|
||||
if parts.len <= 1 {
|
||||
if p.len > 0 {
|
||||
files << parts[0]
|
||||
}
|
||||
} else {
|
||||
key := parts[0]
|
||||
rest := parts[1..].join('/')
|
||||
mut arr := dir_children[key] or { []string{} }
|
||||
arr << rest
|
||||
dir_children[key] = arr
|
||||
}
|
||||
}
|
||||
mut dir_names := dir_children.keys()
|
||||
dir_names.sort()
|
||||
files.sort()
|
||||
// render directories first, then files
|
||||
for j, d in dir_names {
|
||||
is_last_dir := j == dir_names.len - 1
|
||||
connector := if is_last_dir && files.len == 0 { '└── ' } else { '├── ' }
|
||||
out += '${prefix}${connector}${d}\n'
|
||||
child_prefix := if is_last_dir && files.len == 0 {
|
||||
prefix + ' '
|
||||
} else {
|
||||
prefix + '│ '
|
||||
}
|
||||
out += tree_from_rel_paths(dir_children[d], child_prefix)
|
||||
}
|
||||
for i, f in files {
|
||||
file_connector := if i == files.len - 1 { '└── ' } else { '├── ' }
|
||||
out += '${prefix}${file_connector}${f} *\n'
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
// resolve_path resolves a relative path against a base path.
|
||||
// If rel_path is absolute, returns it as-is.
|
||||
// If rel_path is empty, returns base_path.
|
||||
pub fn resolve_path(base_path string, rel_path string) string {
|
||||
if rel_path.len == 0 {
|
||||
return base_path
|
||||
}
|
||||
if os.is_abs_path(rel_path) {
|
||||
return rel_path
|
||||
}
|
||||
return os.join_path(base_path, rel_path)
|
||||
}
|
||||
|
||||
pub struct DirItem {
|
||||
pub:
|
||||
name string
|
||||
typ string
|
||||
}
|
||||
|
||||
// list_directory lists the contents of a directory.
|
||||
// - base_path: workspace base path
|
||||
// - rel_path: relative path from base (or absolute path)
|
||||
// Returns a list of DirItem with name and type (file/directory).
|
||||
pub fn list_directory(base_path string, rel_path string) ![]DirItem {
|
||||
dir := resolve_path(base_path, rel_path)
|
||||
if dir.len == 0 {
|
||||
return error('base_path not set')
|
||||
}
|
||||
entries := os.ls(dir) or { return error('cannot list directory') }
|
||||
mut out := []DirItem{}
|
||||
for e in entries {
|
||||
full := os.join_path(dir, e)
|
||||
if os.is_dir(full) {
|
||||
out << DirItem{
|
||||
name: e
|
||||
typ: 'directory'
|
||||
}
|
||||
} else if os.is_file(full) {
|
||||
out << DirItem{
|
||||
name: e
|
||||
typ: 'file'
|
||||
}
|
||||
}
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
// list_directory_filtered lists the contents of a directory with ignore filtering applied.
|
||||
// - base_path: workspace base path
|
||||
// - rel_path: relative path from base (or absolute path)
|
||||
// - ignore_matcher: IgnoreMatcher to filter out ignored files/directories
|
||||
// Returns a list of DirItem with name and type (file/directory), filtered by ignore patterns.
|
||||
pub fn list_directory_filtered(base_path string, rel_path string, ignore_matcher &IgnoreMatcher) ![]DirItem {
|
||||
dir := resolve_path(base_path, rel_path)
|
||||
if dir.len == 0 {
|
||||
return error('base_path not set')
|
||||
}
|
||||
entries := os.ls(dir) or { return error('cannot list directory') }
|
||||
mut out := []DirItem{}
|
||||
for e in entries {
|
||||
full := os.join_path(dir, e)
|
||||
|
||||
// Calculate relative path from base_path for ignore checking
|
||||
mut check_path := if rel_path.len > 0 {
|
||||
if rel_path.ends_with('/') { rel_path + e } else { rel_path + '/' + e }
|
||||
} else {
|
||||
e
|
||||
}
|
||||
|
||||
// For directories, also check with trailing slash
|
||||
is_directory := os.is_dir(full)
|
||||
mut should_ignore := ignore_matcher.is_ignored(check_path)
|
||||
if is_directory && !should_ignore {
|
||||
// Also check directory pattern with trailing slash
|
||||
should_ignore = ignore_matcher.is_ignored(check_path + '/')
|
||||
}
|
||||
|
||||
// Check if this entry should be ignored
|
||||
if should_ignore {
|
||||
continue
|
||||
}
|
||||
|
||||
if is_directory {
|
||||
out << DirItem{
|
||||
name: e
|
||||
typ: 'directory'
|
||||
}
|
||||
} else if os.is_file(full) {
|
||||
out << DirItem{
|
||||
name: e
|
||||
typ: 'file'
|
||||
}
|
||||
}
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
// list_files_recursive recursively lists all files in a directory
|
||||
pub fn list_files_recursive(root string) []string {
|
||||
mut out := []string{}
|
||||
entries := os.ls(root) or { return out }
|
||||
for e in entries {
|
||||
fp := os.join_path(root, e)
|
||||
if os.is_dir(fp) {
|
||||
out << list_files_recursive(fp)
|
||||
} else if os.is_file(fp) {
|
||||
out << fp
|
||||
}
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
// build_file_tree_fs builds a file system tree for given root directories
|
||||
pub fn build_file_tree_fs(roots []string, prefix string) string {
|
||||
mut out := ''
|
||||
for i, root in roots {
|
||||
if !os.is_dir(root) {
|
||||
continue
|
||||
}
|
||||
connector := if i == roots.len - 1 { '└── ' } else { '├── ' }
|
||||
out += '${prefix}${connector}${os.base(root)}\n'
|
||||
child_prefix := if i == roots.len - 1 { prefix + ' ' } else { prefix + '│ ' }
|
||||
// list children under root
|
||||
entries := os.ls(root) or { []string{} }
|
||||
// sort: dirs first then files
|
||||
mut dirs := []string{}
|
||||
mut files := []string{}
|
||||
for e in entries {
|
||||
fp := os.join_path(root, e)
|
||||
if os.is_dir(fp) {
|
||||
dirs << fp
|
||||
} else if os.is_file(fp) {
|
||||
files << fp
|
||||
}
|
||||
}
|
||||
dirs.sort()
|
||||
files.sort()
|
||||
// files
|
||||
for j, f in files {
|
||||
file_connector := if j == files.len - 1 && dirs.len == 0 {
|
||||
'└── '
|
||||
} else {
|
||||
'├── '
|
||||
}
|
||||
out += '${child_prefix}${file_connector}${os.base(f)} *\n'
|
||||
}
|
||||
// subdirectories
|
||||
for j, d in dirs {
|
||||
sub_connector := if j == dirs.len - 1 { '└── ' } else { '├── ' }
|
||||
out += '${child_prefix}${sub_connector}${os.base(d)}\n'
|
||||
sub_prefix := if j == dirs.len - 1 {
|
||||
child_prefix + ' '
|
||||
} else {
|
||||
child_prefix + '│ '
|
||||
}
|
||||
out += build_file_tree_fs([d], sub_prefix)
|
||||
}
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
// build_file_tree_selected builds a minimal tree that contains only the selected files.
|
||||
// The tree is rendered relative to base_root when provided.
|
||||
pub fn build_file_tree_selected(files []string, base_root string) string {
|
||||
mut rels := []string{}
|
||||
for fo in files {
|
||||
mut rp := fo
|
||||
if base_root.len > 0 && rp.starts_with(base_root) {
|
||||
// make path relative to the base root
|
||||
rp = rp[base_root.len..]
|
||||
if rp.len > 0 && rp.starts_with('/') {
|
||||
rp = rp[1..]
|
||||
}
|
||||
}
|
||||
rels << rp
|
||||
}
|
||||
rels.sort()
|
||||
return tree_from_rel_paths(rels, '')
|
||||
}
|
||||
@@ -5,9 +5,10 @@ import os
|
||||
|
||||
pub struct HeropromptChild {
|
||||
pub mut:
|
||||
content string
|
||||
path pathlib.Path
|
||||
name string
|
||||
content string
|
||||
path pathlib.Path
|
||||
name string
|
||||
include_tree bool // when true and this child is a dir, include full subtree in maps/contents
|
||||
}
|
||||
|
||||
// Utility function to get file extension with special handling for common files
|
||||
|
||||
@@ -39,12 +39,13 @@ pub fn (mut wsp Workspace) add_dir(args AddDirParams) !HeropromptChild {
|
||||
}
|
||||
|
||||
mut ch := HeropromptChild{
|
||||
path: pathlib.Path{
|
||||
path: pathlib.Path{
|
||||
path: abs_path
|
||||
cat: .dir
|
||||
exist: .yes
|
||||
}
|
||||
name: name
|
||||
name: name
|
||||
include_tree: true
|
||||
}
|
||||
wsp.children << ch
|
||||
wsp.save()!
|
||||
@@ -220,55 +221,29 @@ pub:
|
||||
typ string @[json: 'type']
|
||||
}
|
||||
|
||||
pub fn (wsp &Workspace) list() ![]ListItem {
|
||||
mut dir := wsp.base_path
|
||||
if dir.len == 0 {
|
||||
return error('workspace base_path not set')
|
||||
}
|
||||
|
||||
if !os.is_abs_path(dir) {
|
||||
dir = os.join_path(wsp.base_path, dir)
|
||||
}
|
||||
|
||||
entries := os.ls(dir) or { return error('cannot list directory') }
|
||||
pub fn (wsp &Workspace) list_dir(rel_path string) ![]ListItem {
|
||||
// Create an ignore matcher with default patterns
|
||||
ignore_matcher := codewalker.gitignore_matcher_new()
|
||||
items := codewalker.list_directory_filtered(wsp.base_path, rel_path, &ignore_matcher)!
|
||||
mut out := []ListItem{}
|
||||
for e in entries {
|
||||
full := os.join_path(dir, e)
|
||||
if os.is_dir(full) {
|
||||
out << ListItem{
|
||||
name: e
|
||||
typ: 'directory'
|
||||
}
|
||||
} else if os.is_file(full) {
|
||||
out << ListItem{
|
||||
name: e
|
||||
typ: 'file'
|
||||
}
|
||||
for item in items {
|
||||
out << ListItem{
|
||||
name: item.name
|
||||
typ: item.typ
|
||||
}
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
pub fn (wsp &Workspace) list() ![]ListItem {
|
||||
return wsp.list_dir('')
|
||||
}
|
||||
|
||||
// Get the currently selected children (copy)
|
||||
pub fn (wsp Workspace) selected_children() []HeropromptChild {
|
||||
return wsp.children.clone()
|
||||
}
|
||||
|
||||
// Build utilities
|
||||
fn list_files_recursive(root string) []string {
|
||||
mut out := []string{}
|
||||
entries := os.ls(root) or { return out }
|
||||
for e in entries {
|
||||
fp := os.join_path(root, e)
|
||||
if os.is_dir(fp) {
|
||||
out << list_files_recursive(fp)
|
||||
} else if os.is_file(fp) {
|
||||
out << fp
|
||||
}
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
// build_file_content generates formatted content for all selected files (and all files under selected dirs)
|
||||
fn (wsp Workspace) build_file_content() !string {
|
||||
mut content := ''
|
||||
@@ -295,7 +270,7 @@ fn (wsp Workspace) build_file_content() !string {
|
||||
}
|
||||
// files under selected directories, using CodeWalker for filtered traversal
|
||||
for ch in wsp.children {
|
||||
if ch.path.cat == .dir {
|
||||
if ch.path.cat == .dir && ch.include_tree {
|
||||
mut cw := codewalker.new(codewalker.CodeWalkerArgs{})!
|
||||
mut fm := cw.filemap_get(path: ch.path.path)!
|
||||
for rel, fc in fm.content {
|
||||
@@ -316,64 +291,6 @@ fn (wsp Workspace) build_file_content() !string {
|
||||
return content
|
||||
}
|
||||
|
||||
// Minimal tree builder for selected directories only; marks files with *
|
||||
fn build_file_tree_fs(roots []HeropromptChild, prefix string) string {
|
||||
mut out := ''
|
||||
for i, root in roots {
|
||||
if root.path.cat != .dir {
|
||||
continue
|
||||
}
|
||||
connector := if i == roots.len - 1 { '└── ' } else { '├── ' }
|
||||
out += '${prefix}${connector}${root.name}\n'
|
||||
child_prefix := if i == roots.len - 1 { prefix + ' ' } else { prefix + '│ ' }
|
||||
// list children under root
|
||||
entries := os.ls(root.path.path) or { []string{} }
|
||||
// sort: dirs first then files
|
||||
mut dirs := []string{}
|
||||
mut files := []string{}
|
||||
for e in entries {
|
||||
fp := os.join_path(root.path.path, e)
|
||||
if os.is_dir(fp) {
|
||||
dirs << fp
|
||||
} else if os.is_file(fp) {
|
||||
files << fp
|
||||
}
|
||||
}
|
||||
dirs.sort()
|
||||
files.sort()
|
||||
// files
|
||||
for j, f in files {
|
||||
file_connector := if j == files.len - 1 && dirs.len == 0 {
|
||||
'└── '
|
||||
} else {
|
||||
'├── '
|
||||
}
|
||||
out += '${child_prefix}${file_connector}${os.base(f)} *\n'
|
||||
}
|
||||
// subdirectories
|
||||
for j, d in dirs {
|
||||
sub_connector := if j == dirs.len - 1 { '└── ' } else { '├── ' }
|
||||
out += '${child_prefix}${sub_connector}${os.base(d)}\n'
|
||||
sub_prefix := if j == dirs.len - 1 {
|
||||
child_prefix + ' '
|
||||
} else {
|
||||
child_prefix + '│ '
|
||||
}
|
||||
out += build_file_tree_fs([
|
||||
HeropromptChild{
|
||||
path: pathlib.Path{
|
||||
path: d
|
||||
cat: .dir
|
||||
exist: .yes
|
||||
}
|
||||
name: os.base(d)
|
||||
},
|
||||
], sub_prefix)
|
||||
}
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
pub struct HeropromptTmpPrompt {
|
||||
pub mut:
|
||||
user_instructions string
|
||||
@@ -392,42 +309,63 @@ fn (wsp Workspace) build_file_map() string {
|
||||
mut roots := []HeropromptChild{}
|
||||
mut files_only := []HeropromptChild{}
|
||||
for ch in wsp.children {
|
||||
if ch.path.cat == .dir {
|
||||
roots << ch
|
||||
if ch.path.cat == .dir && ch.include_tree {
|
||||
roots << ch // only include directories explicitly marked to include subtree
|
||||
} else if ch.path.cat == .file {
|
||||
files_only << ch
|
||||
}
|
||||
}
|
||||
if roots.len > 0 {
|
||||
base_path := roots[0].path.path
|
||||
parent_path := if base_path.contains('/') {
|
||||
base_path.split('/')[..base_path.split('/').len - 1].join('/')
|
||||
if roots.len > 0 || files_only.len > 0 {
|
||||
// derive a parent path for display
|
||||
mut parent_path := ''
|
||||
if roots.len > 0 {
|
||||
base_path := roots[0].path.path
|
||||
parent_path = if base_path.contains('/') {
|
||||
base_path.split('/')[..base_path.split('/').len - 1].join('/')
|
||||
} else {
|
||||
base_path
|
||||
}
|
||||
} else {
|
||||
base_path
|
||||
// no roots; show workspace base if set, else the parent of first file
|
||||
parent_path = if wsp.base_path.len > 0 {
|
||||
wsp.base_path
|
||||
} else if files_only.len > 0 {
|
||||
os.dir(files_only[0].path.path)
|
||||
} else {
|
||||
''
|
||||
}
|
||||
}
|
||||
// metadata
|
||||
mut total_files := 0
|
||||
mut total_content_length := 0
|
||||
mut file_extensions := map[string]int{}
|
||||
// files under dirs
|
||||
for r in roots {
|
||||
for f in list_files_recursive(r.path.path) {
|
||||
total_files++
|
||||
ext := get_file_extension(os.base(f))
|
||||
if ext.len > 0 {
|
||||
file_extensions[ext] = file_extensions[ext] + 1
|
||||
// files under dirs (only when roots present)
|
||||
if roots.len > 0 {
|
||||
for r in roots {
|
||||
for f in codewalker.list_files_recursive(r.path.path) {
|
||||
total_files++
|
||||
ext := get_file_extension(os.base(f))
|
||||
if ext.len > 0 {
|
||||
file_extensions[ext] = file_extensions[ext] + 1
|
||||
}
|
||||
total_content_length += (os.read_file(f) or { '' }).len
|
||||
}
|
||||
total_content_length += (os.read_file(f) or { '' }).len
|
||||
}
|
||||
}
|
||||
// files only
|
||||
// standalone files
|
||||
for fo in files_only {
|
||||
total_files++
|
||||
ext := get_file_extension(fo.name)
|
||||
if ext.len > 0 {
|
||||
file_extensions[ext] = file_extensions[ext] + 1
|
||||
}
|
||||
total_content_length += fo.content.len
|
||||
// if content not loaded, read length on demand
|
||||
file_len := if fo.content.len == 0 {
|
||||
(os.read_file(fo.path.path) or { '' }).len
|
||||
} else {
|
||||
fo.content.len
|
||||
}
|
||||
total_content_length += file_len
|
||||
}
|
||||
mut extensions_summary := ''
|
||||
for ext, count in file_extensions {
|
||||
@@ -442,10 +380,27 @@ fn (wsp Workspace) build_file_map() string {
|
||||
file_map += ' | Extensions: ${extensions_summary}'
|
||||
}
|
||||
file_map += '\n\n'
|
||||
file_map += build_file_tree_fs(roots, '')
|
||||
// list standalone files as well
|
||||
for fo in files_only {
|
||||
file_map += fo.path.path + ' *\n'
|
||||
// Render selected structure
|
||||
if roots.len > 0 {
|
||||
mut root_paths := []string{}
|
||||
for r in roots {
|
||||
root_paths << r.path.path
|
||||
}
|
||||
file_map += codewalker.build_file_tree_fs(root_paths, '')
|
||||
}
|
||||
// If there are only standalone selected files (no selected dirs),
|
||||
// build a minimal tree via codewalker relative to the workspace base.
|
||||
if files_only.len > 0 && roots.len == 0 {
|
||||
mut paths := []string{}
|
||||
for fo in files_only {
|
||||
paths << fo.path.path
|
||||
}
|
||||
file_map += codewalker.build_selected_tree(paths, wsp.base_path)
|
||||
} else if files_only.len > 0 && roots.len > 0 {
|
||||
// Keep listing absolute paths for standalone files when directories are also selected.
|
||||
for fo in files_only {
|
||||
file_map += fo.path.path + ' *\n'
|
||||
}
|
||||
}
|
||||
}
|
||||
return file_map
|
||||
@@ -470,16 +425,15 @@ pub fn (wsp Workspace) prompt(args WorkspacePrompt) string {
|
||||
}
|
||||
|
||||
// Save the workspace
|
||||
fn (wsp &Workspace) save() !&Workspace {
|
||||
mut tmp := wsp
|
||||
tmp.updated = time.now()
|
||||
tmp.is_saved = true
|
||||
set(tmp)!
|
||||
fn (mut wsp Workspace) save() !&Workspace {
|
||||
wsp.updated = time.now()
|
||||
wsp.is_saved = true
|
||||
set(wsp)!
|
||||
return get(name: wsp.name)!
|
||||
}
|
||||
|
||||
// Generate a random name for the workspace
|
||||
fn generate_random_workspace_name() string {
|
||||
pub fn generate_random_workspace_name() string {
|
||||
adjectives := [
|
||||
'brave',
|
||||
'bright',
|
||||
|
||||
@@ -1,30 +1,28 @@
|
||||
# heroprompt
|
||||
|
||||
|
||||
|
||||
To get started
|
||||
|
||||
```vlang
|
||||
```v
|
||||
|
||||
|
||||
import freeflowuniverse.herolib.clients. heroprompt
|
||||
import freeflowuniverse.herolib.develop.heroprompt
|
||||
|
||||
mut client:= heroprompt.get()!
|
||||
// Example Usage:
|
||||
|
||||
client...
|
||||
// 1. Create a new workspace
|
||||
mut workspace := heroprompt.new(name: 'my_workspace', path: os.getwd())!
|
||||
|
||||
// 2. Add a directory to the workspace
|
||||
workspace.add_dir(path: './my_project_dir')!
|
||||
|
||||
// 3. Add a file to the workspace
|
||||
workspace.add_file(path: './my_project_dir/main.v')!
|
||||
|
||||
// 4. Generate a prompt
|
||||
user_instructions := 'Explain the code in main.v'
|
||||
prompt_output := workspace.prompt(text: user_instructions)
|
||||
println(prompt_output)
|
||||
|
||||
|
||||
|
||||
```
|
||||
|
||||
## example heroscript
|
||||
|
||||
```hero
|
||||
!!heroprompt.configure
|
||||
secret: '...'
|
||||
host: 'localhost'
|
||||
port: 8888
|
||||
```
|
||||
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user