diff --git a/aiprompts/herolib_advanced/ui console chalk.md b/aiprompts/herolib_advanced/ui console chalk.md
index 5ebbbf88..0b267216 100644
--- a/aiprompts/herolib_advanced/ui console chalk.md
+++ b/aiprompts/herolib_advanced/ui console chalk.md
@@ -7,7 +7,7 @@ Chalk offers functions:- `console.color_fg(text string, color string)` - To chan
Example:
-```vlang
+```v
import freeflowuniverse.herolib.ui.console
# basic usage
diff --git a/aiprompts/herolib_core/core_params.md b/aiprompts/herolib_core/core_params.md
index 8edc7a0a..36c86077 100644
--- a/aiprompts/herolib_core/core_params.md
+++ b/aiprompts/herolib_core/core_params.md
@@ -25,7 +25,7 @@ The parser supports various input formats:
4. **Comments**: `// this is a comment` (ignored during parsing)
Example:
-```vlang
+```v
text := "name:'John Doe' age:30 active:true // user details"
params := paramsparser.new(text)!
```
diff --git a/aiprompts/herolib_core/core_ui_console.md b/aiprompts/herolib_core/core_ui_console.md
index 8203c25b..4a623c5a 100644
--- a/aiprompts/herolib_core/core_ui_console.md
+++ b/aiprompts/herolib_core/core_ui_console.md
@@ -4,7 +4,7 @@ has mechanisms to print better to console, see the methods below
import as
-```vlang
+```v
import freeflowuniverse.herolib.ui.console
```
diff --git a/aiprompts/herolib_core/core_vshell.md b/aiprompts/herolib_core/core_vshell.md
index ce06efec..9c6c6095 100644
--- a/aiprompts/herolib_core/core_vshell.md
+++ b/aiprompts/herolib_core/core_vshell.md
@@ -2,7 +2,7 @@
this is how we want example scripts to be, see the first line
-```vlang
+```v
#!/usr/bin/env -S v -cg -gc none -cc tcc -d use_openssl -enable-globals run
import freeflowuniverse.herolib...
diff --git a/aiprompts/instructions_archive/models_from_v/complete.md b/aiprompts/instructions_archive/models_from_v/complete.md
index b0babcb1..da56fd9f 100644
--- a/aiprompts/instructions_archive/models_from_v/complete.md
+++ b/aiprompts/instructions_archive/models_from_v/complete.md
@@ -926,7 +926,7 @@ The parser supports various input formats:
4. **Comments**: `// this is a comment` (ignored during parsing)
Example:
-```vlang
+```v
text := "name:'John Doe' age:30 active:true // user details"
params := paramsparser.new(text)!
```
@@ -1278,7 +1278,7 @@ has mechanisms to print better to console, see the methods below
import as
-```vlang
+```v
import freeflowuniverse.herolib.ui.console
```
@@ -1481,7 +1481,7 @@ File: /Users/despiegk/code/github/freeflowuniverse/herolib/aiprompts/herolib_cor
this is how we want example scripts to be, see the first line
-```vlang
+```v
#!/usr/bin/env -S v -cg -gc none -cc tcc -d use_openssl -enable-globals run
import freeflowuniverse.herolib...
diff --git a/aiprompts/v_advanced/time instructions.md b/aiprompts/v_advanced/time instructions.md
index 51552ebe..f84cd515 100644
--- a/aiprompts/v_advanced/time instructions.md
+++ b/aiprompts/v_advanced/time instructions.md
@@ -83,7 +83,7 @@ fn main() {
}
```
-```vlang
+```v
module time
diff --git a/cli/hero.v b/cli/hero.v
index b32042ad..35facb7a 100644
--- a/cli/hero.v
+++ b/cli/hero.v
@@ -48,7 +48,7 @@ fn do() ! {
mut cmd := Command{
name: 'hero'
description: 'Your HERO toolset.'
- version: '1.0.28'
+ version: '1.0.29'
}
// herocmds.cmd_run_add_flags(mut cmd)
@@ -85,7 +85,7 @@ fn do() ! {
herocmds.cmd_git(mut cmd)
herocmds.cmd_generator(mut cmd)
herocmds.cmd_docusaurus(mut cmd)
- // herocmds.cmd_web(mut cmd)
+ herocmds.cmd_web(mut cmd)
cmd.setup()
cmd.parse(os.args)
@@ -102,4 +102,4 @@ fn main() {
// fn pre_func(cmd Command) ! {
// herocmds.plbook_run(cmd)!
-// }
+// }
\ No newline at end of file
diff --git a/examples/threefold/grid/README.md b/examples/threefold/grid/README.md
index 33f353a8..23c04fe9 100644
--- a/examples/threefold/grid/README.md
+++ b/examples/threefold/grid/README.md
@@ -6,7 +6,7 @@ To be able to run examples you need to install updated version of `griddriver`.
Create some `griddriver_install.vsh` file containing following code:
-```vlang
+```v
#!/usr/bin/env -S v -gc none -cc tcc -d use_openssl -enable-globals run
import freeflowuniverse.herolib.installers.tfgrid.griddriver as griddriverinstaller
diff --git a/examples/web/doctree/content/test.md b/examples/web/doctree/content/test.md
index 00b22b0e..591dbadd 100644
--- a/examples/web/doctree/content/test.md
+++ b/examples/web/doctree/content/test.md
@@ -25,7 +25,7 @@ text in paragraph
| February | $80 |
| March | $420 |
-```vlang
+```v
fn main(){
println('hello world')
}
diff --git a/examples/web/ui_demo.vsh b/examples/web/ui_demo.vsh
index 50711018..10f395eb 100755
--- a/examples/web/ui_demo.vsh
+++ b/examples/web/ui_demo.vsh
@@ -1,13 +1,13 @@
#!/usr/bin/env -S v -n -w -gc none -cg -cc tcc -d use_openssl -enable-globals run
-// import freeflowuniverse.herolib.web.ui
+import freeflowuniverse.herolib.web.ui
-// fn main() {
-// println('Starting UI test server on port 8080...')
-// println('Visit http://localhost:8080 to see the admin interface')
+fn main() {
+ println('Starting UI test server on port 8080...')
+ println('Visit http://localhost:8080 to see the admin interface')
-// ui.start(
-// title: 'Test Admin Panel'
-// port: 8080
-// )!
-// }
+ ui.start(
+ title: 'Test Admin Panel'
+ port: 8080
+ )!
+}
diff --git a/install_hero.sh b/install_hero.sh
index 89ce3aa8..720f70dd 100755
--- a/install_hero.sh
+++ b/install_hero.sh
@@ -4,7 +4,7 @@ set -e
os_name="$(uname -s)"
arch_name="$(uname -m)"
-version='1.0.28'
+version='1.0.29'
# Base URL for GitHub releases
diff --git a/lib/clients/ipapi/readme.md b/lib/clients/ipapi/readme.md
index 01288e93..e246cda7 100644
--- a/lib/clients/ipapi/readme.md
+++ b/lib/clients/ipapi/readme.md
@@ -4,7 +4,7 @@
To get started
-```vlang
+```v
import freeflowuniverse.herolib.clients. ipapi
diff --git a/lib/clients/jina/readme.md b/lib/clients/jina/readme.md
index cad966b1..987d4cbd 100644
--- a/lib/clients/jina/readme.md
+++ b/lib/clients/jina/readme.md
@@ -6,7 +6,7 @@ see https://jina.ai/
To get started
-```vlang
+```v
import freeflowuniverse.herolib.clients. jina
diff --git a/lib/clients/mailclient/readme.md b/lib/clients/mailclient/readme.md
index d3862b36..fd44ffa9 100644
--- a/lib/clients/mailclient/readme.md
+++ b/lib/clients/mailclient/readme.md
@@ -3,7 +3,7 @@
To get started
-```vlang
+```v
import freeflowuniverse.herolib.clients.mailclient
diff --git a/lib/clients/openai/readme.md b/lib/clients/openai/readme.md
index 68f7a5a8..1d6ecc70 100644
--- a/lib/clients/openai/readme.md
+++ b/lib/clients/openai/readme.md
@@ -2,7 +2,7 @@
To get started
-```vlang
+```v
import freeflowuniverse.herolib.clients.openai
import freeflowuniverse.herolib.core.playcmds
diff --git a/lib/clients/runpod/readme.md b/lib/clients/runpod/readme.md
index f04c665d..02d902a1 100644
--- a/lib/clients/runpod/readme.md
+++ b/lib/clients/runpod/readme.md
@@ -4,7 +4,7 @@
To get started
-```vlang
+```v
diff --git a/lib/clients/vastai/readme.md b/lib/clients/vastai/readme.md
index 6c68d3da..a5f1308b 100644
--- a/lib/clients/vastai/readme.md
+++ b/lib/clients/vastai/readme.md
@@ -4,7 +4,7 @@
To get started
-```vlang
+```v
import freeflowuniverse.herolib.clients. vastai
diff --git a/lib/clients/wireguard/readme.md b/lib/clients/wireguard/readme.md
index 5fe40e9f..f571524c 100644
--- a/lib/clients/wireguard/readme.md
+++ b/lib/clients/wireguard/readme.md
@@ -4,7 +4,7 @@
To get started
-```vlang
+```v
import freeflowuniverse.herolib.clients. wireguard
diff --git a/lib/core/generator/generic/templates/readme.md b/lib/core/generator/generic/templates/readme.md
index d7e73a6c..17005bc5 100644
--- a/lib/core/generator/generic/templates/readme.md
+++ b/lib/core/generator/generic/templates/readme.md
@@ -4,7 +4,7 @@ ${args.title}
To get started
-```vlang
+```v
@if args.cat == .installer
diff --git a/lib/core/herocmds/web.v b/lib/core/herocmds/web.v
index 5ee52fd9..805c68f9 100644
--- a/lib/core/herocmds/web.v
+++ b/lib/core/herocmds/web.v
@@ -1,110 +1,110 @@
module herocmds
-// import freeflowuniverse.herolib.ui.console
-// import freeflowuniverse.herolib.web.ui
-// import os
-// import cli { Command, Flag }
-// import time
+import freeflowuniverse.herolib.ui.console
+import freeflowuniverse.herolib.web.ui
+import os
+import cli { Command, Flag }
+import time
-// pub fn cmd_web(mut cmdroot Command) Command {
-// mut cmd_run := Command{
-// name: 'web'
-// description: 'Run the Heroprompt UI (located in lib/web/heroprompt).'
-// required_args: 0
-// execute: cmd_web_execute
-// }
+pub fn cmd_web(mut cmdroot Command) Command {
+ mut cmd_run := Command{
+ name: 'web'
+ description: 'Run the Heroprompt UI (located in lib/web/heroprompt).'
+ required_args: 0
+ execute: cmd_web_execute
+ }
-// cmd_run.add_flag(Flag{
-// flag: .bool
-// required: false
-// name: 'open'
-// abbrev: 'o'
-// description: 'Open the UI in the default browser after starting the server.'
-// })
+ cmd_run.add_flag(Flag{
+ flag: .bool
+ required: false
+ name: 'open'
+ abbrev: 'o'
+ description: 'Open the UI in the default browser after starting the server.'
+ })
-// cmd_run.add_flag(Flag{
-// flag: .string
-// required: false
-// name: 'host'
-// abbrev: 'h'
-// description: 'Host to bind the server to (default: localhost).'
-// })
+ cmd_run.add_flag(Flag{
+ flag: .string
+ required: false
+ name: 'host'
+ abbrev: 'h'
+ description: 'Host to bind the server to (default: localhost).'
+ })
-// cmd_run.add_flag(Flag{
-// flag: .int
-// required: false
-// name: 'port'
-// abbrev: 'p'
-// description: 'Port to bind the server to (default: 8080).'
-// })
+ cmd_run.add_flag(Flag{
+ flag: .int
+ required: false
+ name: 'port'
+ abbrev: 'p'
+ description: 'Port to bind the server to (default: 8080).'
+ })
-// cmdroot.add_command(cmd_run)
-// return cmdroot
-// }
+ cmdroot.add_command(cmd_run)
+ return cmdroot
+}
-// fn cmd_web_execute(cmd Command) ! {
-// // ---------- FLAGS ----------
-// mut open_ := cmd.flags.get_bool('open') or { false }
-// mut host := cmd.flags.get_string('host') or { 'localhost' }
-// mut port := cmd.flags.get_int('port') or { 8080 }
+fn cmd_web_execute(cmd Command) ! {
+ // ---------- FLAGS ----------
+ mut open_ := cmd.flags.get_bool('open') or { false }
+ mut host := cmd.flags.get_string('host') or { 'localhost' }
+ mut port := cmd.flags.get_int('port') or { 8080 }
-// // Set defaults if not provided
-// if host == '' {
-// host = 'localhost'
-// }
-// if port == 0 {
-// port = 8080
-// }
+ // Set defaults if not provided
+ if host == '' {
+ host = 'localhost'
+ }
+ if port == 0 {
+ port = 8080
+ }
-// console.print_header('Starting Heroprompt...')
+ console.print_header('Starting Heroprompt...')
-// // Prepare arguments for the UI factory
-// mut factory_args := ui.FactoryArgs{
-// title: 'Hero Admin Panel'
-// host: host
-// port: port
-// }
+ // Prepare arguments for the UI factory
+ mut factory_args := ui.FactoryArgs{
+ title: 'Hero Admin Panel'
+ host: host
+ port: port
+ }
-// // ---------- START WEB SERVER ----------
-// console.print_header('Starting Heroprompt server...')
+ // ---------- START WEB SERVER ----------
+ console.print_header('Starting Heroprompt server...')
-// // Start the server in a separate thread to allow for browser opening
-// spawn fn [factory_args] () {
-// ui.start(factory_args) or {
-// console.print_stderr('Failed to start Heroprompt server: ${err}')
-// return
-// }
-// }()
+ // Start the server in a separate thread to allow for browser opening
+ spawn fn [factory_args] () {
+ ui.start(factory_args) or {
+ console.print_stderr('Failed to start Heroprompt server: ${err}')
+ return
+ }
+ }()
-// // Give the server a moment to start
-// time.sleep(2 * time.second)
-// url := 'http://${factory_args.host}:${factory_args.port}'
+ // Give the server a moment to start
+ time.sleep(2 * time.second)
+ url := 'http://${factory_args.host}:${factory_args.port}'
-// console.print_green('Heroprompt server is running on ${url}')
+ console.print_green('Heroprompt server is running on ${url}')
-// if open_ {
-// mut cmd_str := ''
-// $if macos {
-// cmd_str = 'open ${url}'
-// } $else $if linux {
-// cmd_str = 'xdg-open ${url}'
-// } $else $if windows {
-// cmd_str = 'start ${url}'
-// }
+ if open_ {
+ mut cmd_str := ''
+ $if macos {
+ cmd_str = 'open ${url}'
+ } $else $if linux {
+ cmd_str = 'xdg-open ${url}'
+ } $else $if windows {
+ cmd_str = 'start ${url}'
+ }
-// if cmd_str != '' {
-// result := os.execute(cmd_str)
-// if result.exit_code == 0 {
-// console.print_green('Opened Heroprompt in default browser.')
-// } else {
-// console.print_stderr('Failed to open browser: ${result.output}')
-// }
-// }
-// }
+ if cmd_str != '' {
+ result := os.execute(cmd_str)
+ if result.exit_code == 0 {
+ console.print_green('Opened Heroprompt in default browser.')
+ } else {
+ console.print_stderr('Failed to open browser: ${result.output}')
+ }
+ }
+ }
-// // Keep the process alive while the server runs
-// console.print_header('Press Ctrl+C to stop the server')
-// for {
-// time.sleep(1 * time.second)
-// }
-// }
+ // Keep the process alive while the server runs
+ console.print_header('Press Ctrl+C to stop the server')
+ for {
+ time.sleep(1 * time.second)
+ }
+}
diff --git a/lib/develop/codewalker/ignore.v b/lib/develop/codewalker/ignore.v
index 432b5a01..1c4aca5e 100644
--- a/lib/develop/codewalker/ignore.v
+++ b/lib/develop/codewalker/ignore.v
@@ -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)
diff --git a/lib/develop/codewalker/tree.v b/lib/develop/codewalker/tree.v
new file mode 100644
index 00000000..696bfe1d
--- /dev/null
+++ b/lib/develop/codewalker/tree.v
@@ -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, '')
+}
diff --git a/lib/develop/gittools/gittools_do.v b/lib/develop/gittools/gittools_do.v
index aa5a152a..863d75d2 100644
--- a/lib/develop/gittools/gittools_do.v
+++ b/lib/develop/gittools/gittools_do.v
@@ -99,6 +99,10 @@ pub fn (mut gs GitStructure) do(args_ ReposActionsArgs) !string {
provider: args.provider
)!
+ if repos.len<4 || args.cmd in 'pull,push,commit,delete'.split(',') {
+ args.reload = true
+ }
+
for mut repo in repos {
repo.status_update(reset: args.reload || args.cmd == 'reload')!
}
@@ -171,14 +175,16 @@ pub fn (mut gs GitStructure) do(args_ ReposActionsArgs) !string {
need_commit0 = true
}
- console.print_debug(" --- status repo ${g.name}'s\n need_commit0:${need_commit0} \n need_pull0:${need_pull0} \n need_push0:${need_push0}")
+ // console.print_debug(" --- status repo ${g.name}'s\n need_commit0:${need_commit0} \n need_pull0:${need_pull0} \n need_push0:${need_push0}")
}
- console.print_debug(" --- status all repo's\n need_commit0:${need_commit0} \n need_pull0:${need_pull0} \n need_push0:${need_push0}")
+ // console.print_debug(" --- status all repo's\n need_commit0:${need_commit0} \n need_pull0:${need_pull0} \n need_push0:${need_push0}")
+
+ // $dbg;
mut ok := false
if need_commit0 || need_pull0 || need_push0 {
- mut out := '\n ** NEED TO '
+ mut out := '\n\n** NEED TO '
if need_commit0 {
out += 'COMMIT '
}
@@ -245,7 +251,7 @@ pub fn (mut gs GitStructure) do(args_ ReposActionsArgs) !string {
g.commit(msg)!
has_changed = true
}
- if need_pull_repo {
+ if has_changed || need_pull_repo {
if args.reset {
console.print_header(' - remove changes ${g.account}/${g.name}')
g.remove_changes()!
@@ -254,7 +260,7 @@ pub fn (mut gs GitStructure) do(args_ ReposActionsArgs) !string {
g.pull()!
has_changed = true
}
- if need_push_repo {
+ if has_changed || need_push_repo {
console.print_header(' - push ${g.account}/${g.name}')
g.push()!
has_changed = true
@@ -267,7 +273,7 @@ pub fn (mut gs GitStructure) do(args_ ReposActionsArgs) !string {
if has_changed {
// console.clear()
- console.print_header('\nCompleted required actions.\n')
+ console.print_header('Completed required actions.\n')
gs.repos_print(
filter: args.filter
diff --git a/lib/develop/heroprompt/heroprompt_child.v b/lib/develop/heroprompt/heroprompt_child.v
index d7b6466b..e57af3a7 100644
--- a/lib/develop/heroprompt/heroprompt_child.v
+++ b/lib/develop/heroprompt/heroprompt_child.v
@@ -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
diff --git a/lib/develop/heroprompt/heroprompt_workspace.v b/lib/develop/heroprompt/heroprompt_workspace.v
index 6b47c5c8..95a6ac4b 100644
--- a/lib/develop/heroprompt/heroprompt_workspace.v
+++ b/lib/develop/heroprompt/heroprompt_workspace.v
@@ -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',
diff --git a/lib/develop/heroprompt/readme.md b/lib/develop/heroprompt/readme.md
index 8a7d70aa..2b38b0eb 100644
--- a/lib/develop/heroprompt/readme.md
+++ b/lib/develop/heroprompt/readme.md
@@ -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
-```
-
-
diff --git a/lib/develop/reprompt/templates/prompt_example.md b/lib/develop/reprompt/templates/prompt_example.md
deleted file mode 100644
index 0675fea0..00000000
--- a/lib/develop/reprompt/templates/prompt_example.md
+++ /dev/null
@@ -1,1607 +0,0 @@
-
-/Users/despiegk/code/github/freeflowuniverse/herolib
-├── .gitignore
-├── .vdocignore
-├── compile.sh
-├── CONTRIBUTING.md
-├── doc.vsh
-├── generate.vsh
-├── herolib.code-workspace
-├── install_hero.sh
-├── install_herolib.vsh
-├── install_v.sh
-├── LICENSE
-├── README.md
-├── release_OLD.sh
-├── release.vsh
-├── specs.md
-├── test_basic.vsh
-└── test_runner.vsh
-├── .github
-│ └── workflows
-│ ├── documentation.yml
-│ ├── hero_build.yml
-│ └── test.yml
-├── aiprompts
-│ └── herolib_start_here.md
-│ ├── .openhands
-│ │ └── setup.sh
-│ ├── ai_instruct
-│ │ ├── documentation_from_v_md.md
-│ │ ├── documentation_from_v.md
-│ │ ├── prompt_processing_instructions.md
-│ │ ├── prompt_processing_openrpc_like.md
-│ │ └── what_is_a_hero_twin.md
-│ │ ├── models_from_v
-│ ├── bizmodel
-│ │ ├── bizmodel_cost.md
-│ │ ├── bizmodel_funding.md
-│ │ ├── bizmodel_generation_prompt.md
-│ │ ├── bizmodel_hr.md
-│ │ ├── bizmodel_revenue.md
-│ │ └── costs.heroscript
-│ ├── documentor
-│ │ └── generate_v_doc_readable_md_files.md
-│ ├── docusaurus
-│ │ └── docusaurus_ebook_manual.md
-│ ├── herolib_advanced
-│ │ ├── advanced_paths.md
-│ │ ├── builder.md
-│ │ ├── cmdline_argument_parsing_example.vsh
-│ │ ├── datatypes.md
-│ │ ├── osal.md
-│ │ ├── ourdb.md
-│ │ ├── redis.md
-│ │ ├── spreadsheet.md
-│ │ └── ui console chalk.md
-│ ├── herolib_core
-│ │ ├── core_curdir_example.md
-│ │ ├── core_globals.md
-│ │ ├── core_heroscript_basics.md
-│ │ ├── core_heroscript_playbook.md
-│ │ ├── core_http_client.md
-│ │ ├── core_osal.md
-│ │ ├── core_ourtime.md
-│ │ ├── core_params.md
-│ │ ├── core_paths.md
-│ │ ├── core_text.md
-│ │ ├── core_ui_console.md
-│ │ └── core_vshell.md
-│ ├── v_advanced
-│ │ ├── advanced_topics.md
-│ │ ├── compress.md
-│ │ ├── generics.md
-│ │ ├── html_parser.md
-│ │ ├── io.md
-│ │ ├── net.md
-│ │ ├── reflection.md
-│ │ ├── regex.md
-│ │ ├── smtp.md
-│ │ └── time instructions.md
-│ ├── v_core
-│ │ └── v_manual.md
-│ ├── v_veb_webserver
-│ │ ├── veb_assets.md
-│ │ ├── veb_auth.md
-│ │ ├── veb_csrf.md
-│ │ ├── veb_sse.md
-│ │ ├── veb.md
-│ │ └── vtemplates.md
-├── cli
-│ ├── .gitignore
-│ ├── compile
-│ ├── compile_upload.vsh
-│ ├── compile_vdo.vsh
-│ ├── compile.vsh
-│ ├── hero.v
-│ └── vdo.v
-├── docker
-│ └── docker_ubuntu_install.sh
-│ ├── herolib
-│ │ ├── .gitignore
-│ │ ├── build.sh
-│ │ ├── debug.sh
-│ │ ├── docker-compose.yml
-│ │ ├── Dockerfile
-│ │ ├── export.sh
-│ │ ├── README.md
-│ │ ├── shell.sh
-│ │ ├── ssh_init.sh
-│ │ ├── ssh.sh
-│ │ └── start.sh
-│ │ ├── scripts
-│ ├── postgresql
-│ │ ├── docker-compose.yml
-│ │ ├── readme.md
-│ │ └── start.sh
-├── examples
-│ ├── README.md
-│ └── sync_do.sh
-│ ├── aiexamples
-│ │ ├── groq.vsh
-│ │ ├── jetconvertor.vsh
-│ │ ├── jina.vsh
-│ │ └── qdrant.vsh
-│ ├── baobab
-│ │ ├── generator
-│ │ └── specification
-│ ├── biztools
-│ │ ├── bizmodel_complete.vsh
-│ │ ├── bizmodel_export.vsh
-│ │ ├── bizmodel_full.vsh
-│ │ ├── bizmodel.vsh
-│ │ ├── bizmodel1.vsh
-│ │ ├── bizmodel2.vsh
-│ │ ├── costs.vsh
-│ │ ├── funding.vsh
-│ │ ├── hr.vsh
-│ │ └── notworking.md
-│ │ ├── _archive
-│ │ ├── bizmodel_docusaurus
-│ │ ├── examples
-│ ├── builder
-│ │ ├── simple_ip4.vsh
-│ │ ├── simple_ip6.vsh
-│ │ └── simple.vsh
-│ │ ├── remote_executor
-│ ├── clients
-│ │ ├── aiclient_example.vsh
-│ │ ├── jina_example.vsh
-│ │ ├── mail.vsh
-│ │ ├── mycelium_rpc.vsh
-│ │ ├── mycelium.vsh
-│ │ ├── psql.vsh
-│ │ └── zinit_rpc_example.vsh
-│ ├── core
-│ │ ├── agent_encoding.vsh
-│ │ ├── generate.vsh
-│ │ └── secrets_example.vsh
-│ │ ├── base
-│ │ ├── db
-│ │ ├── dbfs
-│ │ ├── openapi
-│ │ ├── openrpc
-│ │ ├── pathlib
-│ ├── data
-│ │ ├── .gitignore
-│ │ ├── cache.vsh
-│ │ ├── compress_gzip_example.vsh
-│ │ ├── deduped_mycelium_master.vsh
-│ │ ├── deduped_mycelium_worker.vsh
-│ │ ├── encoder.vsh
-│ │ ├── encrypt_decrypt.vsh
-│ │ ├── graphdb.vsh
-│ │ ├── heroencoder_example.vsh
-│ │ ├── heroencoder_simple.vsh
-│ │ ├── jsonexample.vsh
-│ │ ├── ourdb_client.vsh
-│ │ ├── ourdb_example.vsh
-│ │ ├── ourdb_server.vsh
-│ │ ├── radixtree.vsh
-│ │ └── regex_example.vsh
-│ │ ├── location
-│ │ ├── ourdb_syncer
-│ │ ├── params
-│ │ ├── resp
-│ ├── develop
-│ │ ├── gittools
-│ │ ├── ipapi
-│ │ ├── juggler
-│ │ ├── luadns
-│ │ ├── openai
-│ │ ├── runpod
-│ │ ├── vastai
-│ │ └── wireguard
-│ ├── hero
-│ │ └── alpine_example.vsh
-│ │ ├── db
-│ │ ├── generation
-│ │ ├── openapi
-│ ├── installers
-│ │ ├── .gitignore
-│ │ ├── cometbft.vsh
-│ │ ├── conduit.vsh
-│ │ ├── coredns.vsh
-│ │ ├── hero_install.vsh
-│ │ ├── installers.vsh
-│ │ ├── traefik.vsh
-│ │ └── youki.vsh
-│ │ ├── db
-│ │ ├── infra
-│ │ ├── lang
-│ │ ├── net
-│ │ ├── sysadmintools
-│ │ ├── threefold
-│ │ ├── virt
-│ ├── jobs
-│ │ └── vfs_jobs_example.vsh
-│ ├── lang
-│ │ └── python
-│ ├── mcp
-│ │ ├── http_demo
-│ │ ├── http_server
-│ │ ├── inspector
-│ │ └── simple_http
-│ ├── osal
-│ │ ├── .gitignore
-│ │ ├── notifier.vsh
-│ │ ├── startup_manager.vsh
-│ │ ├── systemd.vsh
-│ │ ├── tun.vsh
-│ │ ├── ufw_play.vsh
-│ │ └── ufw.vsh
-│ │ ├── coredns
-│ │ ├── download
-│ │ ├── ping
-│ │ ├── process
-│ │ ├── rsync
-│ │ ├── sandbox
-│ │ ├── sshagent
-│ │ ├── zinit
-│ ├── schemas
-│ │ ├── openapi
-│ │ └── openrpc
-│ ├── threefold
-│ │ └── .gitignore
-│ │ ├── grid
-│ │ ├── gridproxy
-│ │ ├── holochain
-│ │ ├── solana
-│ │ ├── tfgrid3deployer
-│ ├── tools
-│ │ ├── imagemagick
-│ │ ├── tmux
-│ │ └── vault
-│ ├── ui
-│ │ ├── flow1.v
-│ │ └── silence.vsh
-│ │ ├── console
-│ │ ├── telegram
-│ ├── vfs
-│ │ └── vfs_db
-│ ├── virt
-│ │ ├── daguserver
-│ │ ├── docker
-│ │ ├── hetzner
-│ │ ├── lima
-│ │ ├── podman_buildah
-│ │ ├── runc
-│ │ └── windows
-│ ├── web
-│ │ ├── .gitignore
-│ │ ├── docusaurus_example.vsh
-│ │ ├── starllight_example.vsh
-│ │ └── ui_demo.vsh
-│ │ ├── doctree
-│ │ ├── markdown_renderer
-│ ├── webdav
-│ │ ├── .gitignore
-│ │ └── webdav_vfs.vsh
-├── lib
-│ ├── readme.md
-│ └── v.mod
-│ ├── ai
-│ │ ├── escalayer
-│ │ ├── mcp
-│ │ └── utils
-│ ├── baobab
-│ │ └── README.md
-│ │ ├── actor
-│ │ ├── generator
-│ │ ├── osis
-│ │ ├── specification
-│ │ ├── stage
-│ ├── biz
-│ │ ├── bizmodel
-│ │ ├── investortool
-│ │ ├── planner
-│ │ └── spreadsheet
-│ ├── builder
-│ │ ├── bootstrapper.v
-│ │ ├── builder_factory.v
-│ │ ├── done.v
-│ │ ├── executor_local_test.v
-│ │ ├── executor_local.v
-│ │ ├── executor_ssh_test.v
-│ │ ├── executor_ssh.v
-│ │ ├── executor.v
-│ │ ├── model_package.v
-│ │ ├── node_commands.v
-│ │ ├── node_executor.v
-│ │ ├── node_factory.v
-│ │ ├── node.v
-│ │ ├── nodedb_test.v
-│ │ ├── portforward_lib.v
-│ │ ├── readme.md
-│ │ └── this_remote.v
-│ ├── clients
-│ │ ├── ipapi
-│ │ ├── jina
-│ │ ├── livekit
-│ │ ├── mailclient
-│ │ ├── meilisearch
-│ │ ├── mycelium
-│ │ ├── mycelium_rpc
-│ │ ├── openai
-│ │ ├── postgresql_client
-│ │ ├── qdrant
-│ │ ├── rclone
-│ │ ├── runpod
-│ │ ├── sendgrid
-│ │ ├── vastai
-│ │ ├── wireguard
-│ │ ├── zerodb_client
-│ │ ├── zinit
-│ │ └── zinit_rpc
-│ ├── code
-│ │ └── generator
-│ ├── conversiontools
-│ │ └── tools.v
-│ │ ├── docsorter
-│ │ ├── imagemagick
-│ │ ├── pdftotext
-│ │ ├── text_extractor
-│ ├── core
-│ │ ├── interactive.v
-│ │ ├── memdb_test.v
-│ │ ├── memdb.v
-│ │ ├── platform_test.v
-│ │ ├── platform.v
-│ │ ├── readme.md
-│ │ ├── sudo_test.v
-│ │ └── sudo.v
-│ │ ├── base
-│ │ ├── code
-│ │ ├── generator
-│ │ ├── herocmds
-│ │ ├── httpconnection
-│ │ ├── logger
-│ │ ├── openrpc_remove
-│ │ ├── pathlib
-│ │ ├── playbook
-│ │ ├── playcmds
-│ │ ├── playmacros
-│ │ ├── redisclient
-│ │ ├── rootpath
-│ │ ├── smartid
-│ │ ├── texttools
-│ │ ├── vexecutor
-│ ├── crypt
-│ │ └── crypt.v
-│ │ ├── aes_symmetric
-│ │ ├── crpgp
-│ │ ├── ed25519
-│ │ ├── keychain
-│ │ ├── keysafe
-│ │ ├── openssl
-│ │ ├── pgp
-│ │ ├── secp256k1
-│ │ ├── secrets
-│ ├── data
-│ │ ├── cache
-│ │ ├── currency
-│ │ ├── dbfs
-│ │ ├── dedupestor
-│ │ ├── doctree
-│ │ ├── encoder
-│ │ ├── encoderhero
-│ │ ├── flist
-│ │ ├── gid
-│ │ ├── graphdb
-│ │ ├── ipaddress
-│ │ ├── location
-│ │ ├── markdown
-│ │ ├── markdownparser2
-│ │ ├── markdownrenderer
-│ │ ├── mnemonic
-│ │ ├── models
-│ │ ├── ourdb
-│ │ ├── ourdb_syncer
-│ │ ├── ourjson
-│ │ ├── ourtime
-│ │ ├── paramsparser
-│ │ ├── radixtree
-│ │ ├── resp
-│ │ ├── serializers
-│ │ ├── tst
-│ │ ├── verasure
-│ │ └── vstor
-│ ├── dav
-│ │ └── webdav
-│ ├── develop
-│ │ ├── gittools
-│ │ ├── luadns
-│ │ ├── performance
-│ │ ├── sourcetree
-│ │ ├── vscode
-│ │ └── vscode_extensions
-│ ├── hero
-│ │ └── models
-│ │ ├── db
-│ ├── installers
-│ │ ├── install_multi.v
-│ │ └── upload.v
-│ │ ├── base
-│ │ ├── db
-│ │ ├── develapps
-│ │ ├── infra
-│ │ ├── lang
-│ │ ├── net
-│ │ ├── sysadmintools
-│ │ ├── threefold
-│ │ ├── ulist
-│ │ ├── virt
-│ │ ├── web
-│ ├── lang
-│ │ ├── python
-│ │ └── rust
-│ ├── mcp
-│ │ ├── backend_interface.v
-│ │ ├── backend_memory.v
-│ │ ├── factory.v
-│ │ ├── generics.v
-│ │ ├── handler_initialize_test.v
-│ │ ├── handler_initialize.v
-│ │ ├── handler_prompts.v
-│ │ ├── handler_resources.v
-│ │ ├── handler_tools.v
-│ │ ├── model_configuration_test.v
-│ │ ├── model_configuration.v
-│ │ ├── model_error.v
-│ │ ├── README.md
-│ │ └── server.v
-│ │ ├── baobab
-│ │ ├── cmd
-│ │ ├── mcpgen
-│ │ ├── pugconvert
-│ │ ├── rhai
-│ │ ├── transport
-│ │ ├── vcode
-│ ├── osal
-│ │ ├── core
-│ │ ├── coredns
-│ │ ├── hostsfile
-│ │ ├── notifier
-│ │ ├── osinstaller
-│ │ ├── rsync
-│ │ ├── screen
-│ │ ├── sshagent
-│ │ ├── startupmanager
-│ │ ├── systemd
-│ │ ├── tmux
-│ │ ├── traefik
-│ │ ├── tun
-│ │ ├── ufw
-│ │ └── zinit
-│ ├── schemas
-│ │ ├── jsonrpc
-│ │ ├── jsonschema
-│ │ ├── openapi
-│ │ └── openrpc
-│ ├── security
-│ │ ├── authentication
-│ │ └── jwt
-│ ├── threefold
-│ │ ├── grid3
-│ │ └── grid4
-│ ├── ui
-│ │ ├── factory.v
-│ │ └── readme.md
-│ │ ├── console
-│ │ ├── generic
-│ │ ├── logger
-│ │ ├── telegram
-│ │ ├── template
-│ │ ├── uimodel
-│ ├── vfs
-│ │ ├── interface.v
-│ │ ├── metadata.v
-│ │ └── README.md
-│ │ ├── vfs_calendar
-│ │ ├── vfs_contacts
-│ │ ├── vfs_db
-│ │ ├── vfs_local
-│ │ ├── vfs_mail
-│ │ ├── vfs_nested
-│ ├── virt
-│ │ ├── cloudhypervisor
-│ │ ├── docker
-│ │ ├── herocontainers
-│ │ ├── hetzner
-│ │ ├── lima
-│ │ ├── qemu
-│ │ ├── runc
-│ │ └── utils
-│ ├── web
-│ │ ├── doctreeclient
-│ │ ├── docusaurus
-│ │ ├── echarts
-│ │ ├── site
-│ │ └── ui
-├── libarchive
-│ ├── installers
-│ │ └── web
-│ ├── rhai
-│ │ ├── generate_rhai_example.v
-│ │ ├── generate_wrapper_module.v
-│ │ ├── register_functions.v
-│ │ ├── register_types_test.v
-│ │ ├── register_types.v
-│ │ ├── rhai_test.v
-│ │ ├── rhai.v
-│ │ └── verify.v
-│ │ ├── prompts
-│ │ ├── templates
-│ │ ├── testdata
-│ └── starlight
-│ ├── clean.v
-│ ├── config.v
-│ ├── factory.v
-│ ├── model.v
-│ ├── site_get.v
-│ ├── site.v
-│ ├── template.v
-│ └── watcher.v
-│ ├── templates
-├── manual
-│ ├── config.json
-│ ├── create_tag.md
-│ └── serve_wiki.sh
-│ ├── best_practices
-│ │ └── using_args_in_function.md
-│ │ ├── osal
-│ │ ├── scripts
-│ ├── core
-│ │ ├── base.md
-│ │ ├── context_session_job.md
-│ │ ├── context.md
-│ │ ├── play.md
-│ │ └── session.md
-│ │ ├── concepts
-│ ├── documentation
-│ │ └── docextractor.md
-├── research
-│ └── globals
-│ ├── globals_example_inplace.vsh
-│ ├── globals_example_reference.vsh
-│ ├── globals_example.vsh
-│ └── ubuntu_partition.sh
-├── vscodeplugin
-│ ├── install_ubuntu.sh
-│ ├── package.sh
-│ └── readme.md
-│ ├── heroscrypt-syntax
-│ │ ├── heroscript-syntax-0.0.1.vsix
-│ │ ├── language-configuration.json
-│ │ └── package.json
-│ │ ├── syntaxes
-
-
-
-File: /Users/despiegk/code/github/freeflowuniverse/herolib/lib/core/playcmds/_archive/bizmodel.v
-```v
-module playcmds
-
-// import freeflowuniverse.herolib.core.playbook
-
-// fn git(mut actions playbook.Actions, action playbook.Action) ! {
-// if action.name == 'init' {
-// // means we support initialization afterwards
-// c.bizmodel_init(mut actions, action)!
-// }
-
-// // if action.name == 'get' {
-// // mut gs := gittools.new()!
-// // url := action.params.get('url')!
-// // branch := action.params.get_default('branch', '')!
-// // reset := action.params.get_default_false('reset')!
-// // pull := action.params.get_default_false('pull')!
-// // mut gr := gs.repo_get_from_url(url: url, branch: branch, pull: pull, reset: reset)!
-// // }
-// }
-
-```
-
-File: /Users/despiegk/code/github/freeflowuniverse/herolib/lib/core/playcmds/_archive/currency.v
-```v
-module playcmds
-
-// fn currency_actions(actions_ []playbook.Action) ! {
-// mut actions2 := actions.filtersort(actions: actions_, actor: 'currency', book: '*')!
-// if actions2.len == 0 {
-// return
-// }
-
-// mut cs := currency.new()!
-
-// for action in actions2 {
-// // TODO: set the currencies
-// if action.name == 'default_set' {
-// cur := action.params.get('cur')!
-// usdval := action.params.get_int('usdval')!
-// cs.default_set(cur, usdval)!
-// }
-// }
-
-// // TODO: add the currency metainfo, do a test
-// }
-
-```
-
-File: /Users/despiegk/code/github/freeflowuniverse/herolib/lib/core/playcmds/_archive/dagu.v
-```v
-module playcmds
-
-// import freeflowuniverse.herolib.installers.sysadmintools.daguserver
-
-// pub fn scheduler(heroscript string) ! {
-// daguserver.play(
-// heroscript: heroscript
-// )!
-// }
-
-```
-
-File: /Users/despiegk/code/github/freeflowuniverse/herolib/lib/core/playcmds/_archive/downloader.v
-```v
-module playcmds
-
-// import freeflowuniverse.herolib.core.playbook
-// import freeflowuniverse.herolib.sysadmin.downloader
-
-// can start with sal, dal, ... the 2nd name is typicall the actor (or topic)
-// do this function public and then it breaches out to detail functionality
-
-// pub fn sal_downloader(action playbook.Action) ! {
-// match action.actor {
-// 'downloader' {
-// match action.name {
-// 'get' {
-// downloader_get(action: action)!
-// }
-// else {
-// return error('actions not supported yet')
-// }
-// }
-// }
-// else {
-// return error('actor not supported yet')
-// }
-// }
-// }
-
-// fn downloader_get(args ActionExecArgs) ! {
-// action := args.action
-// // session:=args.action or {panic("no context")} //if we need it here
-// mut name := action.params.get_default('name', '')!
-// mut downloadpath := action.params.get_default('downloadpath', '')!
-// mut url := action.params.get_default('url', '')!
-// mut reset := action.params.get_default_false('reset')
-// mut gitpull := action.params.get_default_false('gitpull')
-
-// mut minsize_kb := action.params.get_u32_default('minsize_kb', 0)!
-// mut maxsize_kb := action.params.get_u32_default('maxsize_kb', 0)!
-
-// mut destlink := action.params.get_default_false('destlink')
-
-// mut dest := action.params.get_default('dest', '')!
-// mut hash := action.params.get_default('hash', '')!
-// mut metapath := action.params.get_default('metapath', '')!
-
-// mut meta := downloader.download(
-// name: name
-// downloadpath: downloadpath
-// url: url
-// reset: reset
-// gitpull: gitpull
-// minsize_kb: minsize_kb
-// maxsize_kb: maxsize_kb
-// destlink: destlink
-// dest: dest
-// hash: hash
-// metapath: metapath
-// // session:session // TODO IMPLEMENT (also optional)
-// )!
-// }
-
-```
-
-File: /Users/despiegk/code/github/freeflowuniverse/herolib/lib/core/playcmds/_archive/play_caddy.v
-```v
-module playcmds
-
-// import freeflowuniverse.herolib.installers.web.caddy as caddy_installer
-// import freeflowuniverse.herolib.servers.caddy { CaddyFile }
-// import freeflowuniverse.herolib.core.playbook
-// import os
-// // import net.urllib
-
-// pub fn play_caddy(mut plbook playbook.PlayBook) ! {
-// play_caddy_basic(mut plbook)!
-// play_caddy_configure(mut plbook)!
-// }
-
-// pub fn play_caddy_configure(mut plbook playbook.PlayBook) ! {
-// mut caddy_actions := plbook.find(filter: 'caddy_configure')!
-// if caddy_actions.len == 0 {
-// return
-// }
-// }
-
-// pub fn play_caddy_basic(mut plbook playbook.PlayBook) ! {
-// caddy_actions := plbook.find(filter: 'caddy.')!
-// if caddy_actions.len == 0 {
-// return
-// }
-
-// mut install_actions := plbook.find(filter: 'caddy.install')!
-
-// if install_actions.len > 0 {
-// for install_action in install_actions {
-// mut p := install_action.params
-// xcaddy := p.get_default_false('xcaddy')
-// file_path := p.get_default('file_path', '/etc/caddy')!
-// file_url := p.get_default('file_url', '')!
-// reset := p.get_default_false('reset')
-// start := p.get_default_false('start')
-// restart := p.get_default_false('restart')
-// stop := p.get_default_false('stop')
-// homedir := p.get_default('file_url', '')!
-// plugins := p.get_list_default('plugins', []string{})!
-
-// caddy_installer.install(
-// xcaddy: xcaddy
-// file_path: file_path
-// file_url: file_url
-// reset: reset
-// start: start
-// restart: restart
-// stop: stop
-// homedir: homedir
-// plugins: plugins
-// )!
-// }
-// }
-
-// mut config_actions := plbook.find(filter: 'caddy.configure')!
-// if config_actions.len > 0 {
-// mut coderoot := ''
-// mut reset := false
-// mut pull := false
-
-// mut public_ip := ''
-
-// mut c := caddy.get('')!
-// // that to me seems to be wrong, not generic enough
-// if config_actions.len > 1 {
-// return error('can only have 1 config action for books')
-// } else if config_actions.len == 1 {
-// mut p := config_actions[0].params
-// path := p.get_default('path', '/etc/caddy')!
-// url := p.get_default('url', '')!
-// public_ip = p.get_default('public_ip', '')!
-// c = caddy.configure('', homedir: path)!
-// config_actions[0].done = true
-// }
-
-// mut caddyfile := CaddyFile{}
-// for mut action in plbook.find(filter: 'caddy.add_reverse_proxy')! {
-// mut p := action.params
-// mut from := p.get_default('from', '')!
-// mut to := p.get_default('to', '')!
-
-// if from == '' || to == '' {
-// return error('from & to cannot be empty')
-// }
-
-// caddyfile.add_reverse_proxy(
-// from: from
-// to: to
-// )!
-// action.done = true
-// }
-
-// for mut action in plbook.find(filter: 'caddy.add_file_server')! {
-// mut p := action.params
-// mut domain := p.get_default('domain', '')!
-// mut root := p.get_default('root', '')!
-
-// if root.starts_with('~') {
-// root = '${os.home_dir()}${root.trim_string_left('~')}'
-// }
-
-// if domain == '' || root == '' {
-// return error('domain & root cannot be empty')
-// }
-
-// caddyfile.add_file_server(
-// domain: domain
-// root: root
-// )!
-// action.done = true
-// }
-
-// for mut action in plbook.find(filter: 'caddy.add_basic_auth')! {
-// mut p := action.params
-// mut domain := p.get_default('domain', '')!
-// mut username := p.get_default('username', '')!
-// mut password := p.get_default('password', '')!
-
-// if domain == '' || username == '' || password == '' {
-// return error('domain & root cannot be empty')
-// }
-
-// caddyfile.add_basic_auth(
-// domain: domain
-// username: username
-// password: password
-// )!
-// action.done = true
-// }
-
-// for mut action in plbook.find(filter: 'caddy.generate')! {
-// c.set_caddyfile(caddyfile)!
-// action.done = true
-// }
-
-// for mut action in plbook.find(filter: 'caddy.start')! {
-// c.start()!
-// action.done = true
-// }
-// c.reload()!
-// }
-// }
-
-```
-
-File: /Users/despiegk/code/github/freeflowuniverse/herolib/lib/core/playcmds/_archive/play_dagu_test.v
-```v
-module playcmds
-
-import freeflowuniverse.herolib.core.playbook
-
-const dagu_script = "
-!!dagu.configure
- instance: 'test'
- username: 'admin'
- password: 'testpassword'
-
-!!dagu.new_dag
- name: 'test_dag'
-
-!!dagu.add_step
- dag: 'test_dag'
- name: 'hello_world'
- command: 'echo hello world'
-
-!!dagu.add_step
- dag: 'test_dag'
- name: 'last_step'
- command: 'echo last step'
-
-
-"
-
-fn test_play_dagu() ! {
- mut plbook := playbook.new(text: dagu_script)!
- play_dagu(mut plbook)!
- // panic('s')
-}
-
-```
-
-File: /Users/despiegk/code/github/freeflowuniverse/herolib/lib/core/playcmds/_archive/play_dagu.v
-```v
-module playcmds
-
-// import freeflowuniverse.herolib.clients.daguclient
-// import freeflowuniverse.herolib.installers.sysadmintools.daguserver
-// import freeflowuniverse.herolib.installers.sysadmintools.daguserver
-import freeflowuniverse.herolib.core.playbook
-import freeflowuniverse.herolib.ui.console
-import os
-
-// pub fn play_dagu(mut plbook playbook.PlayBook) ! {
-// // dagu_actions := plbook.find(filter: 'dagu.')!
-// // if dagu_actions.len == 0 {
-// // return
-// // }
-
-// // play_dagu_basic(mut plbook)!
-// // play_dagu_configure(mut plbook)!
-// }
-
-// // play_dagu plays the dagu play commands
-// pub fn play_dagu_basic(mut plbook playbook.PlayBook) ! {
-// // mut install_actions := plbook.find(filter: 'daguserver.configure')!
-
-// // if install_actions.len > 0 {
-// // for install_action in install_actions {
-// // mut p := install_action.params
-// // panic("daguinstall play")
-// // }
-// // }
-
-// // dagu_actions := plbook.find(filter: 'daguserver.install')!
-// // if dagu_actions.len > 0 {
-// // panic("daguinstall play")
-// // return
-// // }
-
-// // mut config_actions := plbook.find(filter: 'dagu.configure')!
-// // mut d := if config_actions.len > 1 {
-// // return error('can only have 1 config action for dagu')
-// // } else if config_actions.len == 1 {
-// // mut p := config_actions[0].params
-// // instance := p.get_default('instance', 'default')!
-// // port := p.get_int_default('port', 8888)!
-// // username := p.get_default('username', '')!
-// // password := p.get_default('password', '')!
-// // config_actions[0].done = true
-// // mut server := daguserver.configure(instance,
-// // port: port
-// // username: username
-// // password: password
-// // )!
-// // server.start()!
-// // console.print_debug('Dagu server is running at http://localhost:${port}')
-// // console.print_debug('Username: ${username} password: ${password}')
-
-// // // configure dagu client with server url and api secret
-// // server_cfg := server.config()!
-// // daguclient.get(instance,
-// // url: 'http://localhost:${port}'
-// // apisecret: server_cfg.secret
-// // )!
-// // } else {
-// // mut server := daguserver.get('')!
-// // server.start()!
-// // daguclient.get('')!
-// // }
-
-// // mut dags := map[string]DAG{}
-
-// // for mut action in plbook.find(filter: 'dagu.new_dag')! {
-// // mut p := action.params
-// // name := p.get_default('name', '')!
-// // dags[name] = DAG{}
-// // action.done = true
-// // }
-
-// // for mut action in plbook.find(filter: 'dagu.add_step')! {
-// // mut p := action.params
-// // dag := p.get_default('dag', 'default')!
-// // name := p.get_default('name', 'default')!
-// // command := p.get_default('command', '')!
-// // dags[dag].step_add(
-// // nr: dags.len
-// // name: name
-// // command: command
-// // )!
-// // }
-
-// // for mut action in plbook.find(filter: 'dagu.run')! {
-// // mut p := action.params
-// // dag := p.get_default('dag', 'default')!
-// // // d.new_dag(dags[dag])!
-// // panic('to implement')
-// // }
-// }
-
-```
-
-File: /Users/despiegk/code/github/freeflowuniverse/herolib/lib/core/playcmds/_archive/play_juggler.v
-```v
-module playcmds
-
-import freeflowuniverse.herolib.data.doctree
-import freeflowuniverse.herolib.ui.console
-import freeflowuniverse.herolib.core.playbook
-import freeflowuniverse.herolib.develop.juggler
-import os
-
-pub fn play_juggler(mut plbook playbook.PlayBook) ! {
- mut coderoot := ''
- // mut install := false
- mut reset := false
- mut pull := false
-
- mut config_actions := plbook.find(filter: 'juggler.configure')!
-
- mut j := juggler.Juggler{}
-
- if config_actions.len > 1 {
- return error('can only have 1 config action for juggler')
- } else if config_actions.len == 1 {
- mut p := config_actions[0].params
- path := p.get_default('path', '/etc/juggler')!
- url := p.get_default('url', '')!
- username := p.get_default('username', '')!
- password := p.get_default('password', '')!
- port := p.get_int_default('port', 8000)!
-
- j = juggler.configure(
- url: 'https://git.threefold.info/projectmycelium/itenv'
- username: username
- password: password
- reset: true
- )!
- config_actions[0].done = true
- }
-
- for mut action in plbook.find(filter: 'juggler.start')! {
- j.start()!
- action.done = true
- }
-
- for mut action in plbook.find(filter: 'juggler.restart')! {
- j.restart()!
- action.done = true
- }
-}
-
-```
-
-File: /Users/despiegk/code/github/freeflowuniverse/herolib/lib/core/playcmds/_archive/play_publisher_test.v
-```v
-module playcmds
-
-import freeflowuniverse.herolib.core.playbook
-import freeflowuniverse.herolib.core.playcmds
-import freeflowuniverse.herolib.core.pathlib
-import os
-
-fn test_play_publisher() {
- mut p := pathlib.get_file(path: '/tmp/heroscript/do.hero', create: true)!
-
- s2 := "
-
-!!publisher.new_collection
- url:'https://git.threefold.info/tfgrid/info_tfgrid/src/branch/main/collections'
- reset: false
- pull: true
-
-
-!!book.define
- name:'info_tfgrid'
- summary_url:'https://git.threefold.info/tfgrid/info_tfgrid/src/branch/development/books/tech/SUMMARY.md'
- title:'ThreeFold Technology'
- collections: 'about,dashboard,farmers,library,partners_utilization,tech,p2p'
-
-
-!!book.publish
- name:'tech'
- production: false
-"
- p.write(s2)!
-
- mut plbook := playbook.new(path: '/tmp/heroscript')!
- playcmds.play_publisher(mut plbook)!
-}
-
-```
-
-File: /Users/despiegk/code/github/freeflowuniverse/herolib/lib/core/playcmds/_archive/play_publisher.v
-```v
-module playcmds
-
-import freeflowuniverse.herolib.core.playbook
-// import freeflowuniverse.herolib.hero.publishing
-
-// pub fn play_publisher(mut plbook playbook.PlayBook) ! {
-// publishing.play(mut plbook)!
-// }
-
-```
-
-File: /Users/despiegk/code/github/freeflowuniverse/herolib/lib/core/playcmds/_archive/play_threefold.v
-```v
-module playcmds
-
-import freeflowuniverse.herolib.core.playbook
-// import freeflowuniverse.herolib.threefold.grid
-// import freeflowuniverse.herolib.threefold.tfrobot
-// import os
-
-// pub fn play_threefold(mut plbook playbook.PlayBook) ! {
-// panic('fix tfrobot module')
-// // mut config_actions := plbook.find(filter: 'threefold.configure')!
-
-// // mnemonics_ := os.getenv_opt('TFGRID_MNEMONIC') or { '' }
-// // mut ssh_key := os.getenv_opt('SSH_KEY') or { '' }
-
-// // tfrobot.configure('play', network: 'main', mnemonics: mnemonics_)!
-
-// // mut robot := tfrobot.get('play')!
-
-// // if config_actions.len > 1 {
-// // return error('can only have 1 config action for threefold')
-// // } else if config_actions.len == 1 {
-// // mut a := config_actions[0]
-// // mut p := a.params
-// // mut network := p.get_default('network', 'main')!
-// // mnemonics := p.get_default('mnemonics', '')!
-// // ssh_key = p.get_default('ssh_key', '')!
-
-// // network = network.to_lower()
-
-// // // mnemonics string
-// // // network string = 'main'
-// // tfrobot.configure('play', network: network, mnemonics: mnemonics)!
-
-// // robot = tfrobot.get('play')!
-
-// // config_actions[0].done = true
-// // }
-// // cfg := robot.config()!
-// // if cfg.mnemonics == '' {
-// // return error('TFGRID_MNEMONIC should be specified as env variable')
-// // }
-
-// // if ssh_key == '' {
-// // return error('SSHKey should be specified as env variable')
-// // }
-
-// // panic('implement')
-
-// // for mut action in plbook.find(filter: 'threefold.deploy_vm')! {
-// // mut p := action.params
-// // deployment_name := p.get_default('deployment_name', 'deployment')!
-// // name := p.get_default('name', 'vm')!
-// // ssh_key := p.get_default('ssh_key', '')!
-// // cores := p.get_int_default('cores', 1)!
-// // memory := p.get_int_default('memory', 20)!
-// // panic("implement")
-// // action.done = true
-// // }
-
-// // for mut action in plbook.find(filter: 'threefold.deploy_zdb')! {
-// // panic("implement")
-// // action.done = true
-// // }
-// }
-
-```
-
-File: /Users/despiegk/code/github/freeflowuniverse/herolib/lib/core/playcmds/_archive/play_zola.v
-```v
-module playcmds
-
-// import freeflowuniverse.herolib.ui.console
-// import freeflowuniverse.herolib.web.zola
-// import freeflowuniverse.herolib.core.playbook
-
-// struct WebsiteItem {
-// mut:
-// name string
-// site ?&zola.ZolaSite
-// }
-
-// pub fn play_zola(mut plbook playbook.PlayBook) ! {
-// // mut coderoot := ''
-// mut buildroot := ''
-// mut publishroot := ''
-// mut install := true
-// mut reset := false
-
-// wsactions := plbook.find(filter: 'website.')!
-// if wsactions.len == 0 {
-// return
-// }
-
-// mut config_actions := plbook.find(filter: 'websites:configure')!
-// if config_actions.len > 1 {
-// return error('can only have 1 config action for websites')
-// } else if config_actions.len == 1 {
-// mut p := config_actions[0].params
-// buildroot = p.get_default('buildroot', '')!
-// publishroot = p.get_default('publishroot', '')!
-// // coderoot = p.get_default('coderoot', '')!
-// install = p.get_default_true('install')
-// reset = p.get_default_false('reset')
-// config_actions[0].done = true
-// }
-// mut websites := zola.new(
-// path_build: buildroot
-// path_publish: publishroot
-// install: install
-// reset: reset
-// )!
-
-// mut ws := WebsiteItem{}
-
-// for mut action in plbook.find(filter: 'website.')! {
-// if action.name == 'define' {
-// console.print_debug('website.define')
-// mut p := action.params
-// ws.name = p.get('name')!
-// title := p.get_default('title', '')!
-// description := p.get_default('description', '')!
-// ws.site = websites.new(name: ws.name, title: title, description: description)!
-// } else if action.name == 'template_add' {
-// console.print_debug('website.template_add')
-// mut p := action.params
-// url := p.get_default('url', '')!
-// mut site_ := ws.site or {
-// return error("can't find website for template_add, should have been defined before with !!website.define")
-// }
-
-// site_.template_add(url: url)!
-// } else if action.name == 'content_add' {
-// console.print_debug('website.content_add')
-// mut p := action.params
-// url := p.get_default('url', '')!
-// mut site_ := ws.site or {
-// return error("can't find website for content_add, should have been defined before with !!website.define")
-// }
-
-// site_.content_add(url: url)!
-// } else if action.name == 'doctree_add' {
-// console.print_debug('website.doctree_add')
-// mut p := action.params
-// url := p.get_default('url', '')!
-// pull := p.get_default_false('pull')
-// mut site_ := ws.site or {
-// return error("can't find website for doctree_add, should have been defined before with !!website.define")
-// }
-
-// site_.doctree_add(url: url, pull: pull)!
-// } else if action.name == 'post_add' {
-// console.print_debug('website.post_add')
-// mut p := action.params
-// name := p.get_default('name', '')!
-// collection := p.get_default('collection', '')!
-// file := p.get_default('file', '')!
-// page := p.get_default('page', '')!
-// pointer := p.get_default('pointer', '')!
-// mut site_ := ws.site or {
-// return error("can't find website for doctree_add, should have been defined before with !!website.define")
-// }
-
-// site_.post_add(name: name, collection: collection, file: file, pointer: pointer)!
-// } else if action.name == 'blog_add' {
-// console.print_debug('website.blog_add')
-// mut p := action.params
-// name := p.get_default('name', '')!
-// collection := p.get_default('collection', '')!
-// file := p.get_default('file', '')!
-// page := p.get_default('page', '')!
-// pointer := p.get_default('pointer', '')!
-// mut site_ := ws.site or {
-// return error("can't find website for doctree_add, should have been defined before with !!website.define")
-// }
-
-// site_.blog_add(name: name)!
-// } else if action.name == 'person_add' {
-// console.print_debug('website.person_add')
-// mut p := action.params
-// name := p.get_default('name', '')!
-// page := p.get_default('page', '')!
-// collection := p.get_default('collection', '')!
-// file := p.get_default('file', '')!
-// pointer := p.get_default('pointer', '')!
-// mut site_ := ws.site or {
-// return error("can't find website for doctree_add, should have been defined before with !!website.define")
-// }
-
-// site_.person_add(
-// name: name
-// collection: collection
-// file: file
-// page: page
-// pointer: pointer
-// )!
-// } else if action.name == 'people_add' {
-// console.print_debug('website.people_add')
-// mut p := action.params
-// name := p.get_default('name', '')!
-// description := p.get_default('description', '')!
-// sort_by_ := p.get_default('sort_by', '')!
-// mut site_ := ws.site or {
-// return error("can't find website for people_add, should have been defined before with !!website.define")
-// }
-
-// sort_by := zola.SortBy.from(sort_by_)!
-// site_.people_add(
-// name: name
-// title: p.get_default('title', '')!
-// sort_by: sort_by
-// description: description
-// )!
-// } else if action.name == 'blog_add' {
-// console.print_debug('website.blog_add')
-// mut p := action.params
-// name := p.get_default('name', '')!
-// description := p.get_default('description', '')!
-// sort_by_ := p.get_default('sort_by', '')!
-// mut site_ := ws.site or {
-// return error("can't find website for people_add, should have been defined before with !!website.define")
-// }
-
-// sort_by := zola.SortBy.from(sort_by_)!
-// site_.blog_add(
-// name: name
-// title: p.get_default('title', '')!
-// sort_by: sort_by
-// description: description
-// )!
-// } else if action.name == 'news_add' {
-// console.print_debug('website.news_add')
-// mut p := action.params
-// name := p.get_default('name', '')!
-// collection := p.get_default('collection', '')!
-// pointer := p.get_default('pointer', '')!
-// file := p.get_default('file', '')!
-// mut site_ := ws.site or {
-// return error("can't find website for news_add, should have been defined before with !!website.define")
-// }
-
-// site_.article_add(name: name, collection: collection, file: file, pointer: pointer)!
-// } else if action.name == 'header_add' {
-// console.print_debug('website.header_add')
-// mut p := action.params
-// template := p.get_default('template', '')!
-// logo := p.get_default('logo', '')!
-// mut site_ := ws.site or {
-// return error("can't find website for doctree_add, should have been defined before with !!website.define")
-// }
-
-// site_.header_add(template: template, logo: logo)!
-// } else if action.name == 'header_link_add' {
-// console.print_debug('website.header_link_add')
-// mut p := action.params
-// page := p.get_default('page', '')!
-// label := p.get_default('label', '')!
-// mut site_ := ws.site or {
-// return error("can't find website for header_link_add, should have been defined before with !!website.define")
-// }
-
-// site_.header_link_add(page: page, label: label)!
-// } else if action.name == 'footer_add' {
-// console.print_debug('website.footer_add')
-// mut p := action.params
-// template := p.get_default('template', '')!
-// mut site_ := ws.site or {
-// return error("can't find website for doctree_add, should have been defined before with !!website.define")
-// }
-
-// site_.footer_add(template: template)!
-// } else if action.name == 'page_add' {
-// console.print_debug('website.page_add')
-// mut p := action.params
-// name := p.get_default('name', '')!
-// collection := p.get_default('collection', '')!
-// file := p.get_default('file', '')!
-// homepage := p.get_default_false('homepage')
-// mut site_ := ws.site or {
-// return error("can't find website for doctree_add, should have been defined before with !!website.define")
-// }
-
-// site_.page_add(name: name, collection: collection, file: file, homepage: homepage)!
-
-// // }else if action.name=="pull"{
-// // mut site_:=ws.site or { return error("can't find website for pull, should have been defined before with !!website.define")}
-// // site_.pull()!
-// } else if action.name == 'section_add' {
-// console.print_debug('website.section_add')
-// // mut p := action.params
-// // name := p.get_default('name', '')!
-// // // collection := p.get_default('collection', '')!
-// // // file := p.get_default('file', '')!
-// // // homepage := p.get_default_false('homepage')
-// // mut site_ := ws.site or {
-// // return error("can't find website for doctree_add, should have been defined before with !!website.define")
-// // }
-
-// // site_.add_section(name: name)!
-
-// // }else if action.name=="pull"{
-// // mut site_:=ws.site or { return error("can't find website for pull, should have been defined before with !!website.define")}
-// // site_.pull()!
-// } else if action.name == 'generate' {
-// mut site_ := ws.site or {
-// return error("can't find website for generate, should have been defined before with !!website.define")
-// }
-
-// site_.generate()!
-// // site_.serve()!
-// } else {
-// return error("Cannot find right action for website. Found '${action.name}' which is a non understood action for !!website.")
-// }
-// action.done = true
-// }
-// }
-
-```
-
-File: /Users/despiegk/code/github/freeflowuniverse/herolib/lib/core/playcmds/factory.v
-```v
-module playcmds
-
-import freeflowuniverse.herolib.core.playbook { PlayBook }
-import freeflowuniverse.herolib.data.doctree
-import freeflowuniverse.herolib.biz.bizmodel
-import freeflowuniverse.herolib.web.site
-import freeflowuniverse.herolib.web.docusaurus
-import freeflowuniverse.herolib.clients.openai
-
-// -------------------------------------------------------------------
-// run – entry point for all HeroScript play‑commands
-// -------------------------------------------------------------------
-
-@[params]
-pub struct PlayArgs {
-pub mut:
- heroscript string
- heroscript_path string
- plbook ?PlayBook
- reset bool
-}
-
-pub fn run(args_ PlayArgs) ! {
- mut args := args_
- mut plbook := args.plbook or {
- playbook.new(text: args.heroscript, path: args.heroscript_path)!
- }
-
- // Core actions
- play_core(mut plbook)!
- // Git actions
- play_git(mut plbook)!
-
- // Business model (e.g. currency, bizmodel)
- bizmodel.play(mut plbook)!
-
- // OpenAI client
- openai.play(mut plbook)!
-
- // Website / docs
- site.play(mut plbook)!
- doctree.play(mut plbook)!
- docusaurus.play(mut plbook)!
-
- // Ensure we did not leave any actions un‑processed
- plbook.empty_check()!
-}
-
-```
-
-File: /Users/despiegk/code/github/freeflowuniverse/herolib/lib/core/playcmds/play_core.v
-```v
-module playcmds
-
-import freeflowuniverse.herolib.develop.gittools
-import freeflowuniverse.herolib.core.playbook { PlayBook }
-import freeflowuniverse.herolib.ui.console
-import freeflowuniverse.herolib.core.texttools
-
-// -------------------------------------------------------------------
-// Core play‑command processing (context, session, env‑subst, etc)
-// -------------------------------------------------------------------
-
-fn play_core(mut plbook PlayBook) ! {
- // ----------------------------------------------------------------
- // 1. Include handling (play include / echo)
- // ----------------------------------------------------------------
- // Track included paths to prevent infinite recursion
- mut included_paths := map[string]bool{}
-
- for action_ in plbook.find(filter: 'play.*')! {
- if action_.name == 'include' {
- console.print_debug('play run:${action_}')
- mut action := *action_
- mut playrunpath := action.params.get_default('path', '')!
- if playrunpath.len == 0 {
- action.name = 'pull'
- playrunpath = gittools.path(
- path: action.params.get_default('path', '')!
- git_url: action.params.get_default('git_url', '')!
- git_reset: action.params.get_default_false('git_reset')
- git_pull: action.params.get_default_false('git_pull')
- )!
- }
- if playrunpath.len == 0 {
- return error("can't run a heroscript didn't find url or path.")
- }
-
- // Check for cycle detection
- if playrunpath in included_paths {
- console.print_debug('Skipping already included path: ${playrunpath}')
- continue
- }
-
- console.print_debug('play run path:${playrunpath}')
- included_paths[playrunpath] = true
- plbook.add(path: playrunpath)!
- }
- if action_.name == 'echo' {
- content := action_.params.get_default('content', "didn't find content")!
- console.print_header(content)
- }
- }
-
- // ----------------------------------------------------------------
- // 2. Session environment handling
- // ----------------------------------------------------------------
- // Guard – make sure a session exists
- mut session := plbook.session
-
- // !!session.env_set / env_set_once
- for mut action in plbook.find(filter: 'session.')! {
- mut p := action.params
- match action.name {
- 'env_set' {
- key := p.get('key')!
- val := p.get('val') or { p.get('value')! }
- session.env_set(key, val)!
- }
- 'env_set_once' {
- key := p.get('key')!
- val := p.get('val') or { p.get('value')! }
- // Use the dedicated “set‑once” method
- session.env_set_once(key, val)!
- }
- else { /* ignore unknown sub‑action */ }
- }
- action.done = true
- }
-
- // ----------------------------------------------------------------
- // 3. Template replacement in action parameters
- // ----------------------------------------------------------------
- // Apply template replacement from session environment variables
- if session.env.len > 0 {
- // Create a map with name_fix applied to keys for template replacement
- mut env_fixed := map[string]string{}
- for key, value in session.env {
- env_fixed[texttools.name_fix(key)] = value
- }
-
- for mut action in plbook.actions {
- if !action.done {
- action.params.replace(env_fixed)
- }
- }
- }
-
- for mut action in plbook.find(filter: 'core.coderoot_set')! {
- mut p := action.params
- if p.exists('coderoot') {
- coderoot := p.get_path_create('coderoot')!
- if session.context.config.coderoot != coderoot {
- session.context.config.coderoot = coderoot
- session.context.save()!
- }
- } else {
- return error('coderoot needs to be specified')
- }
- action.done = true
- }
-
- for mut action in plbook.find(filter: 'core.params_context_set')! {
- mut p := action.params
- mut context_params := session.context.params()!
- for param in p.params {
- context_params.set(param.key, param.value)
- }
- session.context.save()!
- action.done = true
- }
-
- for mut action in plbook.find(filter: 'core.params_session_set')! {
- mut p := action.params
- for param in p.params {
- session.params.set(param.key, param.value)
- }
- session.save()!
- action.done = true
- }
-}
-
-```
-
-
-these are my instructions what needs to be done with the attached code
-
-TODO…
-
diff --git a/lib/installers/db/cometbft/readme.md b/lib/installers/db/cometbft/readme.md
index 00c6b32a..412a9a09 100644
--- a/lib/installers/db/cometbft/readme.md
+++ b/lib/installers/db/cometbft/readme.md
@@ -4,7 +4,7 @@
To get started
-```vlang
+```v
import freeflowuniverse.herolib.installers.db.cometbft as cometbft_installer
diff --git a/lib/installers/db/meilisearch_installer/readme.md b/lib/installers/db/meilisearch_installer/readme.md
index 46a6e264..92e61d3b 100644
--- a/lib/installers/db/meilisearch_installer/readme.md
+++ b/lib/installers/db/meilisearch_installer/readme.md
@@ -4,7 +4,7 @@
To get started
-```vlang
+```v
import freeflowuniverse.herolib.installers.db.meilisearch as meilisearchinstaller
diff --git a/lib/installers/db/postgresql/readme.md b/lib/installers/db/postgresql/readme.md
index 06a25bf5..ba45ab97 100644
--- a/lib/installers/db/postgresql/readme.md
+++ b/lib/installers/db/postgresql/readme.md
@@ -4,7 +4,7 @@
To get started
-```vlang
+```v
diff --git a/lib/installers/db/qdrant_installer/readme.md b/lib/installers/db/qdrant_installer/readme.md
index 8e5f6b78..cc2e142f 100644
--- a/lib/installers/db/qdrant_installer/readme.md
+++ b/lib/installers/db/qdrant_installer/readme.md
@@ -4,7 +4,7 @@ Is a powerfull db for embedding for AI Agents.
To get started
-```vlang
+```v
import freeflowuniverse.herolib.installers.db.qdrant_installer
diff --git a/lib/installers/infra/coredns/readme.md b/lib/installers/infra/coredns/readme.md
index 15fa18a8..9c8b59ef 100644
--- a/lib/installers/infra/coredns/readme.md
+++ b/lib/installers/infra/coredns/readme.md
@@ -4,7 +4,7 @@ coredns
To get started
-```vlang
+```v
import freeflowuniverse.herolib.installers.infra.coredns as coredns_installer
diff --git a/lib/installers/infra/gitea/readme.md b/lib/installers/infra/gitea/readme.md
index 9dfb7153..9c14c56e 100644
--- a/lib/installers/infra/gitea/readme.md
+++ b/lib/installers/infra/gitea/readme.md
@@ -4,7 +4,7 @@
To get started
-```vlang
+```v
import freeflowuniverse.herolib.installers.infra.gitea as gitea_installer
diff --git a/lib/installers/infra/livekit/readme.md b/lib/installers/infra/livekit/readme.md
index 61c79442..df226a99 100644
--- a/lib/installers/infra/livekit/readme.md
+++ b/lib/installers/infra/livekit/readme.md
@@ -4,7 +4,7 @@
To get started
-```vlang
+```v
diff --git a/lib/installers/infra/screen/readme.md b/lib/installers/infra/screen/readme.md
index f8480f1f..8b13f311 100644
--- a/lib/installers/infra/screen/readme.md
+++ b/lib/installers/infra/screen/readme.md
@@ -4,7 +4,7 @@
To get started
-```vlang
+```v
import freeflowuniverse.herolib.installers.something.screen as screen_installer
diff --git a/lib/installers/infra/zinit_installer/readme.md b/lib/installers/infra/zinit_installer/readme.md
index a5130131..a1994d3c 100644
--- a/lib/installers/infra/zinit_installer/readme.md
+++ b/lib/installers/infra/zinit_installer/readme.md
@@ -4,7 +4,7 @@
To get started
-```vlang
+```v
import freeflowuniverse.herolib.installers.something. zinit
diff --git a/lib/installers/lang/golang/readme.md b/lib/installers/lang/golang/readme.md
index 0d8346a1..ca110c61 100644
--- a/lib/installers/lang/golang/readme.md
+++ b/lib/installers/lang/golang/readme.md
@@ -4,7 +4,7 @@
To get started
-```vlang
+```v
import freeflowuniverse.herolib.installers.lang.golang
diff --git a/lib/installers/lang/nodejs/readme.md b/lib/installers/lang/nodejs/readme.md
index 14f8478e..04585cd9 100644
--- a/lib/installers/lang/nodejs/readme.md
+++ b/lib/installers/lang/nodejs/readme.md
@@ -4,7 +4,7 @@
To get started
-```vlang
+```v
import freeflowuniverse.herolib.installers.something.nodejs as nodejs_installer
diff --git a/lib/installers/lang/python/readme.md b/lib/installers/lang/python/readme.md
index f4046274..8b80bde6 100644
--- a/lib/installers/lang/python/readme.md
+++ b/lib/installers/lang/python/readme.md
@@ -4,7 +4,7 @@
To get started
-```vlang
+```v
import freeflowuniverse.herolib.installers.something.python as python_installer
diff --git a/lib/installers/lang/rust/readme.md b/lib/installers/lang/rust/readme.md
index 1aa9a9a1..3b251986 100644
--- a/lib/installers/lang/rust/readme.md
+++ b/lib/installers/lang/rust/readme.md
@@ -4,7 +4,7 @@
To get started
-```vlang
+```v
import freeflowuniverse.herolib.installers.something.rust as rust_installer
diff --git a/lib/installers/net/wireguard_installer/readme.md b/lib/installers/net/wireguard_installer/readme.md
index 73a58c61..8047b5c1 100644
--- a/lib/installers/net/wireguard_installer/readme.md
+++ b/lib/installers/net/wireguard_installer/readme.md
@@ -4,7 +4,7 @@
To get started
-```vlang
+```v
import freeflowuniverse.herolib.installers.something.wireguard as wireguard_installer
diff --git a/lib/installers/net/yggdrasil/readme.md b/lib/installers/net/yggdrasil/readme.md
index 8da4e9ec..8241c696 100644
--- a/lib/installers/net/yggdrasil/readme.md
+++ b/lib/installers/net/yggdrasil/readme.md
@@ -4,7 +4,7 @@
To get started
-```vlang
+```v
import freeflowuniverse.herolib.installers.something.yggdrasil as yggdrasil_installer
diff --git a/lib/installers/sysadmintools/b2/readme.md b/lib/installers/sysadmintools/b2/readme.md
index 68824662..514ee422 100644
--- a/lib/installers/sysadmintools/b2/readme.md
+++ b/lib/installers/sysadmintools/b2/readme.md
@@ -4,7 +4,7 @@
To get started
-```vlang
+```v
import freeflowuniverse.herolib.installers.something.b2 as b2_installer
diff --git a/lib/installers/sysadmintools/fungistor/readme.md b/lib/installers/sysadmintools/fungistor/readme.md
index 731a2330..7f0d665e 100644
--- a/lib/installers/sysadmintools/fungistor/readme.md
+++ b/lib/installers/sysadmintools/fungistor/readme.md
@@ -4,7 +4,7 @@
To get started
-```vlang
+```v
import freeflowuniverse.herolib.installers.something.fungistor as fungistor_installer
diff --git a/lib/installers/sysadmintools/garage_s3/readme.md b/lib/installers/sysadmintools/garage_s3/readme.md
index 04075960..bd280790 100644
--- a/lib/installers/sysadmintools/garage_s3/readme.md
+++ b/lib/installers/sysadmintools/garage_s3/readme.md
@@ -4,7 +4,7 @@
To get started
-```vlang
+```v
diff --git a/lib/installers/sysadmintools/grafana/readme.md b/lib/installers/sysadmintools/grafana/readme.md
index 2be52a90..14d7b25f 100644
--- a/lib/installers/sysadmintools/grafana/readme.md
+++ b/lib/installers/sysadmintools/grafana/readme.md
@@ -4,7 +4,7 @@
To get started
-```vlang
+```v
import freeflowuniverse.herolib.installers.something.grafana as grafana_installer
diff --git a/lib/installers/sysadmintools/rclone/readme.md b/lib/installers/sysadmintools/rclone/readme.md
index 09428d8a..99f5165c 100644
--- a/lib/installers/sysadmintools/rclone/readme.md
+++ b/lib/installers/sysadmintools/rclone/readme.md
@@ -4,7 +4,7 @@
To get started
-```vlang
+```v
diff --git a/lib/installers/sysadmintools/restic/readme.md b/lib/installers/sysadmintools/restic/readme.md
index 61863bd2..0e8d2013 100644
--- a/lib/installers/sysadmintools/restic/readme.md
+++ b/lib/installers/sysadmintools/restic/readme.md
@@ -4,7 +4,7 @@
To get started
-```vlang
+```v
import freeflowuniverse.herolib.installers.something.restic as restic_installer
diff --git a/lib/installers/threefold/griddriver/readme.md b/lib/installers/threefold/griddriver/readme.md
index 990b8c23..11dea35a 100644
--- a/lib/installers/threefold/griddriver/readme.md
+++ b/lib/installers/threefold/griddriver/readme.md
@@ -2,7 +2,7 @@
To use the installer:
-```vlang
+```v
import freeflowuniverse.herolib.installers.threefold.griddriver
fn main() {
diff --git a/lib/installers/virt/cloudhypervisor/readme.md b/lib/installers/virt/cloudhypervisor/readme.md
index 1d0d0ef7..698213a5 100644
--- a/lib/installers/virt/cloudhypervisor/readme.md
+++ b/lib/installers/virt/cloudhypervisor/readme.md
@@ -4,7 +4,7 @@
To get started
-```vlang
+```v
import freeflowuniverse.herolib.installers.something. cloudhypervisor
diff --git a/lib/installers/virt/docker/readme.md b/lib/installers/virt/docker/readme.md
index 8cb368a3..ac886ba7 100644
--- a/lib/installers/virt/docker/readme.md
+++ b/lib/installers/virt/docker/readme.md
@@ -2,7 +2,7 @@
To get started
-```vlang
+```v
import freeflowuniverse.herolib.installers.something.docker as docker_installer
diff --git a/lib/installers/virt/pacman/readme.md b/lib/installers/virt/pacman/readme.md
index 42007738..ecf9ab55 100644
--- a/lib/installers/virt/pacman/readme.md
+++ b/lib/installers/virt/pacman/readme.md
@@ -4,7 +4,7 @@
To get started
-```vlang
+```v
import freeflowuniverse.herolib.installers.virt.pacman
diff --git a/lib/installers/virt/podman/readme.md b/lib/installers/virt/podman/readme.md
index 968205bd..f8de50a4 100644
--- a/lib/installers/virt/podman/readme.md
+++ b/lib/installers/virt/podman/readme.md
@@ -7,7 +7,7 @@ Podman is a lightweight container manager that allows users to manage and run co
The following example demonstrates how to use the Podman installer in a VLang script. It checks if Podman is installed, removes it if found, or installs it if not.
### Example Code (VLang)
-```vlang
+```v
import freeflowuniverse.herolib.installers.virt.podman as podman_installer
mut podman := podman_installer.get()!
diff --git a/lib/installers/virt/youki/readme.md b/lib/installers/virt/youki/readme.md
index 0c20bfc6..781dcdf6 100644
--- a/lib/installers/virt/youki/readme.md
+++ b/lib/installers/virt/youki/readme.md
@@ -4,7 +4,7 @@
To get started
-```vlang
+```v
import freeflowuniverse.herolib.installers.virt.youki
diff --git a/lib/installers/web/bun/readme.md b/lib/installers/web/bun/readme.md
index e756650f..2ca7cb7f 100644
--- a/lib/installers/web/bun/readme.md
+++ b/lib/installers/web/bun/readme.md
@@ -4,7 +4,7 @@
To get started
-```vlang
+```v
import freeflowuniverse.herolib.installers.something.bun as bun_installer
diff --git a/lib/installers/web/imagemagick/readme.md b/lib/installers/web/imagemagick/readme.md
index ca621788..36cc4439 100644
--- a/lib/installers/web/imagemagick/readme.md
+++ b/lib/installers/web/imagemagick/readme.md
@@ -4,7 +4,7 @@
To get started
-```vlang
+```v
import freeflowuniverse.herolib.installers.something.imagemagick as imagemagick_installer
diff --git a/lib/installers/web/tailwind/readme.md b/lib/installers/web/tailwind/readme.md
index 098cefeb..3c798a16 100644
--- a/lib/installers/web/tailwind/readme.md
+++ b/lib/installers/web/tailwind/readme.md
@@ -4,7 +4,7 @@
To get started
-```vlang
+```v
diff --git a/lib/installers/web/tailwind4/readme.md b/lib/installers/web/tailwind4/readme.md
index 13f5639a..a9e3dfb3 100644
--- a/lib/installers/web/tailwind4/readme.md
+++ b/lib/installers/web/tailwind4/readme.md
@@ -4,7 +4,7 @@
To get started
-```vlang
+```v
diff --git a/lib/installers/web/traefik/readme.md b/lib/installers/web/traefik/readme.md
index 1409995f..dcf74b3d 100644
--- a/lib/installers/web/traefik/readme.md
+++ b/lib/installers/web/traefik/readme.md
@@ -4,7 +4,7 @@
To get started
-```vlang
+```v
diff --git a/lib/installers/web/zola/readme.md b/lib/installers/web/zola/readme.md
index 2da105e2..5bb3a965 100644
--- a/lib/installers/web/zola/readme.md
+++ b/lib/installers/web/zola/readme.md
@@ -4,7 +4,7 @@
To get started
-```vlang
+```v
diff --git a/lib/threefold/grid3/deployer/readme.md b/lib/threefold/grid3/deployer/readme.md
index 1934c719..21363df6 100644
--- a/lib/threefold/grid3/deployer/readme.md
+++ b/lib/threefold/grid3/deployer/readme.md
@@ -2,7 +2,7 @@
To get started
-```vlang
+```v
diff --git a/lib/web/heroprompt/endpoints.v b/lib/web/heroprompt/endpoints.v
deleted file mode 100644
index a48a45ad..00000000
--- a/lib/web/heroprompt/endpoints.v
+++ /dev/null
@@ -1,315 +0,0 @@
-module heroprompt
-
-import veb
-import os
-import json
-import time
-import freeflowuniverse.herolib.develop.heroprompt as hp
-
-// Types for directory listing
-struct DirItem {
- name string
- typ string @[json: 'type']
-}
-
-struct DirResp {
- path string
-mut:
- items []DirItem
-}
-
-// HTML routes
-@['/heroprompt'; get]
-pub fn (app &App) page_index(mut ctx Context) veb.Result {
- return ctx.html(render_index(app))
-}
-
-// API routes (thin wrappers over develop.heroprompt)
-@['/api/heroprompt/workspaces'; get]
-pub fn (app &App) api_list(mut ctx Context) veb.Result {
- mut names := []string{}
- ws := hp.list(fromdb: true) or { []&hp.Workspace{} }
- for w in ws {
- names << w.name
- }
- ctx.set_content_type('application/json')
- return ctx.text(json.encode(names))
-}
-
-@['/api/heroprompt/workspaces'; post]
-pub fn (app &App) api_create(mut ctx Context) veb.Result {
- name := ctx.form['name'] or { 'default' }
- base_path_in := ctx.form['base_path'] or { '' }
- if base_path_in.len == 0 {
- return ctx.text('{"error":"base_path required"}')
- }
- mut base_path := base_path_in
- // Expand tilde to user home
- if base_path.starts_with('~') {
- home := os.home_dir()
- base_path = os.join_path(home, base_path.all_after('~'))
- }
- _ := hp.get(name: name, create: true, path: base_path) or {
- return ctx.text('{"error":"create failed"}')
- }
- ctx.set_content_type('application/json')
- return ctx.text(json.encode({
- 'name': name
- 'base_path': base_path
- }))
-}
-
-@['/api/heroprompt/directory'; get]
-pub fn (app &App) api_directory(mut ctx Context) veb.Result {
- wsname := ctx.query['name'] or { 'default' }
- path_q := ctx.query['path'] or { '' }
- mut wsp := hp.get(name: wsname, create: false) or {
- return ctx.text('{"error":"workspace not found"}')
- }
- // Use workspace list method; empty path means base_path
- items_w := if path_q.len > 0 { wsp.list() or {
- return ctx.text('{"error":"cannot list directory"}')} } else { wsp.list() or {
- return ctx.text('{"error":"cannot list directory"}')} }
- ctx.set_content_type('application/json')
- mut resp := DirResp{
- path: if path_q.len > 0 { path_q } else { wsp.base_path }
- }
- for it in items_w {
- resp.items << DirItem{
- name: it.name
- typ: it.typ
- }
- }
- return ctx.text(json.encode(resp))
-}
-
-// -------- File content endpoint --------
-struct FileResp {
- language string
- content string
-}
-
-@['/api/heroprompt/file'; get]
-pub fn (app &App) api_file(mut ctx Context) veb.Result {
- wsname := ctx.query['name'] or { 'default' }
- path_q := ctx.query['path'] or { '' }
- if path_q.len == 0 {
- return ctx.text('{"error":"path required"}')
- }
- mut base := ''
- if wsp := hp.get(name: wsname, create: false) {
- base = wsp.base_path
- }
- mut file_path := if !os.is_abs_path(path_q) && base.len > 0 {
- os.join_path(base, path_q)
- } else {
- path_q
- }
- if !os.is_file(file_path) {
- return ctx.text('{"error":"not a file"}')
- }
- // limit read to 1MB to avoid huge responses
- max_size := i64(1_000_000)
- sz := os.file_size(file_path)
- if sz > max_size {
- return ctx.text('{"error":"file too large"}')
- }
- content := os.read_file(file_path) or { return ctx.text('{"error":"failed to read"}') }
- lang := detect_lang(file_path)
- ctx.set_content_type('application/json')
- return ctx.text(json.encode(FileResp{ language: lang, content: content }))
-}
-
-fn detect_lang(path string) string {
- ext := os.file_ext(path).trim_left('.')
- return match ext.to_lower() {
- 'v' { 'v' }
- 'js' { 'javascript' }
- 'ts' { 'typescript' }
- 'py' { 'python' }
- 'rs' { 'rust' }
- 'go' { 'go' }
- 'java' { 'java' }
- 'c', 'h' { 'c' }
- 'cpp', 'hpp', 'cc', 'hh' { 'cpp' }
- 'sh', 'bash' { 'bash' }
- 'json' { 'json' }
- 'yaml', 'yml' { 'yaml' }
- 'html', 'htm' { 'html' }
- 'css' { 'css' }
- 'md' { 'markdown' }
- else { 'text' }
- }
-}
-
-// -------- Filename search endpoint --------
-struct SearchItem {
- path string
- typ string @[json: 'type']
-}
-
-@['/api/heroprompt/search'; get]
-pub fn (app &App) api_search(mut ctx Context) veb.Result {
- wsname := ctx.query['name'] or { 'default' }
- q := ctx.query['q'] or { '' }
- if q.len == 0 {
- return ctx.text('{"error":"q required"}')
- }
- mut base := ''
- if wsp := hp.get(name: wsname, create: false) {
- base = wsp.base_path
- }
- if base.len == 0 {
- return ctx.text('{"error":"workspace base_path not set"}')
- }
- max := (ctx.query['max'] or { '200' }).int()
- mut results := []SearchItem{}
- walk_search(base, q, max, mut results)
- ctx.set_content_type('application/json')
- return ctx.text(json.encode(results))
-}
-
-// Workspace details
-@['/api/heroprompt/workspaces/:name'; get]
-pub fn (app &App) api_workspace_get(mut ctx Context, name string) veb.Result {
- wsp := hp.get(name: name, create: false) or {
- return ctx.text('{"error":"workspace not found"}')
- }
- ctx.set_content_type('application/json')
- return ctx.text(json.encode({
- 'name': wsp.name
- 'base_path': wsp.base_path
- }))
-}
-
-@['/api/heroprompt/workspaces/:name'; delete]
-pub fn (app &App) api_workspace_delete(mut ctx Context, name string) veb.Result {
- wsp := hp.get(name: name, create: false) or {
- return ctx.text('{"error":"workspace not found"}')
- }
- wsp.delete_workspace() or { return ctx.text('{"error":"delete failed"}') }
- return ctx.text('{"ok":true}')
-}
-
-@['/api/heroprompt/workspaces/:name'; patch]
-pub fn (app &App) api_workspace_patch(mut ctx Context, name string) veb.Result {
- wsp := hp.get(name: name, create: false) or {
- return ctx.text('{"error":"workspace not found"}')
- }
- new_name := ctx.form['name'] or { '' }
- mut base_path := ctx.form['base_path'] or { '' }
- if base_path.len > 0 && base_path.starts_with('~') {
- home := os.home_dir()
- base_path = os.join_path(home, base_path.all_after('~'))
- }
- updated := wsp.update_workspace(name: new_name, base_path: base_path) or {
- return ctx.text('{"error":"update failed"}')
- }
- ctx.set_content_type('application/json')
- return ctx.text(json.encode({
- 'name': updated.name
- 'base_path': updated.base_path
- }))
-}
-
-// -------- Path validation endpoint --------
-struct PathValidationResp {
- is_abs bool
- exists bool
- is_dir bool
- expanded string
-}
-
-@['/api/heroprompt/validate_path'; get]
-pub fn (app &App) api_validate_path(mut ctx Context) veb.Result {
- p_in := ctx.query['path'] or { '' }
- mut p := p_in
- if p.starts_with('~') {
- home := os.home_dir()
- p = os.join_path(home, p.all_after('~'))
- }
- is_abs := if p != '' { os.is_abs_path(p) } else { false }
- exists := if p != '' { os.exists(p) } else { false }
- isdir := if exists { os.is_dir(p) } else { false }
- ctx.set_content_type('application/json')
- resp := PathValidationResp{
- is_abs: is_abs
- exists: exists
- is_dir: isdir
- expanded: p
- }
- return ctx.text(json.encode(resp))
-}
-
-fn walk_search(root string, q string, max int, mut out []SearchItem) {
- if out.len >= max {
- return
- }
- entries := os.ls(root) or { return }
- for e in entries {
- if e in ['.git', 'node_modules', 'build', 'dist', '.v'] {
- continue
- }
- p := os.join_path(root, e)
- if os.is_dir(p) {
- if out.len >= max {
- return
- }
- if e.to_lower().contains(q.to_lower()) {
- out << SearchItem{
- path: p
- typ: 'directory'
- }
- }
- walk_search(p, q, max, mut out)
- } else if os.is_file(p) {
- if e.to_lower().contains(q.to_lower()) {
- out << SearchItem{
- path: p
- typ: 'file'
- }
- }
- }
- if out.len >= max {
- return
- }
- }
-}
-
-// -------- Selection and prompt endpoints --------
-@['/api/heroprompt/workspaces/:name/files'; post]
-pub fn (app &App) api_add_file(mut ctx Context, name string) veb.Result {
- path := ctx.form['path'] or { '' }
- if path.len == 0 {
- return ctx.text('{"error":"path required"}')
- }
- mut wsp := hp.get(name: name, create: false) or {
- return ctx.text('{"error":"workspace not found"}')
- }
- wsp.add_file(path: path) or { return ctx.text('{"error":"' + err.msg() + '"}') }
- return ctx.text('{"ok":true}')
-}
-
-@['/api/heroprompt/workspaces/:name/dirs'; post]
-pub fn (app &App) api_add_dir(mut ctx Context, name string) veb.Result {
- path := ctx.form['path'] or { '' }
- if path.len == 0 {
- return ctx.text('{"error":"path required"}')
- }
- mut wsp := hp.get(name: name, create: false) or {
- return ctx.text('{"error":"workspace not found"}')
- }
- wsp.add_dir(path: path) or { return ctx.text('{"error":"' + err.msg() + '"}') }
- return ctx.text('{"ok":true}')
-}
-
-@['/api/heroprompt/workspaces/:name/prompt'; post]
-pub fn (app &App) api_generate_prompt(mut ctx Context, name string) veb.Result {
- text := ctx.form['text'] or { '' }
- mut wsp := hp.get(name: name, create: false) or {
- return ctx.text('{"error":"workspace not found"}')
- }
- prompt := wsp.prompt(text: text)
- ctx.set_content_type('text/plain')
- return ctx.text(prompt)
-}
diff --git a/lib/web/heroprompt/server.v b/lib/web/heroprompt/server.v
deleted file mode 100644
index 8ba215cf..00000000
--- a/lib/web/heroprompt/server.v
+++ /dev/null
@@ -1,71 +0,0 @@
-module heroprompt
-
-import veb
-import os
-
-// Public Context type for veb
-pub struct Context {
- veb.Context
-}
-
-// Factory args for starting the server
-@[params]
-pub struct FactoryArgs {
-pub mut:
- host string = 'localhost'
- port int = 8090
- title string = 'Heroprompt'
-}
-
-// App holds server state and config
-pub struct App {
- veb.StaticHandler
-pub mut:
- title string
- port int
- base_path string // absolute path to this module directory
-}
-
-// Create a new App instance (does not start the server)
-pub fn new(args FactoryArgs) !&App {
- base := os.dir(@FILE)
- mut app := App{
- title: args.title
- port: args.port
- base_path: base
- }
- // Serve static assets from this module at /static
- app.mount_static_folder_at(os.join_path(base, 'static'), '/static')!
- return &app
-}
-
-// Start the webserver (blocking)
-pub fn start(args FactoryArgs) ! {
- mut app := new(args)!
- veb.run[App, Context](mut app, app.port)
-}
-
-// Routes
-
-@['/'; get]
-pub fn (app &App) index(mut ctx Context) veb.Result {
- return ctx.html(render_index(app))
-}
-
-// Rendering helpers
-fn render_index(app &App) string {
- tpl := os.join_path(app.base_path, 'templates', 'index.html')
- content := os.read_file(tpl) or { return render_index_fallback(app) }
- return render_template(content, {
- 'title': app.title
- })
-}
-
-fn render_index_fallback(app &App) string {
- return
- '\n
' +
- html_escape(app.title) +
- '' +
- html_escape(app.title) +
- '
Heroprompt server is running.
'
-}
diff --git a/lib/web/heroprompt/static/css/main.css b/lib/web/heroprompt/static/css/main.css
deleted file mode 100644
index 309e68c9..00000000
--- a/lib/web/heroprompt/static/css/main.css
+++ /dev/null
@@ -1,624 +0,0 @@
-:root {
- --bg: #111827;
- --text: #e5e7eb;
- --panel: #0b1220;
- --border: #1f2937;
- --muted: #94a3b8;
- --accent: #93c5fd;
- --input: #0f172a;
- --input-border: #334155;
- --btn: #334155;
- --btn-border: #475569;
-}
-
-:root.light {
- --bg: #f9fafb;
- --text: #0f172a;
- --panel: #ffffff;
- --border: #e5e7eb;
- --muted: #475569;
- --accent: #2563eb;
- --input: #ffffff;
- --input-border: #cbd5e1;
- --btn: #e5e7eb;
- --btn-border: #cbd5e1;
-}
-
-body {
- font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Arial, sans-serif;
- margin: 0;
- padding: 24px;
- background: var(--bg);
- color: var(--text);
-}
-
-h1 {
- font-size: 20px;
- margin: 0 0 12px
-}
-
-.container {
- max-width: 1200px;
- margin: 0 auto
-}
-
-a {
- color: var(--accent)
-}
-
-.toolbar {
- display: flex;
- gap: 8px;
- align-items: center;
- margin-bottom: 12px
-}
-
-.toolbar .spacer {
- flex: 1
-}
-
-.toolbar input {
- background: var(--input);
- border: 1px solid var(--input-border);
- color: var(--text);
- padding: 6px 8px;
- border-radius: 6px
-}
-
-.toolbar button {
- background: var(--btn);
- border: 1px solid var(--btn-border);
- color: var(--text);
- padding: 6px 10px;
- border-radius: 6px;
- cursor: pointer
-}
-
-.layout {
- display: grid;
- grid-template-columns: minmax(300px, 380px) 1fr;
- gap: 16px;
- min-height: 70vh
-}
-
-.sidebar {
- background: var(--panel);
- border: 1px solid var(--border);
- padding: 8px;
- border-radius: 8px
-}
-
-.sidebar-header {
- display: flex;
- gap: 8px;
- row-gap: 6px;
- align-items: center;
- justify-content: space-between;
- margin-bottom: 8px;
- flex-wrap: wrap
-}
-
-.sidebar-header .spacer {
- display: none
-}
-
-.sidebar-header select {
- min-width: 140px;
- max-width: 100%
-}
-
-@media (min-width: 900px) {
- .sidebar-header {
- flex-wrap: nowrap
- }
-
- .sidebar-header .spacer {
- display: block;
- flex: 1
- }
-}
-
-.ws-info {
- font-size: 12px;
- color: var(--muted);
- margin-left: auto
-}
-
-.sidebar-header label {
- font-size: 12px;
- color: var(--muted)
-}
-
-.sidebar-header select {
- background: var(--input);
- border: 1px solid var(--input-border);
- color: var(--text);
- padding: 6px 8px;
- border-radius: 6px
-}
-
-.sidebar-header button {
- background: var(--btn);
- border: 1px solid var(--btn-border);
- color: var(--text);
- padding: 6px 10px;
- border-radius: 6px;
- cursor: pointer;
- white-space: nowrap;
- flex: 0 0 auto
-}
-
-.sidebar-body {
- .sidebar-header #wsCreateBtn {
- width: 32px;
- height: 32px;
- padding: 0;
- line-height: 30px;
- text-align: center;
- font-weight: bold
- }
-
- .hint {
- color: var(--muted);
- font-size: 12px
- }
-
- .prompt .actions {
- margin-top: 8px
- }
-
- .prompt .actions button {
- background: var(--btn);
- border: 1px solid var(--btn-border);
- color: var(--text);
- padding: 6px 10px;
- border-radius: 6px;
- cursor: pointer
- }
-
- display: flex;
- flex-direction: column;
- gap: 8px
-}
-
-.searchbar {
- display: flex;
- gap: 8px
-}
-
-.searchbar input {
- background: var(--input);
- border: 1px solid var(--input-border);
- color: var(--text);
- padding: 6px 8px;
- border-radius: 6px
-}
-
-.searchbar button {
- background: var(--btn);
- border: 1px solid var(--btn-border);
- color: var(--text);
- padding: 6px 10px;
- border-radius: 6px;
- cursor: pointer
-}
-
-.sidebar-header button {
- background: var(--btn);
- border: 1px solid var(--btn-border);
- color: var(--text);
- padding: 6px 10px;
- border-radius: 6px;
- cursor: pointer
-}
-
-.main {
- display: flex;
- flex-direction: column;
-
- .prompt {
- background: var(--panel);
- border: 1px solid var(--border);
- padding: 12px;
- border-radius: 8px
- }
-
- .prompt textarea {
- width: 100%;
- min-height: 120px;
- background: var(--input);
- color: var(--text);
- border: 1px solid var(--input-border);
- border-radius: 6px;
- padding: 8px;
- resize: vertical
- }
-
- gap: 16px
-}
-
-/* Tabs */
-.tabs {
- display: flex;
- justify-content: center;
- gap: 8px;
- margin-bottom: 8px
-}
-
-.tab {
- background: var(--btn);
- border: 1px solid var(--btn-border);
- color: var(--text);
- padding: 6px 10px;
- border-radius: 6px;
- cursor: pointer
-}
-
-.tab.active {
- background: transparent;
- border-color: var(--accent);
- color: var(--accent)
-}
-
-.tab-pane {
- display: none;
-}
-
-.tab-pane.active {
- display: block;
-}
-
-.subbar {
- display: flex;
- gap: 8px;
- align-items: center;
- margin: 8px 0
-}
-
-.subbar input {
- background: var(--input);
- border: 1px solid var(--input-border);
- color: var(--text);
- padding: 6px 8px;
- border-radius: 6px
-}
-
-.subbar button {
- background: var(--btn);
- border: 1px solid var(--btn-border);
- color: var(--text);
- padding: 6px 10px;
- border-radius: 6px;
- cursor: pointer
-}
-
-.subbar input {
- background: var(--input);
- border: 1px solid var(--input-border);
- color: var(--text);
- padding: 6px 8px;
- border-radius: 6px
-}
-
-.subbar button {
- background: var(--btn);
- border: 1px solid var(--btn-border);
- color: var(--text);
- padding: 6px 10px;
- border-radius: 6px;
- cursor: pointer
-}
-
-
-/* Chat */
-.chat {
- background: var(--panel);
- border: 1px solid var(--border);
- border-radius: 8px;
- display: flex;
- flex-direction: column;
- height: 60vh
-}
-
-.messages {
- flex: 1;
- padding: 12px;
- overflow: auto;
- display: flex;
- flex-direction: column;
- gap: 10px
-}
-
-.message {
- display: flex;
- gap: 8px;
-}
-
-.message .bubble {
- max-width: 70%;
- padding: 10px 12px;
- border-radius: 12px;
- border: 1px solid var(--border)
-}
-
-.message.user .bubble {
- background: rgba(99, 102, 241, 0.15);
-}
-
-.message.ai .bubble {
- background: rgba(16, 185, 129, 0.15);
-}
-
-.chat-input {
- border-top: 1px solid var(--border);
- padding: 10px;
- display: flex;
- gap: 8px
-}
-
-.chat-input textarea {
- flex: 1;
- background: var(--input);
- border: 1px solid var(--input-border);
- color: var(--text);
- border-radius: 8px;
- padding: 8px;
- min-height: 60px
-}
-
-.chat-input button {
- background: var(--btn);
- border: 1px solid var(--btn-border);
- color: var(--text);
- border-radius: 8px;
- padding: 8px 12px;
- cursor: pointer
-}
-
-.preview {
- background: var(--panel);
- border: 1px solid var(--border);
- padding: 8px;
- border-radius: 6px;
- min-height: 300px
-}
-
-.prompt,
-.selection {
- background: var(--panel);
- border: 1px solid var(--border);
- padding: 12px;
- border-radius: 6px
-}
-
-.prompt textarea {
- width: 100%;
- min-height: 120px;
- background: var(--input);
- color: var(--text);
- border: 1px solid var(--input-border);
- border-radius: 6px;
- padding: 8px;
- resize: vertical;
-}
-
-.prompt .actions {
- margin-top: 8px;
-}
-
-.prompt-output {
- margin-top: 10px;
-}
-
-.prompt-output pre {
- white-space: pre-wrap;
- background: var(--panel);
- border: 1px solid var(--border);
- padding: 8px;
- border-radius: 6px;
-}
-
-/* Collapsible tree styles */
-#tree ul {
- list-style: none;
- margin: 0;
- padding-left: 12px;
-}
-
-#tree li {
- margin: 2px 0;
-}
-
-#tree .dir {
- color: var(--text);
-}
-
-#tree .dir .dir-label .name {
- color: var(--text);
-}
-
-#tree .file a {
- color: var(--text);
- text-decoration: none;
-}
-
-#tree .file a:hover {
- text-decoration: underline;
-}
-
-/* Modal */
-.modal[aria-hidden="true"] {
- display: none
-}
-
-.modal {
- position: fixed;
- inset: 0;
- z-index: 1000;
-}
-
-.modal-backdrop {
- position: absolute;
- inset: 0;
-
- background: rgba(0, 0, 0, 0.5)
-}
-
-.modal .row {
- display: flex;
- gap: 12px
-}
-
-.modal .col {
- flex: 1;
- display: flex;
- flex-direction: column;
- gap: 8px
-}
-
-.modal .list {
- list-style: none;
- margin: 0;
- padding: 0;
- max-height: 40vh;
- overflow: auto
-}
-
-.modal .list li {
- display: flex;
- justify-content: space-between;
- align-items: center;
- gap: 8px;
- padding: 6px 0;
- border-bottom: 1px dashed var(--border)
-}
-
-.modal .list .use {
- background: var(--btn);
- border: 1px solid var(--btn-border);
- color: var(--text);
- border-radius: 6px;
- padding: 4px 8px;
- cursor: pointer
-}
-
-.modal-dialog {
- position: relative;
- margin: 8vh auto;
- max-width: 520px;
- background: var(--panel);
- color: var(--text);
- border: 1px solid var(--border);
- border-radius: 10px;
- padding: 12px;
- z-index: 1
-}
-
-.modal-header {
- display: flex;
- justify-content: space-between;
- align-items: center;
- margin-bottom: 8px
-}
-
-.modal-header .icon {
- background: transparent;
- border: none;
- color: var(--text);
- cursor: pointer;
- font-size: 18px
-}
-
-.modal-body {
- display: flex;
- flex-direction: column;
- gap: 8px
-}
-
-.modal-body input {
- background: var(--input);
- color: var(--text);
- border: 1px solid var(--input-border);
- border-radius: 6px;
- padding: 8px
-}
-
-.modal-footer {
- display: flex;
- justify-content: flex-end;
- gap: 8px;
- margin-top: 12px
-}
-
-.modal-footer button {
- background: var(--btn);
- border: 1px solid var(--btn-border);
- color: var(--text);
- border-radius: 6px;
- padding: 6px 10px;
- cursor: pointer
-}
-
-.error {
- color: #ef4444;
- font-size: 12px;
- min-height: 16px
-}
-
-
-#tree .toggle {
- appearance: none;
- -webkit-appearance: none;
- width: 0;
- height: 0;
- position: absolute;
-}
-
-#tree .dir-label {
- display: inline-flex;
- align-items: center;
- gap: 6px;
- cursor: pointer;
- user-select: none;
-}
-
-#tree .dir .chev {
- display: inline-block;
- transform: rotate(-90deg);
- transition: transform 0.15s ease;
- width: 10px;
- height: 10px;
- border-right: 2px solid #93c5fd;
- border-bottom: 2px solid #93c5fd;
- margin-left: 2px;
-}
-
-#tree .dir .toggle:checked+.dir-label .chev {
- transform: rotate(0deg);
-}
-
-#tree .children {
- display: none;
- margin-left: 16px;
-}
-
-#tree .dir .toggle:checked~.children {
- display: block;
-}
-
-#tree button {
- margin-left: 6px;
- background: var(--btn);
- border: 1px solid var(--btn-border);
- color: var(--text);
- padding: 2px 6px;
- border-radius: 4px;
- cursor: pointer;
- font-size: 12px;
-}
-
-#selected li {
- display: flex;
- justify-content: space-between;
- gap: 8px
-}
\ No newline at end of file
diff --git a/lib/web/heroprompt/static/js/main.js b/lib/web/heroprompt/static/js/main.js
deleted file mode 100644
index 40669cd0..00000000
--- a/lib/web/heroprompt/static/js/main.js
+++ /dev/null
@@ -1,391 +0,0 @@
-console.log('Heroprompt UI loaded');
-
-let currentWs = localStorage.getItem('heroprompt-current-ws') || 'default';
-let selected = [];
-
-const el = (id) => document.getElementById(id);
-
-async function api(url) {
- try { const r = await fetch(url); return await r.json(); }
- catch { return { error: 'request failed' }; }
-}
-async function post(url, data) {
- const form = new FormData();
- Object.entries(data).forEach(([k, v]) => form.append(k, v));
- try { const r = await fetch(url, { method: 'POST', body: form }); return await r.json(); }
- catch { return { error: 'request failed' }; }
-}
-
-// Checkbox-based collapsible tree
-let nodeId = 0;
-
-function renderTree(displayName, fullPath) {
- const c = document.createElement('div');
- c.className = 'tree';
- const ul = document.createElement('ul');
- ul.className = 'tree-root';
- const root = buildDirNode(displayName, fullPath, true);
- ul.appendChild(root);
- c.appendChild(ul);
- return c;
-}
-
-function buildDirNode(name, fullPath, expanded = false) {
- const li = document.createElement('li');
- li.className = 'dir';
- const id = `tn_${nodeId++}`;
-
- const toggle = document.createElement('input');
- toggle.type = 'checkbox';
- toggle.className = 'toggle';
- toggle.id = id;
- if (expanded) toggle.checked = true;
-
- const label = document.createElement('label');
- label.htmlFor = id;
- label.className = 'dir-label';
- const icon = document.createElement('span');
- icon.className = 'chev';
- const text = document.createElement('span');
- text.className = 'name';
- text.textContent = name;
- label.appendChild(icon);
- label.appendChild(text);
-
- const add = document.createElement('button');
- add.textContent = '+';
- add.title = 'Add directory to selection';
- add.onclick = () => addDirToSelection(fullPath);
-
- const children = document.createElement('ul');
- children.className = 'children';
-
- toggle.addEventListener('change', async () => {
- if (toggle.checked) {
- if (!li.dataset.loaded) {
- await loadChildren(fullPath, children);
- li.dataset.loaded = '1';
- }
- }
- });
-
- // Load immediately if expanded by default
- if (expanded) {
- setTimeout(async () => {
- await loadChildren(fullPath, children);
- li.dataset.loaded = '1';
- }, 0);
- }
-
- li.appendChild(toggle);
- li.appendChild(label);
- li.appendChild(add);
- li.appendChild(children);
- return li;
-}
-
-function createFileNode(name, fullPath) {
- const li = document.createElement('li');
- li.className = 'file';
- const a = document.createElement('a');
- a.href = '#';
- a.textContent = name;
- a.onclick = (e) => { e.preventDefault(); };
- const add = document.createElement('button');
- add.textContent = '+';
- add.title = 'Add file to selection';
- add.onclick = () => addFileToSelection(fullPath);
- li.appendChild(a);
- li.appendChild(add);
- return li;
-}
-
-async function loadChildren(parentPath, ul) {
- const r = await api(`/api/heroprompt/directory?name=${currentWs}&path=${encodeURIComponent(parentPath)}`);
- if (r.error) { ul.innerHTML = `${r.error}`; return; }
- ul.innerHTML = '';
- for (const it of r.items) {
- const full = parentPath.endsWith('/') ? parentPath + it.name : parentPath + '/' + it.name;
- if (it.type === 'directory') {
- ul.appendChild(buildDirNode(it.name, full, false));
- } else {
- ul.appendChild(createFileNode(it.name, full));
- }
- }
-}
-
-async function loadDir(p) {
- el('tree').innerHTML = '';
- const display = p.split('/').filter(Boolean).slice(-1)[0] || p;
- el('tree').appendChild(renderTree(display, p));
- updateSelectionList();
-}
-
-function updateSelectionList() {
- el('selCount').textContent = String(selected.length);
- const ul = el('selected');
- ul.innerHTML = '';
- for (const p of selected) {
- const li = document.createElement('li');
- li.textContent = p;
- const btn = document.createElement('button');
- btn.textContent = 'remove';
- btn.onclick = () => { selected = selected.filter(x => x !== p); updateSelectionList(); };
- li.appendChild(btn);
- ul.appendChild(li);
- }
- // naive token estimator ~ 4 chars/token
- const tokens = Math.ceil(selected.join('\n').length / 4);
- el('tokenCount').textContent = String(Math.ceil(tokens));
-}
-
-function addToSelection(p) {
- if (!selected.includes(p)) { selected.push(p); updateSelectionList(); }
-}
-
-
-async function addDirToSelection(p) {
- const r = await fetch(`/api/heroprompt/workspaces/${currentWs}/dirs`, { method: 'POST', body: new URLSearchParams({ path: p }) });
- const j = await r.json().catch(() => ({ error: 'request failed' }));
- if (j && j.ok !== false && !j.error) { if (!selected.includes(p)) selected.push(p); updateSelectionList(); }
-}
-
-async function addFileToSelection(p) {
- if (selected.includes(p)) return;
- const r = await fetch(`/api/heroprompt/workspaces/${currentWs}/files`, { method: 'POST', body: new URLSearchParams({ path: p }) });
- const j = await r.json().catch(() => ({ error: 'request failed' }));
- if (j && j.ok !== false && !j.error) { selected.push(p); updateSelectionList(); }
-}
-
-
-// Theme persistence and toggle
-(function initTheme() {
- const saved = localStorage.getItem('hero-theme');
- const root = document.documentElement;
- if (saved === 'light') root.classList.add('light');
-})();
-
-el('toggleTheme').onclick = () => {
- const root = document.documentElement;
- const isLight = root.classList.toggle('light');
- localStorage.setItem('hero-theme', isLight ? 'light' : 'dark');
-};
-
-// Workspaces list + selector
-async function reloadWorkspaces() {
- const sel = document.getElementById('workspaceSelect');
- if (!sel) return;
- sel.innerHTML = '';
- const names = await api('/api/heroprompt/workspaces').catch(() => []);
- for (const n of names || []) {
- const opt = document.createElement('option');
- opt.value = n; opt.textContent = n;
- sel.appendChild(opt);
- }
- // ensure current ws name exists or select first
- function updateWsInfo(info) { const box = document.getElementById('wsInfo'); if (!box) return; if (!info || info.error) { box.textContent = ''; return; } box.textContent = `${info.name} — ${info.base_path}`; }
-
- if ([...sel.options].some(o => o.value === currentWs)) sel.value = currentWs;
- else if (sel.options.length > 0) sel.value = sel.options[0].value;
-}
-// On initial load: pick current or first workspace and load its base
-(async function initWorkspace() {
- const sel = document.getElementById('workspaceSelect');
- const names = await api('/api/heroprompt/workspaces').catch(() => []);
- if (!names || names.length === 0) return;
- if (!currentWs || !names.includes(currentWs)) { currentWs = names[0]; localStorage.setItem('heroprompt-current-ws', currentWs); }
- if (sel) sel.value = currentWs;
- const info = await api(`/api/heroprompt/workspaces/${currentWs}`);
- const base = info?.base_path || '';
- if (base) await loadDir(base);
-})();
-// Create workspace modal wiring
-const wcShow = () => { el('wcName').value = ''; el('wcPath').value = ''; el('wcError').textContent = ''; showModal('wsCreate'); };
-el('wsCreateBtn')?.addEventListener('click', wcShow);
-el('wcClose')?.addEventListener('click', () => hideModal('wsCreate'));
-el('wcCancel')?.addEventListener('click', () => hideModal('wsCreate'));
-
-el('wcCreate')?.addEventListener('click', async () => {
- const name = el('wcName').value.trim();
- const path = el('wcPath').value.trim();
- if (!path) { el('wcError').textContent = 'Path is required.'; return; }
- const formData = { base_path: path };
- if (name) formData.name = name;
- const resp = await post('/api/heroprompt/workspaces', formData);
- if (resp.error) { el('wcError').textContent = resp.error; return; }
- currentWs = resp.name || currentWs;
- localStorage.setItem('heroprompt-current-ws', currentWs);
- await reloadWorkspaces();
- const info = await api(`/api/heroprompt/workspaces/${currentWs}`);
- const base = info?.base_path || '';
- if (base) await loadDir(base);
- hideModal('wsCreate');
-});
-// Workspace details modal
-el('wsDetailsBtn')?.addEventListener('click', async () => {
- const info = await api(`/api/heroprompt/workspaces/${currentWs}`);
- if (info && !info.error) { el('wdName').value = info.name || currentWs; el('wdPath').value = info.base_path || ''; el('wdError').textContent = ''; showModal('wsDetails'); }
-});
-
-el('wdClose')?.addEventListener('click', () => hideModal('wsDetails'));
-el('wdCancel')?.addEventListener('click', () => hideModal('wsDetails'));
-
-el('wdSave')?.addEventListener('click', async () => {
- const newName = el('wdName').value.trim();
- const newPath = el('wdPath').value.trim();
- // update via create semantics if name changed, or add an update endpoint later
- const form = new FormData(); if (newName) form.append('name', newName); if (newPath) form.append('base_path', newPath);
- const resp = await fetch('/api/heroprompt/workspaces', { method: 'POST', body: form });
- const j = await resp.json().catch(() => ({ error: 'request failed' }));
- if (j.error) { el('wdError').textContent = j.error; return; }
- currentWs = j.name || newName || currentWs; localStorage.setItem('heroprompt-current-ws', currentWs);
- await reloadWorkspaces();
- const info = await api(`/api/heroprompt/workspaces/${currentWs}`);
- const base = info?.base_path || '';
- if (base) await loadDir(base);
- hideModal('wsDetails');
-});
-
-el('wdDelete')?.addEventListener('click', async () => {
- // simple delete through factory delete via dedicated endpoint would be ideal; for now we can implement a delete endpoint later
- const ok = confirm('Delete this workspace?'); if (!ok) return;
- const r = await fetch(`/api/heroprompt/workspaces/${currentWs}`, { method: 'DELETE' });
- const j = await r.json().catch(() => ({}));
- // ignore errors for now
- await reloadWorkspaces();
- const sel = document.getElementById('workspaceSelect');
- currentWs = sel?.value || '';
- localStorage.setItem('heroprompt-current-ws', currentWs);
- if (currentWs) { const info = await api(`/api/heroprompt/workspaces/${currentWs}`); const base = info?.base_path || ''; if (base) await loadDir(base); }
- hideModal('wsDetails');
-});
-
-
-
-
-if (document.getElementById('workspaceSelect')) {
- // Copy Prompt: generate on server using workspace.prompt and copy to clipboard
- el('copyPrompt')?.addEventListener('click', async () => {
- const text = el('promptText')?.value || '';
- try {
- const r = await fetch(`/api/heroprompt/workspaces/${currentWs}/prompt`, { method: 'POST', body: new URLSearchParams({ text }) });
- const out = await r.text();
- await navigator.clipboard.writeText(out);
- } catch (e) {
- console.warn('copy prompt failed', e);
- }
- });
-
- reloadWorkspaces();
- document.getElementById('workspaceSelect').addEventListener('change', async (e) => {
- currentWs = e.target.value;
- localStorage.setItem('heroprompt-current-ws', currentWs);
- const info = await api(`/api/heroprompt/workspaces/${currentWs}`);
- const base = info?.base_path || '';
- if (base) await loadDir(base);
- });
-}
-
-document.getElementById('refreshWs')?.addEventListener('click', async () => {
- const info = await api(`/api/heroprompt/workspaces/${currentWs}`);
- const base = info?.base_path || '';
- if (base) await loadDir(base);
-});
-document.getElementById('openWsManage')?.addEventListener('click', async () => {
- // populate manage list and open
- const list = el('wmList'); const err = el('wmError'); if (!list) return;
- err.textContent = ''; list.innerHTML = '';
- const names = await api('/api/heroprompt/workspaces').catch(() => []);
- for (const n of names || []) { const li = document.createElement('li'); const s = document.createElement('span'); s.textContent = n; const b = document.createElement('button'); b.className = 'use'; b.textContent = 'Use'; b.onclick = async () => { currentWs = n; await reloadWorkspaces(); const info = await api(`/api/heroprompt/workspaces/${currentWs}`); const base = info?.base_path || ''; if (base) await loadDir(base); hideModal('wsManage'); }; li.appendChild(s); li.appendChild(b); list.appendChild(li); }
- showModal('wsManage');
-});
-
-// legacy setWs kept for backward compat - binds currentWs
-el('setWs')?.addEventListener('click', async () => {
- const base = el('basePath')?.value?.trim();
- if (!base) { alert('Enter base path'); return; }
- const r = await post('/api/heroprompt/workspaces', { name: currentWs, base_path: base });
- if (r.error) { alert(r.error); return; }
- await loadDir(base);
-});
-
-el('doSearch').onclick = async () => {
- const q = el('search').value.trim();
- if (!q) return;
- const r = await api(`/api/heroprompt/search?name=${currentWs}&q=${encodeURIComponent(q)}`);
- if (r.error) { alert(r.error); return; }
- const tree = el('tree');
- tree.innerHTML = 'Search results:
';
- const ul = document.createElement('ul');
- for (const it of r) {
- const li = document.createElement('li');
- li.className = it.type;
- const a = document.createElement('a');
- a.href = '#'; a.textContent = it.path;
- a.onclick = async (e) => {
- e.preventDefault();
- if (it.type === 'file') {
- const rf = await api(`/api/heroprompt/file?name=${currentWs}&path=${encodeURIComponent(it.path)}`);
- if (!rf.error) el('preview').textContent = rf.content;
- } else {
- await loadDir(it.path);
- }
- };
- const add = document.createElement('button');
- add.textContent = '+';
- add.title = 'Add to selection';
- add.onclick = () => addToSelection(it.path);
- li.appendChild(a);
- li.appendChild(add);
- ul.appendChild(li);
- }
- tree.appendChild(ul);
-};
-
-// Tabs
-function switchTab(id) {
- for (const t of document.querySelectorAll('.tab')) t.classList.remove('active');
- for (const p of document.querySelectorAll('.tab-pane')) p.classList.remove('active');
- const btn = document.querySelector(`.tab[data-tab="${id}"]`);
- const pane = document.getElementById(`tab-${id}`);
- if (btn && pane) {
- btn.classList.add('active');
- pane.classList.add('active');
- }
-}
-
-for (const btn of document.querySelectorAll('.tab')) {
- btn.addEventListener('click', () => switchTab(btn.dataset.tab));
-}
-
-// Chat (client-side mock for now)
-el('sendChat').onclick = () => {
- const input = el('chatInput');
- const text = input.value.trim();
- if (!text) return;
- addChatMessage('user', text);
- input.value = '';
- // Mock AI response
- setTimeout(() => addChatMessage('ai', 'This is a placeholder AI response.'), 500);
-};
-
-function addChatMessage(role, text) {
- const msg = document.createElement('div');
- msg.className = `message ${role}`;
- const bubble = document.createElement('div');
- bubble.className = 'bubble';
- bubble.textContent = text;
- msg.appendChild(bubble);
- el('chatMessages').appendChild(msg);
- el('chatMessages').scrollTop = el('chatMessages').scrollHeight;
-}
-
-// Modal helpers
-function showModal(id) { const m = el(id); if (!m) return; m.setAttribute('aria-hidden', 'false'); }
-function hideModal(id) { const m = el(id); if (!m) return; m.setAttribute('aria-hidden', 'true'); el('wsError').textContent = ''; }
-
-
-
-
-
-
diff --git a/lib/web/heroprompt/templates/index.html b/lib/web/heroprompt/templates/index.html
deleted file mode 100644
index 8d5033de..00000000
--- a/lib/web/heroprompt/templates/index.html
+++ /dev/null
@@ -1,115 +0,0 @@
-
-
-
-
-
-
- {{.title}} - Heroprompt
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
Prompt
-
-
-
-
-
Selected Files (0) — Tokens: 0
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
If blank we will generate one for you
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/lib/web/heroprompt/utils.v b/lib/web/heroprompt/utils.v
deleted file mode 100644
index e44fd628..00000000
--- a/lib/web/heroprompt/utils.v
+++ /dev/null
@@ -1,28 +0,0 @@
-module heroprompt
-
-import strings
-
-// Very small template renderer using {{.var}} replacement
-pub fn render_template(tpl string, data map[string]string) string {
- mut out := tpl
- for k, v in data {
- out = out.replace('{{.' + k + '}}', v)
- }
- return out
-}
-
-// Minimal HTML escape
-pub fn html_escape(s string) string {
- mut b := strings.new_builder(s.len)
- for ch in s {
- match ch {
- `&` { b.write_string('&') }
- `<` { b.write_string('<') }
- `>` { b.write_string('>') }
- `"` { b.write_string('"') }
- `'` { b.write_string(''') }
- else { b.write_string(ch.str()) }
- }
- }
- return b.str()
-}
diff --git a/lib/web/ui/README.md b/lib/web/ui/README.md
new file mode 100644
index 00000000..45045fb3
--- /dev/null
+++ b/lib/web/ui/README.md
@@ -0,0 +1,25 @@
+# HeroPrompt Web UI
+
+A clean web interface for creating and managing AI prompts with file and workspace management.
+
+## Overview
+
+HeroPrompt provides a VS Code–style interface to browse files, organize workspaces, and generate AI prompts. It combines a modern UI with intelligent file handling and flexible prompt generation.
+
+## Features
+
+* 🎨 **Modern UI**: Light/dark themes, responsive layout, smooth animations
+* 📁 **Workspaces**: Create, update, delete, and persist workspaces
+* 🗂️ **File Explorer**: Tree view, filtering, expand/collapse, multi-select
+* 🔍 **Preview**: Card-based file previews with metadata and syntax highlighting
+* 🚀 **Prompt Generation**: Build structured AI prompts from selected files
+
+## Usage
+
+```bash
+./cli/compile.vsh # Compile
+./hero web # Run server
+```
+
+* Create a workspace → select files → preview → generate prompts
+* Manage workspaces (create, update, delete) via UI
diff --git a/lib/web/ui/chat_endpoints.v b/lib/web/ui/chat_endpoints.v
new file mode 100644
index 00000000..41670468
--- /dev/null
+++ b/lib/web/ui/chat_endpoints.v
@@ -0,0 +1,20 @@
+module ui
+
+import os
+
+pub fn render_chat_alt(app &App) !string {
+ tpl := os.join_path(os.dir(@FILE), 'templates', 'chat.html')
+ content := os.read_file(tpl)!
+ menu_content := menu_html(app.menu, 0, 'm')
+ mut result := content
+ result = result.replace('{{.title}}', app.title)
+ result = result.replace('{{.menu_html}}', menu_content)
+ result = result.replace('{{.css_colors_url}}', '/static/css/colors.css')
+ result = result.replace('{{.css_main_url}}', '/static/css/main.css')
+ result = result.replace('{{.css_chat_url}}', '/static/css/chat.css')
+ result = result.replace('{{.js_theme_url}}', '/static/js/theme.js')
+ result = result.replace('{{.js_chat_url}}', '/static/js/chat.js')
+ // version banner
+ result = result.replace('
-//
-//
HeroScript Editor
-//
HeroScript editor template not found. Please check the template files.
-//
Back to Admin
-//
-// ')
+ return result
+}
diff --git a/lib/web/ui/chat_utils.v b/lib/web/ui/chat_utils.v
new file mode 100644
index 00000000..79ca78a8
--- /dev/null
+++ b/lib/web/ui/chat_utils.v
@@ -0,0 +1,3 @@
+module ui
+
+// Placeholder for chat-specific utilities
diff --git a/lib/web/ui/endpoints.v b/lib/web/ui/endpoints.v
deleted file mode 100644
index 91d694c8..00000000
--- a/lib/web/ui/endpoints.v
+++ /dev/null
@@ -1,244 +0,0 @@
-module ui
-
-// import veb
-// import freeflowuniverse.herolib.develop.heroprompt
-// import os
-// import json
-
-// // Directory browsing and file read endpoints for Heroprompt.js compatibility
-// struct DirItem {
-// name string
-// typ string @[json: 'type']
-// }
-
-// struct DirResp {
-// path string
-// mut:
-// items []DirItem
-// }
-
-// @['/api/heroprompt/directory'; get]
-// pub fn (app &App) api_heroprompt_directory(mut ctx Context) veb.Result {
-// // Optional workspace name, defaults to 'default'
-// wsname := ctx.query['name'] or { 'default' }
-// path_q := ctx.query['path'] or { '' }
-// if path_q.len == 0 {
-// return ctx.text('{"error":"path required"}')
-// }
-// // Try to resolve against workspace base_path if available, but do not require it
-// mut base := ''
-// if wsp := heroprompt.get(name: wsname, create: false) {
-// base = wsp.base_path
-// }
-// // Resolve path: if absolute, use as-is; else join with base
-// mut dir_path := path_q
-// if !os.is_abs_path(dir_path) && base.len > 0 {
-// dir_path = os.join_path(base, dir_path)
-// }
-// // List entries
-// entries := os.ls(dir_path) or { return ctx.text('{"error":"cannot list directory"}') }
-// mut items := []map[string]string{}
-// for e in entries {
-// full := os.join_path(dir_path, e)
-// if os.is_dir(full) {
-// items << {
-// 'name': e
-// 'type': 'directory'
-// }
-// } else if os.is_file(full) {
-// items << {
-// 'name': e
-// 'type': 'file'
-// }
-// }
-// }
-// ctx.set_content_type('application/json')
-// // Encode strongly typed JSON response
-// mut resp := DirResp{
-// path: dir_path
-// }
-// for it in items {
-// resp.items << DirItem{
-// name: it['name'] or { '' }
-// typ: it['type'] or { '' }
-// }
-// }
-// return ctx.text(json.encode(resp))
-// }
-
-// @['/api/heroprompt/file'; get]
-// pub fn (app &App) api_heroprompt_file(mut ctx Context) veb.Result {
-// wsname := ctx.query['name'] or { 'default' }
-// path_q := ctx.query['path'] or { '' }
-// if path_q.len == 0 {
-// return ctx.text('{"error":"path required"}')
-// }
-// // Try to resolve against workspace base_path if available, but do not require it
-// mut base := ''
-// if wsp := heroprompt.get(name: wsname, create: false) {
-// base = wsp.base_path
-// }
-// mut file_path := path_q
-// if !os.is_abs_path(file_path) && base.len > 0 {
-// file_path = os.join_path(base, file_path)
-// }
-// content := os.read_file(file_path) or { return ctx.text('{"error":"failed to read"}') }
-// lang := detect_lang(file_path)
-// ctx.set_content_type('application/json')
-// return ctx.text(json.encode({
-// 'language': lang
-// 'content': content
-// }))
-// }
-
-// fn detect_lang(path string) string {
-// ext := os.file_ext(path).trim_left('.')
-// return match ext.to_lower() {
-// 'v' { 'v' }
-// 'js' { 'javascript' }
-// 'ts' { 'typescript' }
-// 'py' { 'python' }
-// 'rs' { 'rust' }
-// 'go' { 'go' }
-// 'java' { 'java' }
-// 'c', 'h' { 'c' }
-// 'cpp', 'hpp', 'cc', 'hh' { 'cpp' }
-// 'sh', 'bash' { 'bash' }
-// 'json' { 'json' }
-// 'yaml', 'yml' { 'yaml' }
-// 'html', 'htm' { 'html' }
-// 'css' { 'css' }
-// 'md' { 'markdown' }
-// else { 'text' }
-// }
-// }
-
-// // Heroprompt API: list workspaces
-// @['/api/heroprompt/workspaces'; get]
-// pub fn (app &App) api_heroprompt_list(mut ctx Context) veb.Result {
-// mut names := []string{}
-// ws := heroprompt.list(fromdb: true) or { []&heroprompt.Workspace{} }
-// for w in ws {
-// names << w.name
-// }
-// ctx.set_content_type('application/json')
-// return ctx.text(json.encode(names))
-// }
-
-// // Heroprompt API: create/get workspace
-// @['/api/heroprompt/workspaces'; post]
-// pub fn (app &App) api_heroprompt_create(mut ctx Context) veb.Result {
-// name := ctx.form['name'] or { '' }
-// base_path := ctx.form['base_path'] or { '' }
-
-// if base_path.len == 0 {
-// return ctx.text('{"error":"base_path required"}')
-// }
-
-// mut wsp := heroprompt.get(name: name, create: true, path: base_path) or {
-// return ctx.text('{"error":"create failed"}')
-// }
-
-// ctx.set_content_type('application/json')
-// return ctx.text(json.encode({
-// 'name': name
-// 'base_path': base_path
-// }))
-// }
-
-// // Heroprompt API: add directory to workspace
-// @['/api/heroprompt/workspaces/:name/dirs'; post]
-// pub fn (app &App) api_heroprompt_add_dir(mut ctx Context, name string) veb.Result {
-// path := ctx.form['path'] or { '' }
-// if path.len == 0 {
-// return ctx.text('{"error":"path required"}')
-// }
-// mut wsp := heroprompt.get(name: name, create: true) or {
-// return ctx.text('{"error":"workspace not found"}')
-// }
-// wsp.add_dir(path: path) or { return ctx.text('{"error":"' + err.msg() + '"}') }
-// ctx.set_content_type('application/json')
-// return ctx.text('{"ok":true}')
-// }
-
-// // Heroprompt API: add file to workspace
-// @['/api/heroprompt/workspaces/:name/files'; post]
-// pub fn (app &App) api_heroprompt_add_file(mut ctx Context, name string) veb.Result {
-// path := ctx.form['path'] or { '' }
-// if path.len == 0 {
-// return ctx.text('{"error":"path required"}')
-// }
-// mut wsp := heroprompt.get(name: name, create: true) or {
-// return ctx.text('{"error":"workspace not found"}')
-// }
-// wsp.add_file(path: path) or { return ctx.text('{"error":"' + err.msg() + '"}') }
-// ctx.set_content_type('application/json')
-// return ctx.text('{"ok":true}')
-// }
-
-// // Heroprompt API: generate prompt
-// @['/api/heroprompt/workspaces/:name/prompt'; post]
-// pub fn (app &App) api_heroprompt_prompt(mut ctx Context, name string) veb.Result {
-// text := ctx.form['text'] or { '' }
-// mut wsp := heroprompt.get(name: name, create: false) or {
-// return ctx.text('{"error":"workspace not found"}')
-// }
-// prompt := wsp.prompt(text: text)
-// ctx.set_content_type('text/plain')
-// return ctx.text(prompt)
-// }
-
-// // Heroprompt API: get workspace details
-// @['/api/heroprompt/workspaces/:name'; get]
-// pub fn (app &App) api_heroprompt_get(mut ctx Context, name string) veb.Result {
-// wsp := heroprompt.get(name: name, create: false) or {
-// return ctx.text('{"error":"workspace not found"}')
-// }
-// mut children := []map[string]string{}
-// for ch in wsp.children {
-// children << {
-// 'name': ch.name
-// 'path': ch.path.path
-// 'type': if ch.path.cat == .dir { 'directory' } else { 'file' }
-// }
-// }
-// ctx.set_content_type('application/json')
-// return ctx.text(json.encode({
-// 'name': wsp.name
-// 'base_path': wsp.base_path
-// 'children': json.encode(children)
-// }))
-// }
-
-// // Heroprompt API: delete workspace
-// @['/api/heroprompt/workspaces/:name'; delete]
-// pub fn (app &App) api_heroprompt_delete(mut ctx Context, name string) veb.Result {
-// wsp := heroprompt.get(name: name, create: false) or {
-// return ctx.text('{"error":"workspace not found"}')
-// }
-// wsp.delete_workspace() or { return ctx.text('{"error":"delete failed"}') }
-// ctx.set_content_type('application/json')
-// return ctx.text('{"ok":true}')
-// }
-
-// // Heroprompt API: remove directory
-// @['/api/heroprompt/workspaces/:name/dirs/remove'; post]
-// pub fn (app &App) api_heroprompt_remove_dir(mut ctx Context, name string) veb.Result {
-// path := ctx.form['path'] or { '' }
-// mut wsp := heroprompt.get(name: name, create: false) or {
-// return ctx.text('{"error":"workspace not found"}')
-// }
-// wsp.remove_dir(path: path, name: '') or { return ctx.text('{"error":"' + err.msg() + '"}') }
-// return ctx.text('{"ok":true}')
-// }
-
-// // Heroprompt API: remove file
-// @['/api/heroprompt/workspaces/:name/files/remove'; post]
-// pub fn (app &App) api_heroprompt_remove_file(mut ctx Context, name string) veb.Result {
-// path := ctx.form['path'] or { '' }
-// mut wsp := heroprompt.get(name: name, create: false) or {
-// return ctx.text('{"error":"workspace not found"}')
-// }
-// wsp.remove_file(path: path, name: '') or { return ctx.text('{"error":"' + err.msg() + '"}') }
-// return ctx.text('{"ok":true}')
-// }
diff --git a/lib/web/ui/factory.v b/lib/web/ui/factory.v
deleted file mode 100644
index 384b1055..00000000
--- a/lib/web/ui/factory.v
+++ /dev/null
@@ -1,471 +0,0 @@
-module ui
-
-// import veb
-// import os
-
-// // Public Context type for veb
-// pub struct Context {
-// veb.Context
-// }
-
-// // Simple tree menu structure
-// pub struct MenuItem {
-// pub:
-// title string
-// href string
-// children []MenuItem
-// }
-
-// // Factory args
-// @[params]
-// pub struct WebArgs {
-// pub mut:
-// name string = 'default'
-// host string = 'localhost'
-// port int = 8080
-// title string = 'Admin'
-// menu []MenuItem
-// open bool
-// }
-
-// // The App holds server state and config
-// pub struct App {
-// veb.StaticHandler
-// pub mut:
-// title string = 'default'
-// menu []MenuItem
-// port int = 7711
-// }
-
-// // Start the webserver (blocking)
-// pub fn start(args WebArgs) ! {
-// mut app := App{
-// title: args.title
-// menu: args.menu
-// port: args.port
-// }
-// veb.run[App, Context](mut app, app.port)
-// }
-
-// // Routes
-
-// // Redirect root to /admin
-// @['/'; get]
-// pub fn (app &App) root(mut ctx Context) veb.Result {
-// return ctx.redirect('/admin')
-// }
-
-// // Admin home page
-// @['/admin'; get]
-// pub fn (app &App) admin_index(mut ctx Context) veb.Result {
-// return ctx.html(app.render_admin('/', 'Welcome'))
-// }
-
-// // HeroScript editor page
-// @['/admin/heroscript'; get]
-// pub fn (app &App) admin_heroscript(mut ctx Context) veb.Result {
-// return ctx.html(app.render_heroscript())
-// }
-
-// // Chat page
-// @['/admin/chat'; get]
-// pub fn (app &App) admin_chat(mut ctx Context) veb.Result {
-// return ctx.html(app.render_chat())
-// }
-
-// // Static CSS files
-// @['/static/css/colors.css'; get]
-// pub fn (app &App) serve_colors_css(mut ctx Context) veb.Result {
-// css_path := os.join_path(os.dir(@FILE), 'templates', 'css', 'colors.css')
-// css_content := os.read_file(css_path) or { return ctx.text('/* CSS file not found */') }
-// ctx.set_content_type('text/css')
-// return ctx.text(css_content)
-// }
-
-// @['/static/css/main.css'; get]
-// pub fn (app &App) serve_main_css(mut ctx Context) veb.Result {
-// css_path := os.join_path(os.dir(@FILE), 'templates', 'css', 'main.css')
-// css_content := os.read_file(css_path) or { return ctx.text('/* CSS file not found */') }
-// ctx.set_content_type('text/css')
-// return ctx.text(css_content)
-// }
-
-// // Static JS files
-// @['/static/js/theme.js'; get]
-// pub fn (app &App) serve_theme_js(mut ctx Context) veb.Result {
-// js_path := os.join_path(os.dir(@FILE), 'templates', 'js', 'theme.js')
-// js_content := os.read_file(js_path) or { return ctx.text('/* JS file not found */') }
-// ctx.set_content_type('application/javascript')
-// return ctx.text(js_content)
-// }
-
-// @['/static/js/heroscript.js'; get]
-// pub fn (app &App) serve_heroscript_js(mut ctx Context) veb.Result {
-// js_path := os.join_path(os.dir(@FILE), 'templates', 'js', 'heroscript.js')
-// js_content := os.read_file(js_path) or { return ctx.text('/* JS file not found */') }
-// ctx.set_content_type('application/javascript')
-// return ctx.text(js_content)
-// }
-
-// @['/static/js/chat.js'; get]
-// pub fn (app &App) serve_chat_js(mut ctx Context) veb.Result {
-// js_path := os.join_path(os.dir(@FILE), 'templates', 'js', 'chat.js')
-// js_content := os.read_file(js_path) or { return ctx.text('/* JS file not found */') }
-// ctx.set_content_type('application/javascript')
-// return ctx.text(js_content)
-// }
-
-// @['/static/css/heroscript.css'; get]
-// pub fn (app &App) serve_heroscript_css(mut ctx Context) veb.Result {
-// css_path := os.join_path(os.dir(@FILE), 'templates', 'css', 'heroscript.css')
-// css_content := os.read_file(css_path) or { return ctx.text('/* CSS file not found */') }
-// ctx.set_content_type('text/css')
-// return ctx.text(css_content)
-// }
-
-// @['/static/css/chat.css'; get]
-// pub fn (app &App) serve_chat_css(mut ctx Context) veb.Result {
-// css_path := os.join_path(os.dir(@FILE), 'templates', 'css', 'chat.css')
-// css_content := os.read_file(css_path) or { return ctx.text('/* CSS file not found */') }
-// ctx.set_content_type('text/css')
-// return ctx.text(css_content)
-// }
-
-// // Catch-all content under /admin/*
-// @['/admin/:path...'; get]
-// pub fn (app &App) admin_section(mut ctx Context, path string) veb.Result {
-// // Render current path in the main content
-// return ctx.html(app.render_admin(path, 'Content'))
-// }
-
-// // View rendering using external template
-
-// fn (app &App) render_admin(path string, heading string) string {
-// // Get the template file path relative to the module
-// template_path := os.join_path(os.dir(@FILE), 'templates', 'admin_layout.html')
-
-// // Read the template file
-// template_content := os.read_file(template_path) or {
-// // Fallback to inline template if file not found
-// return app.render_admin_fallback(path, heading)
-// }
-
-// // Generate menu HTML
-// menu_content := menu_html(app.menu, 0, 'm')
-
-// // Simple template variable replacement
-// mut result := template_content
-// result = result.replace('{{.title}}', app.title)
-// result = result.replace('{{.heading}}', heading)
-// result = result.replace('{{.path}}', path)
-// result = result.replace('{{.menu_html}}', menu_content)
-// result = result.replace('{{.css_colors_url}}', '/static/css/colors.css')
-// result = result.replace('{{.css_main_url}}', '/static/css/main.css')
-// result = result.replace('{{.js_theme_url}}', '/static/js/theme.js')
-
-// return result
-// }
-
-// // HeroScript editor rendering using external template
-// fn (app &App) render_heroscript() string {
-// // Get the template file path relative to the module
-// template_path := os.join_path(os.dir(@FILE), 'templates', 'heroscript_editor.html')
-
-// // Read the template file
-// template_content := os.read_file(template_path) or {
-// // Fallback to basic template if file not found
-// return app.render_heroscript_fallback()
-// }
-
-// // Generate menu HTML
-// menu_content := menu_html(app.menu, 0, 'm')
-
-// // Simple template variable replacement
-// mut result := template_content
-// result = result.replace('{{.title}}', app.title)
-// result = result.replace('{{.menu_html}}', menu_content)
-// result = result.replace('{{.css_colors_url}}', '/static/css/colors.css')
-// result = result.replace('{{.css_main_url}}', '/static/css/main.css')
-// result = result.replace('{{.css_heroscript_url}}', '/static/css/heroscript.css')
-// result = result.replace('{{.js_theme_url}}', '/static/js/theme.js')
-// result = result.replace('{{.js_heroscript_url}}', '/static/js/heroscript.js')
-
-// return result
-// }
-
-// // Chat rendering using external template
-// fn (app &App) render_chat() string {
-// // Get the template file path relative to the module
-// template_path := os.join_path(os.dir(@FILE), 'templates', 'chat.html')
-
-// // Read the template file
-// template_content := os.read_file(template_path) or {
-// // Fallback to basic template if file not found
-// return app.render_chat_fallback()
-// }
-
-// // Generate menu HTML
-// menu_content := menu_html(app.menu, 0, 'm')
-
-// // Simple template variable replacement
-// mut result := template_content
-// result = result.replace('{{.title}}', app.title)
-// result = result.replace('{{.menu_html}}', menu_content)
-// result = result.replace('{{.css_colors_url}}', '/static/css/colors.css')
-// result = result.replace('{{.css_main_url}}', '/static/css/main.css')
-// result = result.replace('{{.css_chat_url}}', '/static/css/chat.css')
-// result = result.replace('{{.js_theme_url}}', '/static/js/theme.js')
-// result = result.replace('{{.js_chat_url}}', '/static/js/chat.js')
-
-// return result
-// }
-
-// // Fallback HeroScript rendering method
-// fn (app &App) render_heroscript_fallback() string {
-// return '
-//
-//
-//