Compare commits

..

67 Commits

Author SHA1 Message Date
Mahmoud-Emad
483b6e3de3 bump version to 1.0.26 2025-06-17 09:51:38 +03:00
f769c34466 Merge branch 'development_griddriver' into development
* development_griddriver:
  Update griddriver to use prebuilt binary
2025-06-17 08:02:22 +02:00
Mahmoud-Emad
c0242a0729 Merge branch 'development' of https://github.com/freeflowuniverse/herolib into development 2025-06-16 14:04:21 +03:00
Mahmoud-Emad
df452ce258 feat: Add mermaid diagrams support to documentation
- Added support for rendering mermaid diagrams in documentation.
- Updated Docusaurus configuration to include mermaid theme and enable
  mermaid rendering in markdown files.
- Updated package.json dependencies to use compatible versions.
2025-06-16 14:04:10 +03:00
7de290ae55 Merge branch 'dify_installer' into development
* dify_installer:
  dify installer
  dify installer
  adding dify installer
  feat: Improve Dify installer
  adding dify installer
  dify installer
  adding dify installer
  feat: Improve Dify installer
  adding dify installer
2025-06-16 10:21:39 +02:00
fe161c5bea Merge branch 'development_fix_zinit' into development
* development_fix_zinit:
  feat: Fix type mismatch error in rpc.discover response handling
2025-06-16 10:21:26 +02:00
Mahmoud-Emad
fca7c7364a docs: Remove redundant line in README
- Removed a trailing empty line in the README file.
2025-06-16 11:09:16 +03:00
Omdanii
ef705d1be0 Merge pull request #91 from freeflowuniverse/development_mecelium_rpc_client
feat: Add Mycelium JSON-RPC client
2025-06-16 10:51:31 +03:00
Omdanii
3154733be1 Merge pull request #92 from freeflowuniverse/development_zinit_rpc_client
feat: Add Zinit JSON-RPC client
2025-06-16 10:51:13 +03:00
b285e85eb5 Merge branch 'development' of github.com:freeflowuniverse/herolib into development
* 'development' of github.com:freeflowuniverse/herolib:
  feat: Improve ZinitClient and JSON-RPC client
2025-06-15 16:30:46 +02:00
89b7f0d465 ... 2025-06-15 16:30:40 +02:00
Mahmoud-Emad
256d4e9bca feat: Add Zinit RPC client
- Adds a new V client for interacting with the Zinit JSON-RPC API.
- Includes comprehensive example demonstrating all API methods.
- Provides type-safe structs and error handling.
- Implements all 18 methods of the Zinit JSON-RPC API.
2025-06-03 11:11:46 +03:00
Mahmoud-Emad
54f4e83627 Merge branch 'development' into development_mecelium_rpc_client 2025-06-02 17:05:15 +03:00
Mahmoud-Emad
f7a770989b feat: Improve ZinitClient and JSON-RPC client
- Enhanced error handling and response parsing in `ZinitClient`: The
  `discover` function now provides more robust error handling and
  response parsing, improving reliability.
- Improved code style and formatting: Minor formatting changes for
  better readability and maintainability.  The `ServiceConfig` and
  `ServiceConfigResponse` structs have been slightly restructured.
- Updated JSON-RPC client structure: The `Client` struct is now
  publicly mutable (`pub mut`), simplifying its use.  Removed
  unnecessary blank lines for improved code clarity.
2025-06-02 17:04:25 +03:00
Mahmoud-Emad
c5759ea30e feat: Add Mycelium JSON-RPC client
- Adds a new V language client for interacting with the Mycelium
  JSON-RPC admin API.
- Includes comprehensive example code demonstrating all API features.
- Implements all methods defined in the Mycelium JSON-RPC spec.
- Provides type-safe API with robust error handling.
- Uses HTTP transport for communication with the Mycelium node.
2025-06-02 16:48:59 +03:00
Mahmoud-Emad
aef9c84eb5 feat: Fix type mismatch error in rpc.discover response handling
- Correctly handle the complex JSON response of the `rpc.discover`
  method by using `map[string]string` instead of `string`.  This
  addresses a type mismatch error that prevented proper parsing of
  the API specification.
- Improve error handling and provide more informative output to the
  user during the API discovery process.
- Add detailed analysis and recommendations for handling complex JSON
  responses in similar scenarios.
2025-06-01 15:09:57 +03:00
d0baac83a9 ... 2025-05-31 16:02:52 +03:00
b6a2671665 ... 2025-05-31 12:45:05 +03:00
a96ae1252c ... 2025-05-31 11:17:56 +03:00
ac4db0f789 rpc example 2025-05-31 11:05:59 +03:00
37f9ab78ec format, implement unixsocket rpc 2025-05-31 08:26:03 +03:00
timurgordon
9b3ac150bd Merge branch 'development' of https://github.com/freeflowuniverse/herolib into development 2025-05-29 23:10:17 +03:00
timurgordon
dd577d51b9 match template and models for docusaurus 2025-05-29 23:10:13 +03:00
92f9714229 ... 2025-05-29 20:22:52 +04:00
timurgordon
632a1c11c2 fix docusaurus to use siteconfig in heroscript 2025-05-29 17:31:51 +03:00
Peternashaat
63d41352bc adding dify installer 2025-05-29 13:23:41 +00:00
Peternashaat
da8eef3711 dify installer 2025-05-29 13:06:36 +00:00
Peternashaat
f0a4732206 dify installer 2025-05-29 13:09:46 +03:00
Peternashaat
1f053edefc adding dify installer 2025-05-29 13:09:46 +03:00
Mahmoud-Emad
f93db1d23c feat: Improve Dify installer
- Update installer configuration to be more robust and flexible.
- Remove unnecessary installation steps in the installer script.
- Improve the installer's ability to check if Dify is running.
- Refactor Dify installer actions for better code organization.
- Add build functionality to Dify installer.
2025-05-29 13:09:46 +03:00
Peternashaat
105611bbfb adding dify installer 2025-05-29 13:09:46 +03:00
Peternashaat
4977c6de30 dify installer 2025-05-28 07:07:53 +00:00
Peternashaat
eb956bca3d adding dify installer 2025-05-27 11:40:50 +00:00
Mahmoud-Emad
5e511367c3 feat: Improve Dify installer
- Update installer configuration to be more robust and flexible.
- Remove unnecessary installation steps in the installer script.
- Improve the installer's ability to check if Dify is running.
- Refactor Dify installer actions for better code organization.
- Add build functionality to Dify installer.
2025-05-27 12:33:25 +03:00
Peternashaat
484bfe393e adding dify installer 2025-05-27 09:05:08 +00:00
a1404584d6 ... 2025-05-25 06:47:17 +04:00
3ef1698c2c ... 2025-05-25 06:44:51 +04:00
a7fb704627 ... 2025-05-25 06:30:48 +04:00
91ba6001cb ... 2025-05-22 20:12:06 +04:00
345a79d8ff ... 2025-05-21 09:05:30 +04:00
15d886e5e9 ... 2025-05-21 09:00:30 +04:00
d6224d1e60 ... 2025-05-21 08:56:34 +04:00
83fb647ac3 ... 2025-05-21 08:37:02 +04:00
b410544ee1 ... 2025-05-21 08:30:30 +04:00
2d5d1befae ... 2025-05-20 09:48:56 +04:00
fd8b8c8f42 revert 2025-05-20 07:00:00 +04:00
8ffb8c8caf ... 2025-05-20 06:49:39 +04:00
b8b339b85c ... 2025-05-20 06:47:36 +04:00
0789a38ea9 Merge branch 'development' of github.com:freeflowuniverse/herolib into development
# Conflicts:
#	lib/web/docusaurus/dsite.v
2025-05-20 06:40:48 +04:00
995d3c3f6d ... 2025-05-20 06:39:40 +04:00
timurgordon
822b179ef4 docusaurus fixes 2025-05-19 11:19:19 +03:00
4691971bd0 ... 2025-05-19 09:07:38 +04:00
9226e8b490 ... 2025-05-19 08:56:15 +04:00
b7fc7734b6 ... 2025-05-19 08:52:09 +04:00
8749e3a8cb ... 2025-05-19 08:52:03 +04:00
61f9f2868a ... 2025-05-19 08:25:47 +04:00
97dfcbeb51 ... 2025-05-19 07:37:10 +04:00
238fabbcb2 ... 2025-05-19 07:09:27 +04:00
49542b4bff ... 2025-05-19 07:04:20 +04:00
46898112f5 ... 2025-05-19 06:39:52 +04:00
f9bdb22c67 ... 2025-05-19 05:44:23 +04:00
cb664b2115 ... 2025-05-18 10:58:49 +03:00
timurgordon
761b9e031e docusaurus fixes 2025-05-16 18:03:22 +03:00
timurgordon
0d8d11fe26 small heroscript docusaurus fixes 2025-05-15 13:29:29 +03:00
timurgordon
2d5fbd3337 docusaurus fixes and correct hero cli implementation 2025-05-13 01:54:47 +03:00
cd3c98280e ... 2025-05-09 12:53:08 +03:00
Scott Yeager
54dc3d3f1f Update griddriver to use prebuilt binary 2025-03-06 18:26:33 -08:00
191 changed files with 12684 additions and 3145 deletions

1
.gitignore vendored
View File

@@ -47,3 +47,4 @@ tmp
compile_summary.log compile_summary.log
.summary_lock .summary_lock
.aider* .aider*
*.dylib

3
.roo/mcp.json Normal file
View File

@@ -0,0 +1,3 @@
{
"mcpServers": {}
}

View File

View File

77
cfg/config.heroscript Normal file
View File

@@ -0,0 +1,77 @@
!!docusaurus.config
name:"my-documentation"
title:"My Documentation Site"
tagline:"Documentation made simple with V and Docusaurus"
url:"https://docs.example.com"
url_home:"docs/"
base_url:"/"
favicon:"img/favicon.png"
image:"img/hero.png"
copyright:"© 2025 Example Organization"
!!docusaurus.config_meta
description:"Comprehensive documentation for our amazing project"
image:"https://docs.example.com/img/social-card.png"
title:"My Documentation | Official Docs"
!!docusaurus.ssh_connection
name:"production"
host:"example.com"
login:"deploy"
port:22
key_path:"~/.ssh/id_rsa"
!!docusaurus.build_dest
ssh_name:"production"
path:"/var/www/docs"
!!docusaurus.navbar
title:"My Project"
!!docusaurus.navbar_item
label:"Documentation"
href:"/docs"
position:"left"
!!docusaurus.navbar_item
label:"API"
href:"/api"
position:"left"
!!docusaurus.navbar_item
label:"GitHub"
href:"https://github.com/example/repo"
position:"right"
!!docusaurus.footer
style:"dark"
!!docusaurus.footer_item
title:"Documentation"
label:"Introduction"
to:"/docs"
!!docusaurus.footer_item
title:"Documentation"
label:"API Reference"
to:"/api"
!!docusaurus.footer_item
title:"Community"
label:"GitHub"
href:"https://github.com/example/repo"
!!docusaurus.footer_item
title:"Community"
label:"Discord"
href:"https://discord.gg/example"
!!docusaurus.footer_item
title:"More"
label:"Blog"
href:"https://blog.example.com"
!!docusaurus.import_source
url:"https://github.com/example/external-docs"
dest:"external"
replace:"PROJECT_NAME:My Project, VERSION:1.0.0"

View File

@@ -28,7 +28,7 @@ fn get_platform_id() string {
} }
fn read_secrets() ! { fn read_secrets() ! {
secret_file := os.join_path(os.home_dir(), 'code/git.ourworld.tf/despiegk/hero_secrets/mysecrets.sh') secret_file := os.join_path(os.home_dir(), 'code/git.threefold.info/despiegk/hero_secrets/mysecrets.sh')
if os.exists(secret_file) { if os.exists(secret_file) {
println('Reading secrets from ${secret_file}') println('Reading secrets from ${secret_file}')
content := os.read_file(secret_file)! content := os.read_file(secret_file)!

View File

@@ -19,25 +19,24 @@ fn playcmds_do(path string) ! {
} }
fn do() ! { fn do() ! {
if !core.is_osx()! {
if ! core.is_osx()! {
if os.getenv('SUDO_COMMAND') != '' || os.getenv('SUDO_USER') != '' { if os.getenv('SUDO_COMMAND') != '' || os.getenv('SUDO_USER') != '' {
println('Error: Please do not run this program with sudo!') println('Error: Please do not run this program with sudo!')
exit(1) // Exit with error code exit(1) // Exit with error code
} }
} }
if os.getuid() == 0 { if os.getuid() == 0 {
if core.is_osx()! { if core.is_osx()! {
eprintln("please do not run hero as root in osx.") eprintln('please do not run hero as root in osx.')
exit(1) exit(1)
} }
} else { } else {
if ! core.is_osx()! { if !core.is_osx()! {
eprintln("please do run hero as root, don't use sudo.") eprintln("please do run hero as root, don't use sudo.")
exit(1) exit(1)
} }
} }
if os.args.len == 2 { if os.args.len == 2 {
mypath := os.args[1] mypath := os.args[1]
@@ -51,7 +50,7 @@ fn do() ! {
mut cmd := Command{ mut cmd := Command{
name: 'hero' name: 'hero'
description: 'Your HERO toolset.' description: 'Your HERO toolset.'
version: '1.0.25' version: '1.0.26'
} }
// herocmds.cmd_run_add_flags(mut cmd) // herocmds.cmd_run_add_flags(mut cmd)
@@ -115,4 +114,4 @@ fn main() {
fn pre_func(cmd Command) ! { fn pre_func(cmd Command) ! {
herocmds.plbook_run(cmd)! herocmds.plbook_run(cmd)!
} }

View File

@@ -73,9 +73,9 @@ function sshknownkeysadd {
then then
ssh-keyscan github.com >> ~/.ssh/known_hosts ssh-keyscan github.com >> ~/.ssh/known_hosts
fi fi
if ! grep git.ourworld.tf ~/.ssh/known_hosts > /dev/null if ! grep git.threefold.info ~/.ssh/known_hosts > /dev/null
then then
ssh-keyscan git.ourworld.tf >> ~/.ssh/known_hosts ssh-keyscan git.threefold.info >> ~/.ssh/known_hosts
fi fi
git config --global pull.rebase false git config --global pull.rebase false

View File

@@ -5,7 +5,7 @@ import freeflowuniverse.herolib.core.playbook
import os import os
mut plbook := playbook.new( mut plbook := playbook.new(
path: '${os.home_dir()}/code/git.ourworld.tf/ourworld_holding/investorstool/output' path: '${os.home_dir()}/code/git.threefold.info/ourworld_holding/investorstool/output'
)! )!
mut it := investortool.play(mut plbook)! mut it := investortool.play(mut plbook)!
it.check()! it.check()!

View File

@@ -12,7 +12,7 @@ import os
const name = 'tf9_budget' const name = 'tf9_budget'
const wikipath = '${os.home_dir()}/code/git.ourworld.tf/ourworld_holding/info_ourworld/collections/${name}' const wikipath = '${os.home_dir()}/code/git.threefold.info/ourworld_holding/info_ourworld/collections/${name}'
const summarypath = '${wikipath}/summary.md' const summarypath = '${wikipath}/summary.md'
// mut sh := spreadsheet.sheet_new(name: 'test2') or { panic(err) } // mut sh := spreadsheet.sheet_new(name: 'test2') or { panic(err) }

257
examples/clients/mycelium_rpc.vsh Executable file
View File

@@ -0,0 +1,257 @@
#!/usr/bin/env -S v -n -w -gc none -cc tcc -d use_openssl -enable-globals run
// Mycelium RPC Client Example
// This example demonstrates how to use the new Mycelium JSON-RPC client
// to interact with a Mycelium node's admin API
import freeflowuniverse.herolib.clients.mycelium_rpc
import freeflowuniverse.herolib.installers.net.mycelium_installer
import time
import os
import encoding.base64
const mycelium_port = 8990
fn terminate_mycelium() ! {
// Try to find and kill any running mycelium process
res := os.execute('pkill mycelium')
if res.exit_code == 0 {
println('Terminated existing mycelium processes')
time.sleep(1 * time.second)
}
}
fn start_mycelium_node() ! {
// Start a mycelium node with JSON-RPC API enabled
println('Starting Mycelium node with JSON-RPC API on port ${mycelium_port}...')
// Create directory for mycelium data
os.execute('mkdir -p /tmp/mycelium_rpc_example')
// Start mycelium in background with both HTTP and JSON-RPC APIs enabled
spawn fn () {
cmd := 'cd /tmp/mycelium_rpc_example && mycelium --peers tcp://185.69.166.8:9651 quic://[2a02:1802:5e:0:ec4:7aff:fe51:e36b]:9651 tcp://65.109.18.113:9651 --tun-name tun_rpc_example --tcp-listen-port 9660 --quic-listen-port 9661 --api-addr 127.0.0.1:8989 --jsonrpc-addr 127.0.0.1:${mycelium_port}'
println('Executing: ${cmd}')
result := os.execute(cmd)
if result.exit_code != 0 {
println('Mycelium failed to start: ${result.output}')
}
}()
// Wait for the node to start (JSON-RPC server needs a bit more time)
println('Waiting for mycelium to start...')
time.sleep(5 * time.second)
// Check if mycelium is running
check_result := os.execute('pgrep mycelium')
if check_result.exit_code == 0 {
println('Mycelium process is running (PID: ${check_result.output.trim_space()})')
} else {
println('Warning: Mycelium process not found')
}
// Check what ports are listening
port_check := os.execute('lsof -i :${mycelium_port}')
if port_check.exit_code == 0 {
println('Port ${mycelium_port} is listening:')
println(port_check.output)
} else {
println('Warning: Port ${mycelium_port} is not listening')
}
}
fn main() {
// Install mycelium if not already installed
println('Checking Mycelium installation...')
mut installer := mycelium_installer.get()!
installer.install()!
// Clean up any existing processes
terminate_mycelium() or {}
defer {
// Clean up on exit
terminate_mycelium() or {}
os.execute('rm -rf /tmp/mycelium_rpc_example')
}
// Start mycelium node
start_mycelium_node()!
// Create RPC client
println('\n=== Creating Mycelium RPC Client ===')
mut client := mycelium_rpc.new_client(
name: 'example_client'
url: 'http://localhost:${mycelium_port}'
)!
println('Connected to Mycelium node at http://localhost:${mycelium_port}')
// Example 1: Get node information
println('\n=== Getting Node Information ===')
info := client.get_info() or {
println('Error getting node info: ${err}')
println('Make sure Mycelium node is running with API enabled')
return
}
println('Node Subnet: ${info.node_subnet}')
println('Node Public Key: ${info.node_pubkey}')
// Example 2: List peers
println('\n=== Listing Peers ===')
peers := client.get_peers() or {
println('Error getting peers: ${err}')
return
}
println('Found ${peers.len} peers:')
for i, peer in peers {
println('Peer ${i + 1}:')
println(' Endpoint: ${peer.endpoint.proto}://${peer.endpoint.socket_addr}')
println(' Type: ${peer.peer_type}')
println(' Connection State: ${peer.connection_state}')
println(' TX Bytes: ${peer.tx_bytes}')
println(' RX Bytes: ${peer.rx_bytes}')
}
// Example 3: Get routing information
println('\n=== Getting Routing Information ===')
// Get selected routes
routes := client.get_selected_routes() or {
println('Error getting selected routes: ${err}')
return
}
println('Selected Routes (${routes.len}):')
for route in routes {
println(' ${route.subnet} -> ${route.next_hop} (metric: ${route.metric}, seqno: ${route.seqno})')
}
// Get fallback routes
fallback_routes := client.get_fallback_routes() or {
println('Error getting fallback routes: ${err}')
return
}
println('Fallback Routes (${fallback_routes.len}):')
for route in fallback_routes {
println(' ${route.subnet} -> ${route.next_hop} (metric: ${route.metric}, seqno: ${route.seqno})')
}
// Example 4: Topic management
println('\n=== Topic Management ===')
// Get default topic action
default_action := client.get_default_topic_action() or {
println('Error getting default topic action: ${err}')
return
}
println('Default topic action (accept): ${default_action}')
// Get configured topics
topics := client.get_topics() or {
println('Error getting topics: ${err}')
return
}
println('Configured topics (${topics.len}):')
for topic in topics {
println(' - ${topic}')
}
// Example 5: Add a test topic (try different names)
println('\n=== Adding Test Topics ===')
test_topics := ['example_topic', 'test_with_underscore', 'hello world', 'test', 'a']
for topic in test_topics {
println('Trying to add topic: "${topic}"')
add_result := client.add_topic(topic) or {
println('Error adding topic "${topic}": ${err}')
continue
}
if add_result {
println('Successfully added topic: ${topic}')
// Try to remove it immediately
remove_result := client.remove_topic(topic) or {
println('Error removing topic "${topic}": ${err}')
continue
}
if remove_result {
println('Successfully removed topic: ${topic}')
}
break // Stop after first success
}
}
// Example 6: Message operations (demonstration only - requires another node)
println('\n=== Message Operations (Demo) ===')
println('Note: These operations require another Mycelium node to be meaningful')
// Try to pop a message with a short timeout (will likely return "No message ready" error)
message := client.pop_message(false, 1, '') or {
println('No messages available (expected): ${err}')
mycelium_rpc.InboundMessage{}
}
if message.id != '' {
println('Received message:')
println(' ID: ${message.id}')
println(' From: ${message.src_ip}')
println(' Payload: ${base64.decode_str(message.payload)}')
}
// Example 7: Peer management (demonstration)
println('\n=== Peer Management Demo ===')
// Try to add a peer (this is just for demonstration)
test_endpoint := 'tcp://127.0.0.1:9999'
add_peer_result := client.add_peer(test_endpoint) or {
println('Error adding peer (expected if endpoint is invalid): ${err}')
false
}
if add_peer_result {
println('Successfully added peer: ${test_endpoint}')
// Remove the test peer
remove_peer_result := client.delete_peer(test_endpoint) or {
println('Error removing peer: ${err}')
false
}
if remove_peer_result {
println('Successfully removed test peer')
}
}
// Example 8: Get public key from IP (demonstration)
println('\n=== Public Key Lookup Demo ===')
// This will likely fail unless we have a valid mycelium IP
if info.node_subnet != '' {
// Extract the first IP from the subnet for testing
subnet_parts := info.node_subnet.split('::')
if subnet_parts.len > 0 {
test_ip := subnet_parts[0] + '::1'
pubkey_response := client.get_public_key_from_ip(test_ip) or {
println('Could not get public key for IP ${test_ip}: ${err}')
mycelium_rpc.PublicKeyResponse{}
}
if pubkey_response.node_pub_key != '' {
println('Public key for ${test_ip}: ${pubkey_response.node_pub_key}')
}
}
}
println('\n=== Mycelium RPC Client Example Completed ===')
println('This example demonstrated:')
println('- Getting node information')
println('- Listing peers and their connection status')
println('- Retrieving routing information')
println('- Managing topics')
println('- Message operations (basic)')
println('- Peer management')
println('- Public key lookups')
println('')
println('For full message sending/receiving functionality, you would need')
println('multiple Mycelium nodes running and connected to each other.')
println('See the Mycelium documentation for more advanced usage.')
}

View File

@@ -0,0 +1,291 @@
#!/usr/bin/env -S v -n -w -gc none -cc tcc -d use_openssl -enable-globals run
import freeflowuniverse.herolib.clients.zinit_rpc
import os
import time
// Comprehensive example demonstrating all Zinit RPC client functionality
// This example shows how to use all 18 methods in the Zinit JSON-RPC API
println('=== Zinit RPC Client Example ===\n')
// Start Zinit in the background
println('Starting Zinit in background...')
mut zinit_process := os.new_process('/usr/local/bin/zinit')
zinit_process.set_args(['init'])
zinit_process.set_redirect_stdio()
zinit_process.run()
// Wait a moment for Zinit to start up
time.sleep(2000 * time.millisecond)
println(' Zinit started')
// Ensure we clean up Zinit when done
defer {
println('\nCleaning up...')
zinit_process.signal_kill()
zinit_process.wait()
println(' Zinit stopped')
}
// Create a new client
mut client := zinit_rpc.new_client(
name: 'example_client'
socket_path: '/tmp/zinit.sock'
) or {
println('Failed to create client: ${err}')
println('Make sure Zinit is running and the socket exists at /tmp/zinit.sock')
exit(1)
}
println(' Created Zinit RPC client')
// 1. Discover API specification
println('\n1. Discovering API specification...')
spec := client.rpc_discover() or {
println('Failed to discover API: ${err}')
exit(1)
}
println(' API discovered:')
println(' - OpenRPC version: ${spec.openrpc}')
println(' - API title: ${spec.info.title}')
println(' - API version: ${spec.info.version}')
println(' - Methods available: ${spec.methods.len}')
// 2. List all services
println('\n2. Listing all services...')
services := client.service_list() or {
println('Failed to list services: ${err}')
exit(1)
}
println(' Found ${services.len} services:')
for service_name, state in services {
println(' - ${service_name}: ${state}')
}
// 3. Create a test service configuration
println('\n3. Creating a test service...')
test_service_name := 'test_echo_service'
config := zinit_rpc.ServiceConfig{
exec: '/bin/echo "Hello from test service"'
oneshot: true
log: 'stdout'
env: {
'TEST_VAR': 'test_value'
}
shutdown_timeout: 10
}
service_path := client.service_create(test_service_name, config) or {
if err.msg().contains('already exists') {
println(' Service already exists, continuing...')
''
} else {
println('Failed to create service: ${err}')
exit(1)
}
}
if service_path != '' {
println(' Service created at: ${service_path}')
}
// 4. Get service configuration
println('\n4. Getting service configuration...')
retrieved_config := client.service_get(test_service_name) or {
println('Failed to get service config: ${err}')
exit(1)
}
println(' Service config retrieved:')
println(' - Exec: ${retrieved_config.exec}')
println(' - Oneshot: ${retrieved_config.oneshot}')
println(' - Log: ${retrieved_config.log}')
println(' - Shutdown timeout: ${retrieved_config.shutdown_timeout}')
// 5. Monitor the service
println('\n5. Starting to monitor the service...')
client.service_monitor(test_service_name) or {
if err.msg().contains('already monitored') {
println(' Service already monitored')
} else {
println('Failed to monitor service: ${err}')
exit(1)
}
}
// 6. Get service status
println('\n6. Getting service status...')
status := client.service_status(test_service_name) or {
println('Failed to get service status: ${err}')
exit(1)
}
println(' Service status:')
println(' - Name: ${status.name}')
println(' - PID: ${status.pid}')
println(' - State: ${status.state}')
println(' - Target: ${status.target}')
if status.after.len > 0 {
println(' - Dependencies:')
for dep_name, dep_state in status.after {
println(' - ${dep_name}: ${dep_state}')
}
}
// 7. Start the service (if it's not running)
if status.state != 'Running' {
println('\n7. Starting the service...')
client.service_start(test_service_name) or {
println('Failed to start service: ${err}')
// Continue anyway
}
println(' Service start command sent')
} else {
println('\n7. Service is already running')
}
// 8. Get service statistics (if running)
println('\n8. Getting service statistics...')
stats := client.service_stats(test_service_name) or {
println('Failed to get service stats (service might not be running): ${err}')
// Continue anyway
zinit_rpc.ServiceStats{}
}
if stats.name != '' {
println(' Service statistics:')
println(' - Name: ${stats.name}')
println(' - PID: ${stats.pid}')
println(' - Memory usage: ${stats.memory_usage} bytes')
println(' - CPU usage: ${stats.cpu_usage}%')
if stats.children.len > 0 {
println(' - Child processes:')
for child in stats.children {
println(' - PID ${child.pid}: Memory ${child.memory_usage} bytes, CPU ${child.cpu_usage}%')
}
}
}
// 9. Get current logs
println('\n9. Getting current logs...')
all_logs := client.stream_current_logs(name: '') or {
println('Failed to get logs: ${err}')
[]string{}
}
if all_logs.len > 0 {
println(' Retrieved ${all_logs.len} log entries (showing last 3):')
start_idx := if all_logs.len > 3 { all_logs.len - 3 } else { 0 }
for i in start_idx .. all_logs.len {
println(' ${all_logs[i]}')
}
} else {
println(' No logs available')
}
// 10. Get logs for specific service
println('\n10. Getting logs for test service...')
service_logs := client.stream_current_logs(name: test_service_name) or {
println('Failed to get service logs: ${err}')
[]string{}
}
if service_logs.len > 0 {
println(' Retrieved ${service_logs.len} log entries for ${test_service_name}:')
for log in service_logs {
println(' ${log}')
}
} else {
println(' No logs available for ${test_service_name}')
}
// 11. Subscribe to logs
println('\n11. Subscribing to log stream...')
subscription_id := client.stream_subscribe_logs(name: test_service_name) or {
println('Failed to subscribe to logs: ${err}')
u64(0)
}
if subscription_id != 0 {
println(' Subscribed to logs with ID: ${subscription_id}')
}
// 12. Send signal to service (if running)
// Get fresh status to make sure service is still running
fresh_status := client.service_status(test_service_name) or {
println('\n12. Skipping signal test (cannot get service status)')
zinit_rpc.ServiceStatus{}
}
if fresh_status.state == 'Running' && fresh_status.pid > 0 {
println('\n12. Sending SIGTERM signal to service...')
client.service_kill(test_service_name, 'SIGTERM') or {
println('Failed to send signal: ${err}')
// Continue anyway
}
println(' Signal sent')
} else {
println('\n12. Skipping signal test (service not running: state=${fresh_status.state}, pid=${fresh_status.pid})')
}
// 13. Stop the service
println('\n13. Stopping the service...')
client.service_stop(test_service_name) or {
if err.msg().contains('is down') {
println(' Service is already stopped')
} else {
println('Failed to stop service: ${err}')
// Continue anyway
}
}
// 14. Forget the service
println('\n14. Forgetting the service...')
client.service_forget(test_service_name) or {
println('Failed to forget service: ${err}')
// Continue anyway
}
println(' Service forgotten')
// 15. Delete the service configuration
println('\n15. Deleting service configuration...')
delete_result := client.service_delete(test_service_name) or {
println('Failed to delete service: ${err}')
''
}
if delete_result != '' {
println(' Service deleted: ${delete_result}')
}
// 16. Test HTTP server operations
println('\n16. Testing HTTP server operations...')
server_result := client.system_start_http_server('127.0.0.1:9999') or {
println('Failed to start HTTP server: ${err}')
''
}
if server_result != '' {
println(' HTTP server started: ${server_result}')
// Stop the HTTP server
client.system_stop_http_server() or { println('Failed to stop HTTP server: ${err}') }
println(' HTTP server stopped')
}
// 17. Test system operations (commented out for safety)
println('\n17. System operations available but not tested for safety:')
println(' - system_shutdown() - Stops all services and powers off the system')
println(' - system_reboot() - Stops all services and reboots the system')
println('\n=== Example completed successfully! ===')
println('\nThis example demonstrated all 18 methods in the Zinit JSON-RPC API:')
println(' rpc.discover - Get OpenRPC specification')
println(' service_list - List all services')
println(' service_create - Create service configuration')
println(' service_get - Get service configuration')
println(' service_monitor - Start monitoring service')
println(' service_status - Get service status')
println(' service_start - Start service')
println(' service_stats - Get service statistics')
println(' stream_current_logs - Get current logs')
println(' stream_subscribe_logs - Subscribe to logs (returns subscription ID)')
println(' service_kill - Send signal to service')
println(' service_stop - Stop service')
println(' service_forget - Stop monitoring service')
println(' service_delete - Delete service configuration')
println(' system_start_http_server - Start HTTP server')
println(' system_stop_http_server - Stop HTTP server')
println(' system_shutdown - Available but not tested')
println(' system_reboot - Available but not tested')

View File

@@ -1,4 +1,4 @@
#!/usr/bin/env -S v -n -w -gc none -cc tcc -d use_openssl -enable-globals run #!/usr/bin/env -S v -n -w -gc none -cg -cc tcc -d use_openssl -enable-globals run
import freeflowuniverse.herolib.crypt.aes_symmetric { decrypt, encrypt } import freeflowuniverse.herolib.crypt.aes_symmetric { decrypt, encrypt }
import freeflowuniverse.herolib.ui.console import freeflowuniverse.herolib.ui.console

View File

@@ -8,7 +8,7 @@ mut gs := gittools.new()!
mydocs_path := gs.get_path( mydocs_path := gs.get_path(
pull: true pull: true
reset: false reset: false
url: 'https://git.ourworld.tf/tfgrid/info_docs_depin/src/branch/main/docs' url: 'https://git.threefold.info/tfgrid/info_docs_depin/src/branch/main/docs'
)! )!
println(mydocs_path) println(mydocs_path)

View File

@@ -1,5 +1,5 @@
!!juggler.configure !!juggler.configure
url: 'https://git.ourworld.tf/projectmycelium/itenv' url: 'https://git.threefold.info/projectmycelium/itenv'
username: '' username: ''
password: '' password: ''
port: 8000 port: 8000

View File

@@ -1 +1 @@
hero juggler -u https://git.ourworld.tf/projectmycelium/itenv hero juggler -u https://git.threefold.info/projectmycelium/itenv

View File

@@ -8,7 +8,7 @@ import veb
osal.load_env_file('${os.dir(@FILE)}/.env')! osal.load_env_file('${os.dir(@FILE)}/.env')!
mut j := juggler.configure( mut j := juggler.configure(
url: 'https://git.ourworld.tf/projectmycelium/itenv' url: 'https://git.threefold.info/projectmycelium/itenv'
username: os.getenv('JUGGLER_USERNAME') username: os.getenv('JUGGLER_USERNAME')
password: os.getenv('JUGGLER_PASSWORD') password: os.getenv('JUGGLER_PASSWORD')
reset: true reset: true

View File

@@ -6,7 +6,7 @@ import os
mut sm := startupmanager.get()! mut sm := startupmanager.get()!
sm.start( sm.start(
name: 'juggler' name: 'juggler'
cmd: 'hero juggler -secret planetfirst -u https://git.ourworld.tf/projectmycelium/itenv -reset true' cmd: 'hero juggler -secret planetfirst -u https://git.threefold.info/projectmycelium/itenv -reset true'
env: { env: {
'HOME': os.home_dir() 'HOME': os.home_dir()
} }

BIN
examples/installers/infra/dify Executable file

Binary file not shown.

View File

@@ -0,0 +1,9 @@
#!/usr/bin/env -S v -n -w -gc none -cc tcc -d use_openssl -enable-globals run
import freeflowuniverse.herolib.installers.infra.dify as dify_installer
mut dify := dify_installer.get()!
dify.install()!
dify.start()!
// dify.destroy()!

View File

@@ -0,0 +1,126 @@
module main
import freeflowuniverse.herolib.osal.zinit
import json
fn main() {
// Create a new Zinit client with the default socket path
mut zinit_client := zinit.new_stateless(socket_path: '/tmp/zinit.sock')!
println('Connected to Zinit via OpenRPC')
// Example 1: Get the OpenRPC API specification
println('\n=== Getting API Specification ===')
api_spec := zinit_client.client.discover() or {
println('Error getting API spec: ${err}')
return
}
println('API Specification (first 100 chars): ${api_spec[..100]}...')
// Example 2: List all services
println('\n=== Listing Services ===')
service_list := zinit_client.client.list() or {
println('Error listing services: ${err}')
return
}
println('Services:')
for name, state in service_list {
println('- ${name}: ${state}')
}
// Example 3: Get detailed status of a service (if any exist)
if service_list.len > 0 {
service_name := service_list.keys()[0]
println('\n=== Getting Status for Service: ${service_name} ===')
status := zinit_client.client.status(service_name) or {
println('Error getting status: ${err}')
return
}
println('Service Status:')
println('- Name: ${status.name}')
println('- PID: ${status.pid}')
println('- State: ${status.state}')
println('- Target: ${status.target}')
println('- Dependencies:')
for dep_name, dep_state in status.after {
println(' - ${dep_name}: ${dep_state}')
}
// Example 4: Get service stats
println('\n=== Getting Stats for Service: ${service_name} ===')
stats := zinit_client.client.stats(service_name) or {
println('Error getting stats: ${err}')
println('Note: Stats are only available for running services')
return
}
println('Service Stats:')
println('- Memory Usage: ${stats.memory_usage} bytes')
println('- CPU Usage: ${stats.cpu_usage}%')
if stats.children.len > 0 {
println('- Child Processes:')
for child in stats.children {
println(' - PID: ${child.pid}, Memory: ${child.memory_usage} bytes, CPU: ${child.cpu_usage}%')
}
}
} else {
println('\nNo services found to query')
}
// Example 5: Create a new service (commented out for safety)
/*
println('\n=== Creating a New Service ===')
new_service_config := zinit.ServiceConfig{
exec: '/bin/echo "Hello from Zinit"'
oneshot: true
after: []string{}
log: 'stdout'
env: {
'ENV_VAR': 'value'
}
}
result := zinit_client.client.create_service('example_service', new_service_config) or {
println('Error creating service: ${err}')
return
}
println('Service created: ${result}')
// Start the service
zinit_client.client.start('example_service') or {
println('Error starting service: ${err}')
return
}
println('Service started')
// Get logs
logs := zinit_client.client.get_logs('example_service') or {
println('Error getting logs: ${err}')
return
}
println('Service logs:')
for log in logs {
println('- ${log}')
}
// Delete the service when done
zinit_client.client.stop('example_service') or {
println('Error stopping service: ${err}')
return
}
time.sleep(1 * time.second)
zinit_client.client.forget('example_service') or {
println('Error forgetting service: ${err}')
return
}
zinit_client.client.delete_service('example_service') or {
println('Error deleting service: ${err}')
return
}
println('Service deleted')
*/
println('\nZinit OpenRPC client example completed')
}

View File

@@ -0,0 +1,857 @@
{
"openrpc": "1.2.6",
"info": {
"version": "1.0.0",
"title": "Zinit JSON-RPC API",
"description": "JSON-RPC 2.0 API for controlling and querying Zinit services",
"license": {
"name": "MIT"
}
},
"servers": [
{
"name": "Unix Socket",
"url": "unix:///tmp/zinit.sock"
}
],
"methods": [
{
"name": "rpc.discover",
"description": "Returns the OpenRPC specification for the API",
"params": [],
"result": {
"name": "OpenRPCSpec",
"description": "The OpenRPC specification",
"schema": {
"type": "string"
}
}
},
{
"name": "service_list",
"description": "Lists all services managed by Zinit",
"params": [],
"result": {
"name": "ServiceList",
"description": "A map of service names to their current states",
"schema": {
"type": "object",
"additionalProperties": {
"type": "string",
"description": "Service state (Running, Success, Error, etc.)"
}
}
},
"examples": [
{
"name": "List all services",
"params": [],
"result": {
"name": "ServiceListResult",
"value": {
"service1": "Running",
"service2": "Success",
"service3": "Error"
}
}
}
]
},
{
"name": "service_status",
"description": "Shows detailed status information for a specific service",
"params": [
{
"name": "name",
"description": "The name of the service",
"required": true,
"schema": {
"type": "string"
}
}
],
"result": {
"name": "ServiceStatus",
"description": "Detailed status information for the service",
"schema": {
"type": "object",
"properties": {
"name": {
"type": "string",
"description": "Service name"
},
"pid": {
"type": "integer",
"description": "Process ID of the running service (if running)"
},
"state": {
"type": "string",
"description": "Current state of the service (Running, Success, Error, etc.)"
},
"target": {
"type": "string",
"description": "Target state of the service (Up, Down)"
},
"after": {
"type": "object",
"description": "Dependencies of the service and their states",
"additionalProperties": {
"type": "string",
"description": "State of the dependency"
}
}
}
}
},
"examples": [
{
"name": "Get status of redis service",
"params": [
{
"name": "name",
"value": "redis"
}
],
"result": {
"name": "ServiceStatusResult",
"value": {
"name": "redis",
"pid": 1234,
"state": "Running",
"target": "Up",
"after": {
"dependency1": "Success",
"dependency2": "Running"
}
}
}
}
],
"errors": [
{
"code": -32000,
"message": "Service not found",
"data": "service name \"unknown\" unknown"
}
]
},
{
"name": "service_start",
"description": "Starts a service",
"params": [
{
"name": "name",
"description": "The name of the service to start",
"required": true,
"schema": {
"type": "string"
}
}
],
"result": {
"name": "StartResult",
"description": "Result of the start operation",
"schema": {
"type": "null"
}
},
"examples": [
{
"name": "Start redis service",
"params": [
{
"name": "name",
"value": "redis"
}
],
"result": {
"name": "StartResult",
"value": null
}
}
],
"errors": [
{
"code": -32000,
"message": "Service not found",
"data": "service name \"unknown\" unknown"
}
]
},
{
"name": "service_stop",
"description": "Stops a service",
"params": [
{
"name": "name",
"description": "The name of the service to stop",
"required": true,
"schema": {
"type": "string"
}
}
],
"result": {
"name": "StopResult",
"description": "Result of the stop operation",
"schema": {
"type": "null"
}
},
"examples": [
{
"name": "Stop redis service",
"params": [
{
"name": "name",
"value": "redis"
}
],
"result": {
"name": "StopResult",
"value": null
}
}
],
"errors": [
{
"code": -32000,
"message": "Service not found",
"data": "service name \"unknown\" unknown"
},
{
"code": -32003,
"message": "Service is down",
"data": "service \"redis\" is down"
}
]
},
{
"name": "service_monitor",
"description": "Starts monitoring a service. The service configuration is loaded from the config directory.",
"params": [
{
"name": "name",
"description": "The name of the service to monitor",
"required": true,
"schema": {
"type": "string"
}
}
],
"result": {
"name": "MonitorResult",
"description": "Result of the monitor operation",
"schema": {
"type": "null"
}
},
"examples": [
{
"name": "Monitor redis service",
"params": [
{
"name": "name",
"value": "redis"
}
],
"result": {
"name": "MonitorResult",
"value": null
}
}
],
"errors": [
{
"code": -32001,
"message": "Service already monitored",
"data": "service \"redis\" already monitored"
},
{
"code": -32005,
"message": "Config error",
"data": "failed to load service configuration"
}
]
},
{
"name": "service_forget",
"description": "Stops monitoring a service. You can only forget a stopped service.",
"params": [
{
"name": "name",
"description": "The name of the service to forget",
"required": true,
"schema": {
"type": "string"
}
}
],
"result": {
"name": "ForgetResult",
"description": "Result of the forget operation",
"schema": {
"type": "null"
}
},
"examples": [
{
"name": "Forget redis service",
"params": [
{
"name": "name",
"value": "redis"
}
],
"result": {
"name": "ForgetResult",
"value": null
}
}
],
"errors": [
{
"code": -32000,
"message": "Service not found",
"data": "service name \"unknown\" unknown"
},
{
"code": -32002,
"message": "Service is up",
"data": "service \"redis\" is up"
}
]
},
{
"name": "service_kill",
"description": "Sends a signal to a running service",
"params": [
{
"name": "name",
"description": "The name of the service to send the signal to",
"required": true,
"schema": {
"type": "string"
}
},
{
"name": "signal",
"description": "The signal to send (e.g., SIGTERM, SIGKILL)",
"required": true,
"schema": {
"type": "string"
}
}
],
"result": {
"name": "KillResult",
"description": "Result of the kill operation",
"schema": {
"type": "null"
}
},
"examples": [
{
"name": "Send SIGTERM to redis service",
"params": [
{
"name": "name",
"value": "redis"
},
{
"name": "signal",
"value": "SIGTERM"
}
],
"result": {
"name": "KillResult",
"value": null
}
}
],
"errors": [
{
"code": -32000,
"message": "Service not found",
"data": "service name \"unknown\" unknown"
},
{
"code": -32003,
"message": "Service is down",
"data": "service \"redis\" is down"
},
{
"code": -32004,
"message": "Invalid signal",
"data": "invalid signal: INVALID"
}
]
},
{
"name": "system_shutdown",
"description": "Stops all services and powers off the system",
"params": [],
"result": {
"name": "ShutdownResult",
"description": "Result of the shutdown operation",
"schema": {
"type": "null"
}
},
"examples": [
{
"name": "Shutdown the system",
"params": [],
"result": {
"name": "ShutdownResult",
"value": null
}
}
],
"errors": [
{
"code": -32006,
"message": "Shutting down",
"data": "system is already shutting down"
}
]
},
{
"name": "system_reboot",
"description": "Stops all services and reboots the system",
"params": [],
"result": {
"name": "RebootResult",
"description": "Result of the reboot operation",
"schema": {
"type": "null"
}
},
"examples": [
{
"name": "Reboot the system",
"params": [],
"result": {
"name": "RebootResult",
"value": null
}
}
],
"errors": [
{
"code": -32006,
"message": "Shutting down",
"data": "system is already shutting down"
}
]
},
{
"name": "service_create",
"description": "Creates a new service configuration file",
"params": [
{
"name": "name",
"description": "The name of the service to create",
"required": true,
"schema": {
"type": "string"
}
},
{
"name": "content",
"description": "The service configuration content",
"required": true,
"schema": {
"type": "object",
"properties": {
"exec": {
"type": "string",
"description": "Command to run"
},
"oneshot": {
"type": "boolean",
"description": "Whether the service should be restarted"
},
"after": {
"type": "array",
"items": {
"type": "string"
},
"description": "Services that must be running before this one starts"
},
"log": {
"type": "string",
"enum": ["null", "ring", "stdout"],
"description": "How to handle service output"
},
"env": {
"type": "object",
"additionalProperties": {
"type": "string"
},
"description": "Environment variables for the service"
},
"shutdown_timeout": {
"type": "integer",
"description": "Maximum time to wait for service to stop during shutdown"
}
}
}
}
],
"result": {
"name": "CreateServiceResult",
"description": "Result of the create operation",
"schema": {
"type": "string"
}
},
"errors": [
{
"code": -32007,
"message": "Service already exists",
"data": "Service 'name' already exists"
},
{
"code": -32008,
"message": "Service file error",
"data": "Failed to create service file"
}
]
},
{
"name": "service_delete",
"description": "Deletes a service configuration file",
"params": [
{
"name": "name",
"description": "The name of the service to delete",
"required": true,
"schema": {
"type": "string"
}
}
],
"result": {
"name": "DeleteServiceResult",
"description": "Result of the delete operation",
"schema": {
"type": "string"
}
},
"errors": [
{
"code": -32000,
"message": "Service not found",
"data": "Service 'name' not found"
},
{
"code": -32008,
"message": "Service file error",
"data": "Failed to delete service file"
}
]
},
{
"name": "service_get",
"description": "Gets a service configuration file",
"params": [
{
"name": "name",
"description": "The name of the service to get",
"required": true,
"schema": {
"type": "string"
}
}
],
"result": {
"name": "GetServiceResult",
"description": "The service configuration",
"schema": {
"type": "object"
}
},
"errors": [
{
"code": -32000,
"message": "Service not found",
"data": "Service 'name' not found"
},
{
"code": -32008,
"message": "Service file error",
"data": "Failed to read service file"
}
]
},
{
"name": "service_stats",
"description": "Get memory and CPU usage statistics for a service",
"params": [
{
"name": "name",
"description": "The name of the service to get stats for",
"required": true,
"schema": {
"type": "string"
}
}
],
"result": {
"name": "ServiceStats",
"description": "Memory and CPU usage statistics for the service",
"schema": {
"type": "object",
"properties": {
"name": {
"type": "string",
"description": "Service name"
},
"pid": {
"type": "integer",
"description": "Process ID of the service"
},
"memory_usage": {
"type": "integer",
"description": "Memory usage in bytes"
},
"cpu_usage": {
"type": "number",
"description": "CPU usage as a percentage (0-100)"
},
"children": {
"type": "array",
"description": "Stats for child processes",
"items": {
"type": "object",
"properties": {
"pid": {
"type": "integer",
"description": "Process ID of the child process"
},
"memory_usage": {
"type": "integer",
"description": "Memory usage in bytes"
},
"cpu_usage": {
"type": "number",
"description": "CPU usage as a percentage (0-100)"
}
}
}
}
}
}
},
"examples": [
{
"name": "Get stats for redis service",
"params": [
{
"name": "name",
"value": "redis"
}
],
"result": {
"name": "ServiceStatsResult",
"value": {
"name": "redis",
"pid": 1234,
"memory_usage": 10485760,
"cpu_usage": 2.5,
"children": [
{
"pid": 1235,
"memory_usage": 5242880,
"cpu_usage": 1.2
}
]
}
}
}
],
"errors": [
{
"code": -32000,
"message": "Service not found",
"data": "service name \"unknown\" unknown"
},
{
"code": -32003,
"message": "Service is down",
"data": "service \"redis\" is down"
}
]
},
{
"name": "system_start_http_server",
"description": "Start an HTTP/RPC server at the specified address",
"params": [
{
"name": "address",
"description": "The network address to bind the server to (e.g., '127.0.0.1:8080')",
"required": true,
"schema": {
"type": "string"
}
}
],
"result": {
"name": "StartHttpServerResult",
"description": "Result of the start HTTP server operation",
"schema": {
"type": "string"
}
},
"examples": [
{
"name": "Start HTTP server on localhost:8080",
"params": [
{
"name": "address",
"value": "127.0.0.1:8080"
}
],
"result": {
"name": "StartHttpServerResult",
"value": "HTTP server started at 127.0.0.1:8080"
}
}
],
"errors": [
{
"code": -32602,
"message": "Invalid address",
"data": "Invalid network address format"
}
]
},
{
"name": "system_stop_http_server",
"description": "Stop the HTTP/RPC server if running",
"params": [],
"result": {
"name": "StopHttpServerResult",
"description": "Result of the stop HTTP server operation",
"schema": {
"type": "null"
}
},
"examples": [
{
"name": "Stop the HTTP server",
"params": [],
"result": {
"name": "StopHttpServerResult",
"value": null
}
}
],
"errors": [
{
"code": -32602,
"message": "Server not running",
"data": "No HTTP server is currently running"
}
]
},
{
"name": "stream_currentLogs",
"description": "Get current logs from zinit and monitored services",
"params": [
{
"name": "name",
"description": "Optional service name filter. If provided, only logs from this service will be returned",
"required": false,
"schema": {
"type": "string"
}
}
],
"result": {
"name": "LogsResult",
"description": "Array of log strings",
"schema": {
"type": "array",
"items": {
"type": "string"
}
}
},
"examples": [
{
"name": "Get all logs",
"params": [],
"result": {
"name": "LogsResult",
"value": [
"2023-01-01T12:00:00 redis: Starting service",
"2023-01-01T12:00:01 nginx: Starting service"
]
}
},
{
"name": "Get logs for a specific service",
"params": [
{
"name": "name",
"value": "redis"
}
],
"result": {
"name": "LogsResult",
"value": [
"2023-01-01T12:00:00 redis: Starting service",
"2023-01-01T12:00:02 redis: Service started"
]
}
}
]
},
{
"name": "stream_subscribeLogs",
"description": "Subscribe to log messages generated by zinit and monitored services",
"params": [
{
"name": "name",
"description": "Optional service name filter. If provided, only logs from this service will be returned",
"required": false,
"schema": {
"type": "string"
}
}
],
"result": {
"name": "LogSubscription",
"description": "A subscription to log messages",
"schema": {
"type": "string"
}
},
"examples": [
{
"name": "Subscribe to all logs",
"params": [],
"result": {
"name": "LogSubscription",
"value": "2023-01-01T12:00:00 redis: Service started"
}
},
{
"name": "Subscribe to filtered logs",
"params": [
{
"name": "name",
"value": "redis"
}
],
"result": {
"name": "LogSubscription",
"value": "2023-01-01T12:00:00 redis: Service started"
}
}
]
}
]
}

View File

@@ -0,0 +1,11 @@
## start zinit
zinit is used as openrpc backend so we can test the openrpc schema.
```bash
#start zinit
zinit init
zinit list
```

View File

@@ -0,0 +1,107 @@
#!/usr/bin/env -S v -n -w -cg -gc none -d use_openssl -enable-globals run
import freeflowuniverse.herolib.schemas.jsonrpc
import freeflowuniverse.herolib.schemas.openrpc //for the model as used
import json
// Define the service status response structure based on the OpenRPC schema
struct ServiceStatus {
name string
pid int
state string
target string
after map[string]string
}
// Generic approach: Use a map to handle any complex JSON response
// This is more flexible than creating specific structs for each API
// Create a client using the Unix socket transport
mut cl := jsonrpc.new_unix_socket_client("/tmp/zinit.sock")
// Example 1: Discover the API using rpc_discover
// Create a request for rpc_discover method with empty parameters
discover_request := jsonrpc.new_request_generic('rpc.discover', []string{})
// Send the request and receive the OpenRPC specification as a JSON string
println('Sending rpc_discover request...')
println('This will return the OpenRPC specification for the API')
// OPTIMAL SOLUTION: The rpc.discover method returns a complex JSON object, not a string
//
// The original error was: "type mismatch for field 'result', expecting `?string` type, got: {...}"
// This happened because the code tried: cl.send[[]string, string](discover_request)
// But rpc.discover returns a complex nested JSON object.
//
// LESSON LEARNED: Always match the expected response type with the actual API response structure.
// The cleanest approach is to use map[string]string for the top-level fields
// This works and shows us the structure without complex nested parsing
discover_result := cl.send[[]string, map[string]string](discover_request)!
println(' FIXED: Type mismatch error resolved!')
println(' Changed from: cl.send[[]string, string]')
println(' Changed to: cl.send[[]string, map[string]string]')
println('\nAPI Discovery Result:')
for key, value in discover_result {
if value != '' {
println(' ${key}: ${value}')
} else {
println(' ${key}: <complex object - contains nested data>')
}
}
println('\n📝 ANALYSIS:')
println(' - openrpc: ${discover_result['openrpc']} (simple string)')
println(' - info: <complex object> (contains title, version, description, license)')
println(' - methods: <complex array> (contains all API method definitions)')
println(' - servers: <complex array> (contains server connection info)')
println('\n💡 RECOMMENDATION for production use:')
println(' - For simple display: Use map[string]string (current approach)')
println(' - For full parsing: Create proper structs matching the response')
println(' - For OpenRPC integration: Extract result as JSON string and pass to openrpc.decode()')
println('\n The core issue (type mismatch) is now completely resolved!')
// Example 2: List all services
// Create a request for service_list method with empty parameters
list_request := jsonrpc.new_request_generic('service_list', []string{})
// Send the request and receive a map of service names to states
println('\nSending service_list request...')
service_list := cl.send[[]string, map[string]string](list_request)!
// Display the service list
println('Service List:')
println(service_list)
// Example 3: Get status of a specific service
// First, check if we have any services to query
if service_list.len > 0 {
// Get the first service name from the list
service_name := service_list.keys()[0]
// Create a request for service_status method with the service name as parameter
// The parameter for service_status is a single string (service name)
status_request := jsonrpc.new_request_generic('service_status', {"name":service_name})
// Send the request and receive a ServiceStatus object
println('\nSending service_status request for service: $service_name')
service_status := cl.send[map[string]string, ServiceStatus](status_request)!
// Display the service status details
println('Service Status:')
println('- Name: ${service_status.name}')
println('- PID: ${service_status.pid}')
println('- State: ${service_status.state}')
println('- Target: ${service_status.target}')
println('- Dependencies:')
for dep_name, dep_state in service_status.after {
println(' - $dep_name: $dep_state')
}
} else {
println('\nNo services found to query status')
}

1
examples/web/.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
build

View File

@@ -0,0 +1,77 @@
!!docusaurus.config
name:"my-documentation"
title:"My Documentation Site"
tagline:"Documentation made simple with V and Docusaurus"
url:"https://docs.example.com"
url_home:"docs/"
base_url:"/"
favicon:"img/favicon.png"
image:"img/hero.png"
copyright:"© 2025 Example Organization"
!!docusaurus.config_meta
description:"Comprehensive documentation for our amazing project"
image:"https://docs.example.com/img/social-card.png"
title:"My Documentation | Official Docs"
!!docusaurus.ssh_connection
name:"production"
host:"example.com"
login:"deploy"
port:22
key_path:"~/.ssh/id_rsa"
!!docusaurus.build_dest
ssh_name:"production"
path:"/var/www/docs"
!!docusaurus.navbar
title:"My Project"
!!docusaurus.navbar_item
label:"Documentation"
href:"/docs"
position:"left"
!!docusaurus.navbar_item
label:"API"
href:"/api"
position:"left"
!!docusaurus.navbar_item
label:"GitHub"
href:"https://github.com/example/repo"
position:"right"
!!docusaurus.footer
style:"dark"
!!docusaurus.footer_item
title:"Documentation"
label:"Introduction"
to:"/docs"
!!docusaurus.footer_item
title:"Documentation"
label:"API Reference"
to:"/api"
!!docusaurus.footer_item
title:"Community"
label:"GitHub"
href:"https://github.com/example/repo"
!!docusaurus.footer_item
title:"Community"
label:"Discord"
href:"https://discord.gg/example"
!!docusaurus.footer_item
title:"More"
label:"Blog"
href:"https://blog.example.com"
!!docusaurus.import_source
url:"https://github.com/example/external-docs"
dest:"external"
replace:"PROJECT_NAME:My Project, VERSION:1.0.0"

View File

@@ -0,0 +1,124 @@
#!/usr/bin/env -S v -n -w -cg -gc none -cc tcc -d use_openssl -enable-globals run
import freeflowuniverse.herolib.web.doctreeclient
import freeflowuniverse.herolib.data.doctree
import os
println('DocTreeClient Example')
println('=====================')
// Step 1: First, populate Redis with doctree data
println('\n1. Setting up doctree data in Redis...')
tree.scan(
git_url: 'https://git.threefold.info/tfgrid/docs_tfgrid4/src/branch/main/collections'
git_pull: false
)!
tree.export(
destination: '/tmp/mdexport'
reset: true
exclude_errors: false
)!
println('Doctree data populated in Redis')
// Step 2: Create a DocTreeClient instance
println('\n2. Creating DocTreeClient...')
mut client := doctreeclient.new()!
println('DocTreeClient created successfully')
// Step 3: List all collections
println('\n3. Listing collections:')
collections := client.list_collections()!
println('Found ${collections.len} collections: ${collections}')
if collections.len == 0 {
println('No collections found. Example cannot continue.')
return
}
// Step 4: Use the example_docs collection
collection_name := 'example_docs'
println('\n4. Using collection: ${collection_name}')
// Step 5: List pages in the collection
println('\n5. Listing pages:')
pages := client.list_pages(collection_name)!
println('Found ${pages.len} pages: ${pages}')
// Step 6: Get content of a page
if pages.len > 0 {
page_name := 'introduction'
println('\n6. Getting content of page: ${page_name}')
// Check if page exists
exists := client.page_exists(collection_name, page_name)
println('Page exists: ${exists}')
// Get page path
page_path := client.get_page_path(collection_name, page_name)!
println('Page path: ${page_path}')
// Get page content
content := client.get_page_content(collection_name, page_name)!
println('Page content:')
println('---')
println(content)
println('---')
}
// Step 7: List images in the collection
println('\n7. Listing images:')
images := client.list_images(collection_name)!
println('Found ${images.len} images: ${images}')
// Step 8: Get image path
if images.len > 0 {
image_name := images[0]
println('\n8. Getting path of image: ${image_name}')
// Check if image exists
exists := client.image_exists(collection_name, image_name)
println('Image exists: ${exists}')
// Get image path
image_path := client.get_image_path(collection_name, image_name)!
println('Image path: ${image_path}')
}
// Step 9: List files in the collection
println('\n9. Listing files:')
files := client.list_files(collection_name)!
println('Found ${files.len} files: ${files}')
// Step 10: Get file path
if files.len > 0 {
file_name := files[0]
println('\n10. Getting path of file: ${file_name}')
// Check if file exists
exists := client.file_exists(collection_name, file_name)
println('File exists: ${exists}')
// Get file path
file_path := client.get_file_path(collection_name, file_name)!
println('File path: ${file_path}')
}
// Step 11: Error handling example
println('\n11. Error handling example:')
println('Trying to access a non-existent page...')
non_existent_page := 'non_existent_page'
content := client.get_page_content(collection_name, non_existent_page) or {
println('Error caught: ${err}')
'Error content'
}
// Step 12: Clean up
println('\n12. Cleaning up...')
os.rmdir_all(example_dir) or { println('Failed to remove example directory: ${err}') }
os.rmdir_all(export_dir) or { println('Failed to remove export directory: ${err}') }
println('\nExample completed successfully!')

View File

@@ -0,0 +1,91 @@
#!/bin/bash
# Exit script on any error
set -e
echo "Starting Docusaurus Example with Hero CLI"
# Define the source directory for the Docusaurus site content
# Using a different name (_cli) to avoid conflicts with the previous example
SOURCE_DIR="${HOME}/hero/var/docusaurus_demo_src_cli"
DOCS_SUBDIR="${SOURCE_DIR}/docs"
# Create the site source directory and the docs subdirectory if they don't exist
echo "Creating site source directory: ${SOURCE_DIR}"
mkdir -p "${DOCS_SUBDIR}"
# --- Create Sample Markdown Content ---
# The 'hero docusaurus' command doesn't automatically create content,
# so we do it here like the V example script did.
echo "Creating sample markdown content..."
# Create intro.md
# Using 'EOF' to prevent shell expansion within the heredoc
cat > "${DOCS_SUBDIR}/intro.md" << 'EOF'
---
title: Introduction (CLI Example)
slug: /
sidebar_position: 1
---
# Welcome to My Documentation (CLI Version)
This is a sample documentation site created with Docusaurus and HeroLib V using the `hero docusaurus` command and a HeroScript configuration file.
## Features
- Easy to use
- Markdown support
- Customizable
- Search functionality
## Getting Started
Follow these steps to get started:
1. Installation
2. Configuration
3. Adding content
4. Deployment
EOF
# Create quick-start.md
cat > "${DOCS_SUBDIR}/quick-start.md" << 'EOF'
---
title: Quick Start (CLI Example)
sidebar_position: 2
---
# Quick Start Guide (CLI Version)
This guide will help you get up and running quickly.
## Installation
```bash
$ npm install my-project
```
## Basic Usage
```javascript
import { myFunction } from "my-project";
// Use the function
const result = myFunction();
console.log(result);
```
EOF
echo "Sample markdown content created."
# --- Run Docusaurus Directly via V Script ---
SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )
# -n initializes the site structure if it doesn't exist (--new)
# -d runs the development server (--dev)
hero docusaurus -buildpath "${HOME}/hero/var/docusaurus_demo_src_cli" -path "${SCRIPT_DIR}/cfg/docusaurus_example_config.heroscript" -new -dev
echo "Hero docusaurus command finished. Check for errors or dev server output."

View File

@@ -0,0 +1,23 @@
#!/usr/bin/env -S v -n -w -cg -gc none -cc tcc -d use_openssl -enable-globals run
import freeflowuniverse.herolib.data.doctree
mut tree := doctree.new(name: 'test')!
// path string
// heal bool = true // healing means we fix images
// git_url string
// git_reset bool
// git_root string
// git_pull bool
tree.scan(
git_url: 'https://git.threefold.info/tfgrid/docs_tfgrid4/src/branch/main/collections'
git_pull: false
)!
tree.export(
destination: '/tmp/mdexport'
reset: true
exclude_errors: false
)!

10
examples/web/siteconfig.vsh Executable file
View File

@@ -0,0 +1,10 @@
#!/usr/bin/env -S v -n -w -gc none -cg -cc tcc -d use_openssl -enable-globals run
import freeflowuniverse.herolib.web.siteconfig
import os
mypath := '${os.dir(@FILE)}/siteconfigexample'
mut sc := siteconfig.new(mypath)!
println(sc)

View File

@@ -0,0 +1,100 @@
!!site.config
name:"depin"
description:"ThreeFold is laying the foundation for a geo aware Web 4, the next generation of the Internet."
tagline:"Geo Aware Internet Platform"
favicon:"img/favicon.png"
image:"img/tf_graph.png"
copyright:"ThreeFold"
!!site.menu
title:"ThreeFold DePIN Tech"
logo_alt:"ThreeFold Logo"
logo_src:"img/logo.svg"
logo_src_dark:"img/new_logo_tft.png"
!!site.menu_item
label:"ThreeFold.io"
href:"https://threefold.io"
position:"right"
!!site.menu_item
label:"Mycelium Network"
href:"https://mycelium.threefold.io/"
position:"right"
!!site.menu_item
label:"AI Box"
href:"https://aibox.threefold.io/"
position:"right"
!!site.footer
style:"dark"
!!site.footer_item
title:"Docs"
label:"Introduction"
href:"https://docs.threefold.io/docs/introduction"
!!site.footer_item
title:"Docs"
label:"Litepaper"
href:"https://docs.threefold.io/docs/litepaper/"
!!site.footer_item
title:"Docs"
label:"Roadmap"
href:"https://docs.threefold.io/docs/roadmap"
!!site.footer_item
title:"Docs"
label:"Manual"
href:"https://manual.grid.tf/"
!!site.footer_item
title:"Features"
label:"Become a Farmer"
href:"https://docs.threefold.io/docs/category/become-a-farmer"
!!site.footer_item
title:"Features"
label:"Components"
href:"https://docs.threefold.io/docs/category/components"
!!site.footer_item
title:"Features"
label:"Technology"
href:"https://threefold.info/tech/"
!!site.footer_item
title:"Features"
label:"Tokenomics"
href:"https://docs.threefold.io/docs/tokens/tokenomics"
!!site.footer_item
title:"Web"
label:"ThreeFold.io"
href:"https://threefold.io"
!!site.footer_item
title:"Web"
label:"Dashboard"
href:"https://dashboard.grid.tf"
!!site.footer_item
title:"Web"
label:"GitHub"
href:"https://github.com/threefoldtech/home"
!!site.footer_item
title:"Web"
label:"Mycelium Network"
href:"https://mycelium.threefold.io/"
!!site.footer_item
title:"Web"
label:"AI Box"
href:"https://www2.aibox.threefold.io/"
!!site.collections
url:"https://github.com/example/external-docs"
replace:"PROJECT_NAME:My Project, VERSION:1.0.0"

View File

@@ -0,0 +1,18 @@
!!site.page name:intro
description:"ThreeFold is laying the foundation for a geo aware Web 4, the next generation of the Internet."
//next is example where we use all properties, folder is where the page is located, prio is the order of the page, if not used the filled in from order in which we parse this config file
!!site.page name:mycelium draft:true folder:"/specs/components" prio:4
content:"the page content itself, only for small pages"
title:"Mycelium as Title"
description:"..."
!!site.page name:fungistor folder:"/specs/components" prio:1
src:"mycollection:mycelium.md"
title:"fungistor as Title"
description:"...."
!!site.page name:fungistor folder:"/specs/components" prio:1
src:"mycollection:mycelium" //can be without .md
title:"fungistor as Title"
description:"..."

View File

@@ -10,7 +10,7 @@ mut docs := starlight.new(
// Create a new starlight site // Create a new starlight site
mut site := docs.get( mut site := docs.get(
url: 'https://git.ourworld.tf/tfgrid/docs_aibox' url: 'https://git.threefold.info/tfgrid/docs_aibox'
init: true // init means we put config files if not there init: true // init means we put config files if not there
)! )!

View File

@@ -1,26 +0,0 @@
#!/usr/bin/env -S v -n -w -cg -gc none -cc tcc -d use_openssl -enable-globals run
import freeflowuniverse.herolib.data.doctree
mut tree := doctree.new(name: 'test')!
// path string
// heal bool = true // healing means we fix images
// git_url string
// git_reset bool
// git_root string
// git_pull bool
// load bool = true // means we scan automatically the added collection
for project in 'projectinca, legal, why'.split(',').map(it.trim_space()) {
tree.scan(
git_url: 'https://git.ourworld.tf/tfgrid/info_tfgrid/src/branch/development/collections/${project}'
git_pull: false
)!
}
tree.export(
destination: '/tmp/mdexport'
reset: true
// keep_structure: true
exclude_errors: false
)!

View File

@@ -4,7 +4,7 @@ set -e
os_name="$(uname -s)" os_name="$(uname -s)"
arch_name="$(uname -m)" arch_name="$(uname -m)"
version='1.0.25' version='1.0.26'
# Base URL for GitHub releases # Base URL for GitHub releases

View File

@@ -88,9 +88,9 @@ function sshknownkeysadd {
then then
ssh-keyscan github.com >> ~/.ssh/known_hosts ssh-keyscan github.com >> ~/.ssh/known_hosts
fi fi
if ! grep git.ourworld.tf ~/.ssh/known_hosts > /dev/null if ! grep git.threefold.info ~/.ssh/known_hosts > /dev/null
then then
ssh-keyscan git.ourworld.tf >> ~/.ssh/known_hosts ssh-keyscan git.threefold.info >> ~/.ssh/known_hosts
fi fi
git config --global pull.rebase false git config --global pull.rebase false

View File

@@ -60,7 +60,7 @@ fn main() {
} }
// Read the rhaiwrapping.md file // Read the rhaiwrapping.md file
rhai_wrapping_md := os.read_file('/Users/timurgordon/code/git.ourworld.tf/herocode/sal/aiprompts/rhaiwrapping.md') or { rhai_wrapping_md := os.read_file('/Users/timurgordon/code/git.threefold.info/herocode/sal/aiprompts/rhaiwrapping.md') or {
println('Failed to read rhaiwrapping.md: ${err}') println('Failed to read rhaiwrapping.md: ${err}')
return return
} }
@@ -140,7 +140,7 @@ fn main() {
println('Task completed successfully') println('Task completed successfully')
println('The wrapper files have been generated and compiled in the target directory.') println('The wrapper files have been generated and compiled in the target directory.')
println('Check /Users/timurgordon/code/git.ourworld.tf/herocode/sal/src/rhai for the compiled output.') println('Check /Users/timurgordon/code/git.threefold.info/herocode/sal/src/rhai for the compiled output.')
} }
// Define the prompt functions // Define the prompt functions
@@ -158,17 +158,17 @@ fn create_example(input string) string {
// Define a Rhai wrapper generator function for Container functions // Define a Rhai wrapper generator function for Container functions
fn create_rhai_wrappers(name string, source_code string, example_rhai string, wrapper_md string, errors_md string, crate_path string) string { fn create_rhai_wrappers(name string, source_code string, example_rhai string, wrapper_md string, errors_md string, crate_path string) string {
guides := os.read_file('/Users/timurgordon/code/git.ourworld.tf/herocode/sal/aiprompts/rhaiwrapping_classicai.md') or { guides := os.read_file('/Users/timurgordon/code/git.threefold.info/herocode/sal/aiprompts/rhaiwrapping_classicai.md') or {
panic('Failed to read guides') panic('Failed to read guides')
} }
engine := $tmpl('./prompts/engine.md') engine := $tmpl('./prompts/engine.md')
vector_vs_array := os.read_file('/Users/timurgordon/code/git.ourworld.tf/herocode/sal/aiprompts/rhai_array_vs_vector.md') or { vector_vs_array := os.read_file('/Users/timurgordon/code/git.threefold.info/herocode/sal/aiprompts/rhai_array_vs_vector.md') or {
panic('Failed to read guides') panic('Failed to read guides')
} }
rhai_integration_fixes := os.read_file('/Users/timurgordon/code/git.ourworld.tf/herocode/sal/aiprompts/rhai_integration_fixes.md') or { rhai_integration_fixes := os.read_file('/Users/timurgordon/code/git.threefold.info/herocode/sal/aiprompts/rhai_integration_fixes.md') or {
panic('Failed to read guides') panic('Failed to read guides')
} }
rhai_syntax_guide := os.read_file('/Users/timurgordon/code/git.ourworld.tf/herocode/sal/aiprompts/rhai_syntax_guide.md') or { rhai_syntax_guide := os.read_file('/Users/timurgordon/code/git.threefold.info/herocode/sal/aiprompts/rhai_syntax_guide.md') or {
panic('Failed to read guides') panic('Failed to read guides')
} }
generic_wrapper_rs := $tmpl('./templates/generic_wrapper.rs') generic_wrapper_rs := $tmpl('./templates/generic_wrapper.rs')

View File

@@ -47,7 +47,7 @@ fn main() {
println('Task completed successfully') println('Task completed successfully')
println('The wrapper files have been generated and compiled in the target directory.') println('The wrapper files have been generated and compiled in the target directory.')
println('Check /Users/timurgordon/code/git.ourworld.tf/herocode/sal/src/rhai for the compiled output.') println('Check /Users/timurgordon/code/git.threefold.info/herocode/sal/src/rhai for the compiled output.')
} }
// Validates command line arguments and returns the source code path // Validates command line arguments and returns the source code path
@@ -175,11 +175,11 @@ fn run_wrapper_generation_task(prompt_content string, gen RhaiGen) !string {
// Define a Rhai wrapper generator function for Container functions // Define a Rhai wrapper generator function for Container functions
fn create_rhai_wrappers(name string, source_code string, example_rhai string, wrapper_md string, errors_md string, crate_path string) string { fn create_rhai_wrappers(name string, source_code string, example_rhai string, wrapper_md string, errors_md string, crate_path string) string {
// Load all required template and guide files // Load all required template and guide files
guides := load_guide_file('/Users/timurgordon/code/git.ourworld.tf/herocode/sal/aiprompts/rhaiwrapping_classicai.md') guides := load_guide_file('/Users/timurgordon/code/git.threefold.info/herocode/sal/aiprompts/rhaiwrapping_classicai.md')
engine := $tmpl('./prompts/engine.md') engine := $tmpl('./prompts/engine.md')
vector_vs_array := load_guide_file('/Users/timurgordon/code/git.ourworld.tf/herocode/sal/aiprompts/rhai_array_vs_vector.md') vector_vs_array := load_guide_file('/Users/timurgordon/code/git.threefold.info/herocode/sal/aiprompts/rhai_array_vs_vector.md')
rhai_integration_fixes := load_guide_file('/Users/timurgordon/code/git.ourworld.tf/herocode/sal/aiprompts/rhai_integration_fixes.md') rhai_integration_fixes := load_guide_file('/Users/timurgordon/code/git.threefold.info/herocode/sal/aiprompts/rhai_integration_fixes.md')
rhai_syntax_guide := load_guide_file('/Users/timurgordon/code/git.ourworld.tf/herocode/sal/aiprompts/rhai_syntax_guide.md') rhai_syntax_guide := load_guide_file('/Users/timurgordon/code/git.threefold.info/herocode/sal/aiprompts/rhai_syntax_guide.md')
generic_wrapper_rs := $tmpl('./templates/generic_wrapper.rs') generic_wrapper_rs := $tmpl('./templates/generic_wrapper.rs')
// Build the prompt content // Build the prompt content

View File

@@ -67,11 +67,11 @@ pub fn rhai_wrapper_generation_prompt(name string, source_code string, source_pk
errors_md := os.read_file('${current_dir}/prompts/errors.md')! errors_md := os.read_file('${current_dir}/prompts/errors.md')!
// Load all required template and guide files // Load all required template and guide files
guides := os.read_file('/Users/timurgordon/code/git.ourworld.tf/herocode/sal/aiprompts/rhaiwrapping_classicai.md')! guides := os.read_file('/Users/timurgordon/code/git.threefold.info/herocode/sal/aiprompts/rhaiwrapping_classicai.md')!
engine := $tmpl('./prompts/engine.md') engine := $tmpl('./prompts/engine.md')
vector_vs_array := os.read_file('/Users/timurgordon/code/git.ourworld.tf/herocode/sal/aiprompts/rhai_array_vs_vector.md')! vector_vs_array := os.read_file('/Users/timurgordon/code/git.threefold.info/herocode/sal/aiprompts/rhai_array_vs_vector.md')!
rhai_integration_fixes := os.read_file('/Users/timurgordon/code/git.ourworld.tf/herocode/sal/aiprompts/rhai_integration_fixes.md')! rhai_integration_fixes := os.read_file('/Users/timurgordon/code/git.threefold.info/herocode/sal/aiprompts/rhai_integration_fixes.md')!
rhai_syntax_guide := os.read_file('/Users/timurgordon/code/git.ourworld.tf/herocode/sal/aiprompts/rhai_syntax_guide.md')! rhai_syntax_guide := os.read_file('/Users/timurgordon/code/git.threefold.info/herocode/sal/aiprompts/rhai_syntax_guide.md')!
generic_wrapper_rs := $tmpl('./templates/generic_wrapper.rs') generic_wrapper_rs := $tmpl('./templates/generic_wrapper.rs')
prompt := $tmpl('./prompts/main.md') prompt := $tmpl('./prompts/main.md')

View File

@@ -1,4 +1,4 @@
// File: /root/code/git.ourworld.tf/herocode/sal/src/virt/nerdctl/container_builder.rs // File: /root/code/git.threefold.info/herocode/sal/src/virt/nerdctl/container_builder.rs
use std::collections::HashMap; use std::collections::HashMap;
use crate::virt::nerdctl::{execute_nerdctl_command, NerdctlError}; use crate::virt::nerdctl::{execute_nerdctl_command, NerdctlError};

View File

@@ -71,7 +71,7 @@ pub fn (r Report) export(export Export) ! {
path: export.path path: export.path
publish_path: export.path publish_path: export.path
init: true init: true
config: docusaurus.Config{ config: docusaurus.Configuration{
navbar: docusaurus.Navbar{ navbar: docusaurus.Navbar{
title: 'Business Model' title: 'Business Model'
items: [ items: [

View File

@@ -43,7 +43,7 @@ client.send(subject:'this is a test',to:'kristof@incubaid.com',body:'
if you have a secrets file you could import as if you have a secrets file you could import as
```bash ```bash
//e.g. source ~/code/git.ourworld.tf/despiegk/hero_secrets/mysecrets.sh //e.g. source ~/code/git.threefold.info/despiegk/hero_secrets/mysecrets.sh
``` ```
following env variables are supported following env variables are supported

View File

@@ -0,0 +1,8 @@
!!hero_code.generate_client
name:'mycelium_rpc'
classname:'MyceliumRPC'
singleton:1
default:0
hasconfig:1
reset:0

View File

@@ -0,0 +1,337 @@
module mycelium_rpc
import freeflowuniverse.herolib.schemas.jsonrpc
import freeflowuniverse.herolib.core.httpconnection
import encoding.base64
// Helper function to get or create the RPC client
fn (mut c MyceliumRPC) get_client() !&jsonrpc.Client {
if client := c.rpc_client {
return client
}
// Create HTTP transport using httpconnection
mut http_conn := httpconnection.new(
name: 'mycelium_rpc_${c.name}'
url: c.url
)!
// Create a simple HTTP transport wrapper
transport := HTTPTransport{
http_conn: http_conn
}
mut client := jsonrpc.new_client(transport)
c.rpc_client = client
return client
}
// HTTPTransport implements IRPCTransportClient for HTTP connections
struct HTTPTransport {
mut:
http_conn &httpconnection.HTTPConnection
}
// send implements the IRPCTransportClient interface
fn (mut t HTTPTransport) send(request string, params jsonrpc.SendParams) !string {
req := httpconnection.Request{
method: .post
prefix: '/'
dataformat: .json
data: request
}
response := t.http_conn.post_json_str(req)!
return response
}
// Admin methods
// get_info gets general info about the node
pub fn (mut c MyceliumRPC) get_info() !Info {
mut client := c.get_client()!
request := jsonrpc.new_request_generic('getInfo', []string{})
return client.send[[]string, Info](request)!
}
// get_peers lists known peers
pub fn (mut c MyceliumRPC) get_peers() ![]PeerStats {
mut client := c.get_client()!
request := jsonrpc.new_request_generic('getPeers', []string{})
return client.send[[]string, []PeerStats](request)!
}
// add_peer adds a new peer identified by the provided endpoint
pub fn (mut c MyceliumRPC) add_peer(endpoint string) !bool {
mut client := c.get_client()!
params := {
'endpoint': endpoint
}
request := jsonrpc.new_request_generic('addPeer', params)
return client.send[map[string]string, bool](request)!
}
// delete_peer removes an existing peer identified by the provided endpoint
pub fn (mut c MyceliumRPC) delete_peer(endpoint string) !bool {
mut client := c.get_client()!
params := {
'endpoint': endpoint
}
request := jsonrpc.new_request_generic('deletePeer', params)
return client.send[map[string]string, bool](request)!
}
// Route methods
// get_selected_routes lists all selected routes
pub fn (mut c MyceliumRPC) get_selected_routes() ![]Route {
mut client := c.get_client()!
request := jsonrpc.new_request_generic('getSelectedRoutes', []string{})
return client.send[[]string, []Route](request)!
}
// get_fallback_routes lists all active fallback routes
pub fn (mut c MyceliumRPC) get_fallback_routes() ![]Route {
mut client := c.get_client()!
request := jsonrpc.new_request_generic('getFallbackRoutes', []string{})
return client.send[[]string, []Route](request)!
}
// get_queried_subnets lists all currently queried subnets
pub fn (mut c MyceliumRPC) get_queried_subnets() ![]QueriedSubnet {
mut client := c.get_client()!
request := jsonrpc.new_request_generic('getQueriedSubnets', []string{})
return client.send[[]string, []QueriedSubnet](request)!
}
// get_no_route_entries lists all subnets which are explicitly marked as no route
pub fn (mut c MyceliumRPC) get_no_route_entries() ![]NoRouteSubnet {
mut client := c.get_client()!
request := jsonrpc.new_request_generic('getNoRouteEntries', []string{})
return client.send[[]string, []NoRouteSubnet](request)!
}
// get_public_key_from_ip gets the pubkey from node ip
pub fn (mut c MyceliumRPC) get_public_key_from_ip(mycelium_ip string) !PublicKeyResponse {
mut client := c.get_client()!
params := {
'mycelium_ip': mycelium_ip
}
request := jsonrpc.new_request_generic('getPublicKeyFromIp', params)
return client.send[map[string]string, PublicKeyResponse](request)!
}
// Message methods
// PopMessageParams represents parameters for pop_message method
pub struct PopMessageParams {
pub mut:
peek ?bool // Whether to peek the message or not
timeout ?i64 // Amount of seconds to wait for a message
topic ?string // Optional filter for loading messages
}
// pop_message gets a message from the inbound message queue
pub fn (mut c MyceliumRPC) pop_message(peek bool, timeout i64, topic string) !InboundMessage {
mut client := c.get_client()!
mut params := PopMessageParams{}
if peek {
params.peek = peek
}
if timeout > 0 {
params.timeout = timeout
}
if topic != '' {
// Encode topic as base64 as required by mycelium
params.topic = base64.encode_str(topic)
}
request := jsonrpc.new_request_generic('popMessage', params)
return client.send[PopMessageParams, InboundMessage](request)!
}
// PushMessageParams represents parameters for push_message method
pub struct PushMessageParams {
pub mut:
message PushMessageBody // The message to send
reply_timeout ?i64 // Amount of seconds to wait for a reply
}
// push_message submits a new message to the system
pub fn (mut c MyceliumRPC) push_message(message PushMessageBody, reply_timeout i64) !string {
mut client := c.get_client()!
mut params := PushMessageParams{
message: message
}
if reply_timeout > 0 {
params.reply_timeout = reply_timeout
}
request := jsonrpc.new_request_generic('pushMessage', params)
// The response can be either InboundMessage or PushMessageResponseId
// For simplicity, we'll return the raw JSON response as string
return client.send[PushMessageParams, string](request)!
}
// PushMessageReplyParams represents parameters for push_message_reply method
pub struct PushMessageReplyParams {
pub mut:
id string // The ID of the message to reply to
message PushMessageBody // The reply message
}
// push_message_reply replies to a message with the given ID
pub fn (mut c MyceliumRPC) push_message_reply(id string, message PushMessageBody) !bool {
mut client := c.get_client()!
params := PushMessageReplyParams{
id: id
message: message
}
request := jsonrpc.new_request_generic('pushMessageReply', params)
return client.send[PushMessageReplyParams, bool](request)!
}
// get_message_info gets the status of an outbound message
pub fn (mut c MyceliumRPC) get_message_info(id string) !MessageStatusResponse {
mut client := c.get_client()!
params := {
'id': id
}
request := jsonrpc.new_request_generic('getMessageInfo', params)
return client.send[map[string]string, MessageStatusResponse](request)!
}
// Topic management methods
// get_default_topic_action gets the default topic action
pub fn (mut c MyceliumRPC) get_default_topic_action() !bool {
mut client := c.get_client()!
request := jsonrpc.new_request_generic('getDefaultTopicAction', []string{})
return client.send[[]string, bool](request)!
}
// SetDefaultTopicActionParams represents parameters for set_default_topic_action method
pub struct SetDefaultTopicActionParams {
pub mut:
accept bool // Whether to accept unconfigured topics by default
}
// set_default_topic_action sets the default topic action
pub fn (mut c MyceliumRPC) set_default_topic_action(accept bool) !bool {
mut client := c.get_client()!
params := SetDefaultTopicActionParams{
accept: accept
}
request := jsonrpc.new_request_generic('setDefaultTopicAction', params)
return client.send[SetDefaultTopicActionParams, bool](request)!
}
// get_topics gets all configured topics
pub fn (mut c MyceliumRPC) get_topics() ![]string {
mut client := c.get_client()!
request := jsonrpc.new_request_generic('getTopics', []string{})
encoded_topics := client.send[[]string, []string](request)!
// Decode base64-encoded topics for user convenience
mut decoded_topics := []string{}
for encoded_topic in encoded_topics {
decoded_topic := base64.decode_str(encoded_topic)
decoded_topics << decoded_topic
}
return decoded_topics
}
// add_topic adds a new topic to the system's whitelist
pub fn (mut c MyceliumRPC) add_topic(topic string) !bool {
mut client := c.get_client()!
// Encode topic as base64 as required by mycelium
encoded_topic := base64.encode_str(topic)
params := {
'topic': encoded_topic
}
request := jsonrpc.new_request_generic('addTopic', params)
return client.send[map[string]string, bool](request)!
}
// remove_topic removes a topic from the system's whitelist
pub fn (mut c MyceliumRPC) remove_topic(topic string) !bool {
mut client := c.get_client()!
// Encode topic as base64 as required by mycelium
encoded_topic := base64.encode_str(topic)
params := {
'topic': encoded_topic
}
request := jsonrpc.new_request_generic('removeTopic', params)
return client.send[map[string]string, bool](request)!
}
// get_topic_sources gets all sources (subnets) that are allowed to send messages for a specific topic
pub fn (mut c MyceliumRPC) get_topic_sources(topic string) ![]string {
mut client := c.get_client()!
// Encode topic as base64 as required by mycelium
encoded_topic := base64.encode_str(topic)
params := {
'topic': encoded_topic
}
request := jsonrpc.new_request_generic('getTopicSources', params)
return client.send[map[string]string, []string](request)!
}
// add_topic_source adds a source (subnet) that is allowed to send messages for a specific topic
pub fn (mut c MyceliumRPC) add_topic_source(topic string, subnet string) !bool {
mut client := c.get_client()!
// Encode topic as base64 as required by mycelium
encoded_topic := base64.encode_str(topic)
params := {
'topic': encoded_topic
'subnet': subnet
}
request := jsonrpc.new_request_generic('addTopicSource', params)
return client.send[map[string]string, bool](request)!
}
// remove_topic_source removes a source (subnet) that is allowed to send messages for a specific topic
pub fn (mut c MyceliumRPC) remove_topic_source(topic string, subnet string) !bool {
mut client := c.get_client()!
// Encode topic as base64 as required by mycelium
encoded_topic := base64.encode_str(topic)
params := {
'topic': encoded_topic
'subnet': subnet
}
request := jsonrpc.new_request_generic('removeTopicSource', params)
return client.send[map[string]string, bool](request)!
}
// get_topic_forward_socket gets the forward socket for a topic
pub fn (mut c MyceliumRPC) get_topic_forward_socket(topic string) !string {
mut client := c.get_client()!
// Encode topic as base64 as required by mycelium
encoded_topic := base64.encode_str(topic)
params := {
'topic': encoded_topic
}
request := jsonrpc.new_request_generic('getTopicForwardSocket', params)
return client.send[map[string]string, string](request)!
}
// set_topic_forward_socket sets the socket path where messages for a specific topic should be forwarded to
pub fn (mut c MyceliumRPC) set_topic_forward_socket(topic string, socket_path string) !bool {
mut client := c.get_client()!
// Encode topic as base64 as required by mycelium
encoded_topic := base64.encode_str(topic)
params := {
'topic': encoded_topic
'socket_path': socket_path
}
request := jsonrpc.new_request_generic('setTopicForwardSocket', params)
return client.send[map[string]string, bool](request)!
}
// remove_topic_forward_socket removes the socket path where messages for a specific topic are forwarded to
pub fn (mut c MyceliumRPC) remove_topic_forward_socket(topic string) !bool {
mut client := c.get_client()!
// Encode topic as base64 as required by mycelium
encoded_topic := base64.encode_str(topic)
params := {
'topic': encoded_topic
}
request := jsonrpc.new_request_generic('removeTopicForwardSocket', params)
return client.send[map[string]string, bool](request)!
}

View File

@@ -0,0 +1,114 @@
module mycelium_rpc
import freeflowuniverse.herolib.core.base
import freeflowuniverse.herolib.core.playbook
import freeflowuniverse.herolib.ui.console
__global (
mycelium_rpc_global map[string]&MyceliumRPC
mycelium_rpc_default string
)
/////////FACTORY
@[params]
pub struct ArgsGet {
pub mut:
name string
}
fn args_get(args_ ArgsGet) ArgsGet {
mut args := args_
if args.name == '' {
args.name = 'default'
}
return args
}
pub fn get(args_ ArgsGet) !&MyceliumRPC {
mut context := base.context()!
mut args := args_get(args_)
mut obj := MyceliumRPC{
name: args.name
}
if args.name !in mycelium_rpc_global {
if !exists(args)! {
set(obj)!
} else {
heroscript := context.hero_config_get('mycelium_rpc', args.name)!
mut obj_ := heroscript_loads(heroscript)!
set_in_mem(obj_)!
}
}
return mycelium_rpc_global[args.name] or {
println(mycelium_rpc_global)
// bug if we get here because should be in globals
panic('could not get config for mycelium_rpc with name, is bug:${args.name}')
}
}
// register the config for the future
pub fn set(o MyceliumRPC) ! {
set_in_mem(o)!
mut context := base.context()!
heroscript := heroscript_dumps(o)!
context.hero_config_set('mycelium_rpc', o.name, heroscript)!
}
// does the config exists?
pub fn exists(args_ ArgsGet) !bool {
mut context := base.context()!
mut args := args_get(args_)
return context.hero_config_exists('mycelium_rpc', args.name)
}
pub fn delete(args_ ArgsGet) ! {
mut args := args_get(args_)
mut context := base.context()!
context.hero_config_delete('mycelium_rpc', args.name)!
if args.name in mycelium_rpc_global {
// del mycelium_rpc_global[args.name]
}
}
// only sets in mem, does not set as config
fn set_in_mem(o MyceliumRPC) ! {
mut o2 := obj_init(o)!
mycelium_rpc_global[o.name] = &o2
mycelium_rpc_default = o.name
}
@[params]
pub struct PlayArgs {
pub mut:
heroscript string // if filled in then plbook will be made out of it
plbook ?playbook.PlayBook
reset bool
}
pub fn play(args_ PlayArgs) ! {
mut args := args_
mut plbook := args.plbook or { playbook.new(text: args.heroscript)! }
mut install_actions := plbook.find(filter: 'mycelium_rpc.configure')!
if install_actions.len > 0 {
for install_action in install_actions {
heroscript := install_action.heroscript()
mut obj2 := heroscript_loads(heroscript)!
set(obj2)!
}
}
}
// switch instance to be used for mycelium_rpc
pub fn switch(name string) {
mycelium_rpc_default = name
}
// helpers
@[params]
pub struct DefaultConfigArgs {
instance string = 'default'
}

View File

@@ -0,0 +1,158 @@
module mycelium_rpc
import freeflowuniverse.herolib.data.encoderhero
import freeflowuniverse.herolib.schemas.jsonrpc
pub const version = '0.0.0'
const singleton = true
const default = false
// Default configuration for Mycelium JSON-RPC API
pub const default_url = 'http://localhost:8990'
// THIS THE THE SOURCE OF THE INFORMATION OF THIS FILE, HERE WE HAVE THE CONFIG OBJECT CONFIGURED AND MODELLED
@[heap]
pub struct MyceliumRPC {
pub mut:
name string = 'default'
url string = default_url // RPC server URL
rpc_client ?&jsonrpc.Client @[skip]
}
// your checking & initialization code if needed
fn obj_init(mycfg_ MyceliumRPC) !MyceliumRPC {
mut mycfg := mycfg_
if mycfg.url == '' {
mycfg.url = default_url
}
// For now, we'll initialize the client when needed
// The actual client will be created in the factory
return mycfg
}
// Response structs based on OpenRPC specification
// Info represents general information about a node
pub struct Info {
pub mut:
node_subnet string @[json: 'nodeSubnet'] // The subnet owned by the node and advertised to peers
node_pubkey string @[json: 'nodePubkey'] // The public key of the node (hex encoded, 64 chars)
}
// Endpoint represents identification to connect to a peer
pub struct Endpoint {
pub mut:
proto string @[json: 'proto'] // Protocol used (tcp, quic)
socket_addr string @[json: 'socketAddr'] // The socket address used
}
// PeerStats represents info about a peer
pub struct PeerStats {
pub mut:
endpoint Endpoint @[json: 'endpoint'] // Peer endpoint
peer_type string @[json: 'type'] // How we know about this peer (static, inbound, linkLocalDiscovery)
connection_state string @[json: 'connectionState'] // Current state of connection (alive, connecting, dead)
tx_bytes i64 @[json: 'txBytes'] // Bytes transmitted to this peer
rx_bytes i64 @[json: 'rxBytes'] // Bytes received from this peer
}
// Route represents information about a route
pub struct Route {
pub mut:
subnet string @[json: 'subnet'] // The overlay subnet for which this is the route
next_hop string @[json: 'nextHop'] // Way to identify the next hop of the route
metric string @[json: 'metric'] // The metric of the route (can be int or "infinite")
seqno int @[json: 'seqno'] // Sequence number advertised with this route
}
// QueriedSubnet represents information about a subnet currently being queried
pub struct QueriedSubnet {
pub mut:
subnet string // The overlay subnet which we are currently querying
expiration string // Amount of seconds until the query expires
}
// NoRouteSubnet represents information about a subnet marked as no route
pub struct NoRouteSubnet {
pub mut:
subnet string // The overlay subnet which is marked
expiration string // Amount of seconds until the entry expires
}
// InboundMessage represents a message received by the system
pub struct InboundMessage {
pub mut:
id string @[json: 'id'] // Id of the message, hex encoded (16 chars)
src_ip string @[json: 'srcIp'] // Sender overlay IP address (IPv6)
src_pk string @[json: 'srcPk'] // Sender public key, hex encoded (64 chars)
dst_ip string @[json: 'dstIp'] // Receiver overlay IP address (IPv6)
dst_pk string @[json: 'dstPk'] // Receiver public key, hex encoded (64 chars)
topic string @[json: 'topic'] // Optional message topic (base64 encoded, 0-340 chars)
payload string @[json: 'payload'] // Message payload, base64 encoded
}
// MessageDestination represents the destination for a message
pub struct MessageDestination {
pub mut:
ip string // Target IP of the message (IPv6)
pk string // Hex encoded public key of the target node (64 chars)
}
// PushMessageBody represents a message to send to a given receiver
pub struct PushMessageBody {
pub mut:
dst MessageDestination // Message destination
topic string // Optional message topic (base64 encoded, 0-340 chars)
payload string // Message to send, base64 encoded
}
// PushMessageResponseId represents the ID generated for a message after pushing
pub struct PushMessageResponseId {
pub mut:
id string // Id of the message, hex encoded (16 chars)
}
// MessageStatusResponse represents information about an outbound message
pub struct MessageStatusResponse {
pub mut:
dst string @[json: 'dst'] // IP address of the receiving node (IPv6)
state string @[json: 'state'] // Transmission state
created i64 @[json: 'created'] // Unix timestamp of when this message was created
deadline i64 @[json: 'deadline'] // Unix timestamp of when this message will expire
msg_len int @[json: 'msgLen'] // Length of the message in bytes
}
// PublicKeyResponse represents public key requested based on a node's IP
pub struct PublicKeyResponse {
pub mut:
node_pub_key string @[json: 'NodePubKey'] // Public key (hex encoded, 64 chars)
}
/////////////NORMALLY NO NEED TO TOUCH
pub fn heroscript_dumps(obj MyceliumRPC) !string {
return encoderhero.encode[MyceliumRPC](obj)!
}
pub fn heroscript_loads(heroscript string) !MyceliumRPC {
mut obj := encoderhero.decode[MyceliumRPC](heroscript)!
return obj
}
// Factory function to create a new MyceliumRPC client instance
@[params]
pub struct NewClientArgs {
pub mut:
name string = 'default'
url string = default_url
}
pub fn new_client(args NewClientArgs) !&MyceliumRPC {
mut client := MyceliumRPC{
name: args.name
url: args.url
}
client = obj_init(client)!
return &client
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,180 @@
# Mycelium RPC Client
This is a V language client for the Mycelium mesh networking system, implementing the JSON-RPC API specification for administrative operations.
## Overview
Mycelium is a mesh networking system that creates secure, encrypted connections between nodes. This client provides a comprehensive API to interact with Mycelium nodes via their JSON-RPC interface for administrative tasks such as:
- Node information retrieval
- Peer management
- Routing information
- Message operations
- Topic management
## Features
- Complete implementation of all methods in the Mycelium JSON-RPC specification
- Type-safe API with proper error handling
- HTTP transport support
- Comprehensive documentation
- Example code for all operations
## Usage
### Basic Example
```v
import freeflowuniverse.herolib.clients.mycelium_rpc
// Create a new client
mut client := mycelium_rpc.new_client(
name: 'my_client'
url: 'http://localhost:8990'
)!
// Get node information
info := client.get_info()!
println('Node Subnet: ${info.node_subnet}')
println('Node Public Key: ${info.node_pubkey}')
// List peers
peers := client.get_peers()!
for peer in peers {
println('Peer: ${peer.endpoint.proto}://${peer.endpoint.socket_addr}')
println('State: ${peer.connection_state}')
}
```
### Configuration
The client can be configured with:
- `name`: Client instance name (default: 'default')
- `url`: Mycelium node API URL (default: 'http://localhost:8990')
### Available Methods
#### Admin Methods
- `get_info()` - Get general information about the node
- `get_peers()` - List known peers
- `add_peer(endpoint)` - Add a new peer
- `delete_peer(endpoint)` - Remove an existing peer
- `get_public_key_from_ip(ip)` - Get public key from node IP
#### Routing Methods
- `get_selected_routes()` - List all selected routes
- `get_fallback_routes()` - List all active fallback routes
- `get_queried_subnets()` - List currently queried subnets
- `get_no_route_entries()` - List subnets marked as no route
#### Message Methods
- `pop_message(peek, timeout, topic)` - Get message from inbound queue
- `push_message(message, reply_timeout)` - Submit new message to system
- `push_message_reply(id, message)` - Reply to a message
- `get_message_info(id)` - Get status of an outbound message
#### Topic Management Methods
- `get_default_topic_action()` - Get default topic action
- `set_default_topic_action(accept)` - Set default topic action
- `get_topics()` - Get all configured topics
- `add_topic(topic)` - Add new topic to whitelist
- `remove_topic(topic)` - Remove topic from whitelist
- `get_topic_sources(topic)` - Get sources for a topic
- `add_topic_source(topic, subnet)` - Add source to topic
- `remove_topic_source(topic, subnet)` - Remove source from topic
- `get_topic_forward_socket(topic)` - Get forward socket for topic
- `set_topic_forward_socket(topic, path)` - Set forward socket for topic
- `remove_topic_forward_socket(topic)` - Remove forward socket for topic
## Data Types
### Info
```v
struct Info {
node_subnet string // The subnet owned by the node
node_pubkey string // The public key of the node (hex encoded)
}
```
### PeerStats
```v
struct PeerStats {
endpoint Endpoint // Peer endpoint
peer_type string // How we know about this peer
connection_state string // Current state of connection
tx_bytes i64 // Bytes transmitted to this peer
rx_bytes i64 // Bytes received from this peer
}
```
### InboundMessage
```v
struct InboundMessage {
id string // Message ID (hex encoded)
src_ip string // Sender overlay IP address
src_pk string // Sender public key (hex encoded)
dst_ip string // Receiver overlay IP address
dst_pk string // Receiver public key (hex encoded)
topic string // Optional message topic (base64 encoded)
payload string // Message payload (base64 encoded)
}
```
## Examples
See `examples/clients/mycelium_rpc.vsh` for a comprehensive example that demonstrates:
- Node information retrieval
- Peer listing and management
- Routing information
- Topic management
- Message operations
- Error handling
## Requirements
- V language compiler
- Mycelium node running with API enabled
- Network connectivity to the Mycelium node
## Running the Example
```bash
# Make sure Mycelium is installed
v run examples/clients/mycelium_rpc.vsh
```
The example will:
1. Install Mycelium if needed
2. Start a Mycelium node with API enabled
3. Demonstrate various RPC operations
4. Clean up resources on exit
## Error Handling
All methods return Result types and should be handled appropriately:
```v
info := client.get_info() or {
println('Error getting node info: ${err}')
return
}
```
## Notes
- The client uses HTTP transport to communicate with the Mycelium node
- All JSON field names are properly mapped using V's `@[json: 'field_name']` attributes
- The client is thread-safe and can be used concurrently
- Message operations require multiple connected Mycelium nodes to be meaningful
## License
This client follows the same license as the HeroLib project.

View File

@@ -2,7 +2,7 @@ module openai
import freeflowuniverse.herolib.core.base import freeflowuniverse.herolib.core.base
import freeflowuniverse.herolib.core.playbook import freeflowuniverse.herolib.core.playbook
import freeflowuniverse.herolib.ui.console // import freeflowuniverse.herolib.ui.console
__global ( __global (
openai_global map[string]&OpenAI openai_global map[string]&OpenAI

152
lib/clients/zinit/README.md Normal file
View File

@@ -0,0 +1,152 @@
# Zinit OpenRPC Client
This is a V language client for the Zinit service manager, implementing the OpenRPC specification.
## Overview
Zinit is a service manager that allows you to manage and monitor services on your system. This client provides a comprehensive API to interact with Zinit via its JSON-RPC interface.
## Features
- Complete implementation of all methods in the Zinit OpenRPC specification
- Type-safe API with proper error handling
- Comprehensive documentation
- Helper functions for common operations
- Example code for all operations
## Usage
### Basic Example
```v
import freeflowuniverse.heroweb.clients.zinit
fn main() {
// Create a new client with the default socket path
mut client := zinit.new_default_client()
// List all services
services := client.service_list() or {
println('Error: ${err}')
return
}
// Print the services
for name, state in services {
println('${name}: ${state}')
}
// Get status of a specific service
if services.len > 0 {
service_name := services.keys()[0]
status := client.service_status(service_name) or {
println('Error: ${err}')
return
}
println('Service: ${status.name}')
println('State: ${status.state}')
println('PID: ${status.pid}')
}
}
```
### Creating and Managing Services
```v
import freeflowuniverse.heroweb.clients.zinit
fn main() {
mut client := zinit.new_default_client()
// Create a new service configuration
config := zinit.ServiceConfig{
exec: '/bin/echo "Hello, World!"'
oneshot: true
log: zinit.log_stdout
env: {
'ENV_VAR': 'value'
}
}
// Create the service
client.service_create('hello', config) or {
println('Error creating service: ${err}')
return
}
// Start the service
client.service_start('hello') or {
println('Error starting service: ${err}')
return
}
// Get the service logs
logs := client.stream_current_logs('hello') or {
println('Error getting logs: ${err}')
return
}
for log in logs {
println(log)
}
// Clean up
client.service_stop('hello') or {}
client.service_forget('hello') or {}
client.service_delete('hello') or {}
}
```
## API Reference
### Client Creation
- `new_client(socket_path string) &Client` - Create a new client with a custom socket path
- `new_default_client() &Client` - Create a new client with the default socket path (`/tmp/zinit.sock`)
### Service Management
- `service_list() !map[string]string` - List all services and their states
- `service_status(name string) !ServiceStatus` - Get detailed status of a service
- `service_start(name string) !` - Start a service
- `service_stop(name string) !` - Stop a service
- `service_monitor(name string) !` - Start monitoring a service
- `service_forget(name string) !` - Stop monitoring a service
- `service_kill(name string, signal string) !` - Send a signal to a service
- `service_create(name string, config ServiceConfig) !string` - Create a new service
- `service_delete(name string) !string` - Delete a service
- `service_get(name string) !ServiceConfig` - Get a service configuration
- `service_stats(name string) !ServiceStats` - Get memory and CPU usage statistics
### System Operations
- `system_shutdown() !` - Stop all services and power off the system
- `system_reboot() !` - Stop all services and reboot the system
- `system_start_http_server(address string) !string` - Start an HTTP/RPC server
- `system_stop_http_server() !` - Stop the HTTP/RPC server
### Logs
- `stream_current_logs(name ?string) ![]string` - Get current logs
- `stream_subscribe_logs(name ?string) !string` - Subscribe to log messages
## Constants
- `default_socket_path` - Default Unix socket path (`/tmp/zinit.sock`)
- `state_running`, `state_success`, `state_error`, etc. - Common service states
- `target_up`, `target_down` - Common service targets
- `log_null`, `log_ring`, `log_stdout` - Common log types
- `signal_term`, `signal_kill`, etc. - Common signals
## Helper Functions
- `new_service_config(exec string) ServiceConfig` - Create a basic service configuration
- `new_oneshot_service_config(exec string) ServiceConfig` - Create a oneshot service configuration
- `is_service_not_found_error(err IError) bool` - Check if an error is a "service not found" error
- `format_memory_usage(bytes i64) string` - Format memory usage in human-readable format
- `format_cpu_usage(cpu_percent f64) string` - Format CPU usage
## License
MIT

18
lib/clients/zinit/error.v Normal file
View File

@@ -0,0 +1,18 @@
module zinit
// Request Types for Zinit API
//
// This file contains all the request types used by the Zinit API.
// ZinitError represents an error returned by the zinit API
pub struct ZinitError {
pub mut:
code int // Error code
message string // Error message
data string // Additional error data
}
// Error implements the error interface for ZinitError
pub fn (e ZinitError) msg() string {
return 'Zinit Error ${e.code}: ${e.message} - ${e.data}'
}

View File

@@ -0,0 +1,23 @@
module zinit
import freeflowuniverse.herolib.schemas.jsonrpc
// Client is an OpenRPC client for Zinit
pub struct Client {
mut:
rpc_client &jsonrpc.Client
}
@[params]
pub struct ClientParams {
path string = '/tmp/zinit.sock' // Path to the Zinit RPC socket
}
// new_client creates a new Zinit RPC client with a custom socket path
pub fn new_client(args_ ClientParams) &Client {
mut args:=args_
mut cl := jsonrpc.new_unix_socket_client(args.path)
return &Client{
rpc_client: cl
}
}

87
lib/clients/zinit/model.v Normal file
View File

@@ -0,0 +1,87 @@
module zinit
// ServiceCreateResponse represents the response from service_create
pub struct ServiceCreateResponse {
pub mut:
path string // Path to the created service file
}
// ServiceDeleteResponse represents the response from service_delete
pub struct ServiceDeleteResponse {
pub mut:
result string // Result of the delete operation
}
// SystemStartHttpServerResponse represents the response from system_start_http_server
pub struct SystemStartHttpServerResponse {
pub mut:
result string // Result of starting the HTTP server
}
// StreamCurrentLogsResponse represents the response from stream_currentLogs
pub struct StreamCurrentLogsResponse {
pub mut:
logs []string // Log entries
}
// StreamSubscribeLogsResponse represents the response from stream_subscribeLogs
pub struct StreamSubscribeLogsResponse {
pub mut:
subscription_id string // ID of the log subscription
}
// Module version information
pub const (
version = '1.0.0'
author = 'Hero Code'
license = 'MIT'
)
// Default socket path for zinit
pub const default_socket_path = '/tmp/zinit.sock'
// Common service states
pub const (
state_running = 'Running'
state_success = 'Success'
state_error = 'Error'
state_stopped = 'Stopped'
state_failed = 'Failed'
)
// Common service targets
pub const (
target_up = 'Up'
target_down = 'Down'
)
// Common log types
pub const (
log_null = 'null'
log_ring = 'ring'
log_stdout = 'stdout'
)
// Common signals
pub const (
signal_term = 'SIGTERM'
signal_kill = 'SIGKILL'
signal_hup = 'SIGHUP'
signal_usr1 = 'SIGUSR1'
signal_usr2 = 'SIGUSR2'
)
// JSON-RPC error codes as defined in the OpenRPC specification
pub const (
error_service_not_found = -32000
error_service_already_monitored = -32001
error_service_is_up = -32002
error_service_is_down = -32003
error_invalid_signal = -32004
error_config_error = -32005
error_shutting_down = -32006
error_service_already_exists = -32007
error_service_file_error = -32008
)

View File

@@ -0,0 +1,873 @@
{
"openrpc": "1.2.6",
"info": {
"version": "1.0.0",
"title": "Zinit JSON-RPC API",
"description": "JSON-RPC 2.0 API for controlling and querying Zinit services",
"license": {
"name": "MIT"
}
},
"servers": [
{
"name": "Unix Socket",
"url": "unix:///tmp/zinit.sock"
}
],
"methods": [
{
"name": "rpc.discover",
"description": "Returns the OpenRPC specification for the API",
"params": [],
"result": {
"name": "OpenRPCSpec",
"description": "The OpenRPC specification",
"schema": {
"type": "object"
}
},
"examples": [
{
"name": "Get API specification",
"params": [],
"result": {
"name": "OpenRPCSpecResult",
"value": {
"openrpc": "1.2.6",
"info": {
"version": "1.0.0",
"title": "Zinit JSON-RPC API"
}
}
}
}
]
},
{
"name": "service_list",
"description": "Lists all services managed by Zinit",
"params": [],
"result": {
"name": "ServiceList",
"description": "A map of service names to their current states",
"schema": {
"type": "object",
"additionalProperties": {
"type": "string",
"description": "Service state (Running, Success, Error, etc.)"
}
}
},
"examples": [
{
"name": "List all services",
"params": [],
"result": {
"name": "ServiceListResult",
"value": {
"service1": "Running",
"service2": "Success",
"service3": "Error"
}
}
}
]
},
{
"name": "service_status",
"description": "Shows detailed status information for a specific service",
"params": [
{
"name": "name",
"description": "The name of the service",
"required": true,
"schema": {
"type": "string"
}
}
],
"result": {
"name": "ServiceStatus",
"description": "Detailed status information for the service",
"schema": {
"type": "object",
"properties": {
"name": {
"type": "string",
"description": "Service name"
},
"pid": {
"type": "integer",
"description": "Process ID of the running service (if running)"
},
"state": {
"type": "string",
"description": "Current state of the service (Running, Success, Error, etc.)"
},
"target": {
"type": "string",
"description": "Target state of the service (Up, Down)"
},
"after": {
"type": "object",
"description": "Dependencies of the service and their states",
"additionalProperties": {
"type": "string",
"description": "State of the dependency"
}
}
}
}
},
"examples": [
{
"name": "Get status of redis service",
"params": [
{
"name": "name",
"value": "redis"
}
],
"result": {
"name": "ServiceStatusResult",
"value": {
"name": "redis",
"pid": 1234,
"state": "Running",
"target": "Up",
"after": {
"dependency1": "Success",
"dependency2": "Running"
}
}
}
}
],
"errors": [
{
"code": -32000,
"message": "Service not found",
"data": "service name \"unknown\" unknown"
}
]
},
{
"name": "service_start",
"description": "Starts a service",
"params": [
{
"name": "name",
"description": "The name of the service to start",
"required": true,
"schema": {
"type": "string"
}
}
],
"result": {
"name": "StartResult",
"description": "Result of the start operation",
"schema": {
"type": "null"
}
},
"examples": [
{
"name": "Start redis service",
"params": [
{
"name": "name",
"value": "redis"
}
],
"result": {
"name": "StartResult",
"value": null
}
}
],
"errors": [
{
"code": -32000,
"message": "Service not found",
"data": "service name \"unknown\" unknown"
}
]
},
{
"name": "service_stop",
"description": "Stops a service",
"params": [
{
"name": "name",
"description": "The name of the service to stop",
"required": true,
"schema": {
"type": "string"
}
}
],
"result": {
"name": "StopResult",
"description": "Result of the stop operation",
"schema": {
"type": "null"
}
},
"examples": [
{
"name": "Stop redis service",
"params": [
{
"name": "name",
"value": "redis"
}
],
"result": {
"name": "StopResult",
"value": null
}
}
],
"errors": [
{
"code": -32000,
"message": "Service not found",
"data": "service name \"unknown\" unknown"
},
{
"code": -32003,
"message": "Service is down",
"data": "service \"redis\" is down"
}
]
},
{
"name": "service_monitor",
"description": "Starts monitoring a service. The service configuration is loaded from the config directory.",
"params": [
{
"name": "name",
"description": "The name of the service to monitor",
"required": true,
"schema": {
"type": "string"
}
}
],
"result": {
"name": "MonitorResult",
"description": "Result of the monitor operation",
"schema": {
"type": "null"
}
},
"examples": [
{
"name": "Monitor redis service",
"params": [
{
"name": "name",
"value": "redis"
}
],
"result": {
"name": "MonitorResult",
"value": null
}
}
],
"errors": [
{
"code": -32001,
"message": "Service already monitored",
"data": "service \"redis\" already monitored"
},
{
"code": -32005,
"message": "Config error",
"data": "failed to load service configuration"
}
]
},
{
"name": "service_forget",
"description": "Stops monitoring a service. You can only forget a stopped service.",
"params": [
{
"name": "name",
"description": "The name of the service to forget",
"required": true,
"schema": {
"type": "string"
}
}
],
"result": {
"name": "ForgetResult",
"description": "Result of the forget operation",
"schema": {
"type": "null"
}
},
"examples": [
{
"name": "Forget redis service",
"params": [
{
"name": "name",
"value": "redis"
}
],
"result": {
"name": "ForgetResult",
"value": null
}
}
],
"errors": [
{
"code": -32000,
"message": "Service not found",
"data": "service name \"unknown\" unknown"
},
{
"code": -32002,
"message": "Service is up",
"data": "service \"redis\" is up"
}
]
},
{
"name": "service_kill",
"description": "Sends a signal to a running service",
"params": [
{
"name": "name",
"description": "The name of the service to send the signal to",
"required": true,
"schema": {
"type": "string"
}
},
{
"name": "signal",
"description": "The signal to send (e.g., SIGTERM, SIGKILL)",
"required": true,
"schema": {
"type": "string"
}
}
],
"result": {
"name": "KillResult",
"description": "Result of the kill operation",
"schema": {
"type": "null"
}
},
"examples": [
{
"name": "Send SIGTERM to redis service",
"params": [
{
"name": "name",
"value": "redis"
},
{
"name": "signal",
"value": "SIGTERM"
}
],
"result": {
"name": "KillResult",
"value": null
}
}
],
"errors": [
{
"code": -32000,
"message": "Service not found",
"data": "service name \"unknown\" unknown"
},
{
"code": -32003,
"message": "Service is down",
"data": "service \"redis\" is down"
},
{
"code": -32004,
"message": "Invalid signal",
"data": "invalid signal: INVALID"
}
]
},
{
"name": "system_shutdown",
"description": "Stops all services and powers off the system",
"params": [],
"result": {
"name": "ShutdownResult",
"description": "Result of the shutdown operation",
"schema": {
"type": "null"
}
},
"examples": [
{
"name": "Shutdown the system",
"params": [],
"result": {
"name": "ShutdownResult",
"value": null
}
}
],
"errors": [
{
"code": -32006,
"message": "Shutting down",
"data": "system is already shutting down"
}
]
},
{
"name": "system_reboot",
"description": "Stops all services and reboots the system",
"params": [],
"result": {
"name": "RebootResult",
"description": "Result of the reboot operation",
"schema": {
"type": "null"
}
},
"examples": [
{
"name": "Reboot the system",
"params": [],
"result": {
"name": "RebootResult",
"value": null
}
}
],
"errors": [
{
"code": -32006,
"message": "Shutting down",
"data": "system is already shutting down"
}
]
},
{
"name": "service_create",
"description": "Creates a new service configuration file",
"params": [
{
"name": "name",
"description": "The name of the service to create",
"required": true,
"schema": {
"type": "string"
}
},
{
"name": "content",
"description": "The service configuration content",
"required": true,
"schema": {
"type": "object",
"properties": {
"exec": {
"type": "string",
"description": "Command to run"
},
"oneshot": {
"type": "boolean",
"description": "Whether the service should be restarted"
},
"after": {
"type": "array",
"items": {
"type": "string"
},
"description": "Services that must be running before this one starts"
},
"log": {
"type": "string",
"enum": ["null", "ring", "stdout"],
"description": "How to handle service output"
},
"env": {
"type": "object",
"additionalProperties": {
"type": "string"
},
"description": "Environment variables for the service"
},
"shutdown_timeout": {
"type": "integer",
"description": "Maximum time to wait for service to stop during shutdown"
}
}
}
}
],
"result": {
"name": "CreateServiceResult",
"description": "Result of the create operation",
"schema": {
"type": "string"
}
},
"errors": [
{
"code": -32007,
"message": "Service already exists",
"data": "Service 'name' already exists"
},
{
"code": -32008,
"message": "Service file error",
"data": "Failed to create service file"
}
]
},
{
"name": "service_delete",
"description": "Deletes a service configuration file",
"params": [
{
"name": "name",
"description": "The name of the service to delete",
"required": true,
"schema": {
"type": "string"
}
}
],
"result": {
"name": "DeleteServiceResult",
"description": "Result of the delete operation",
"schema": {
"type": "string"
}
},
"errors": [
{
"code": -32000,
"message": "Service not found",
"data": "Service 'name' not found"
},
{
"code": -32008,
"message": "Service file error",
"data": "Failed to delete service file"
}
]
},
{
"name": "service_get",
"description": "Gets a service configuration file",
"params": [
{
"name": "name",
"description": "The name of the service to get",
"required": true,
"schema": {
"type": "string"
}
}
],
"result": {
"name": "GetServiceResult",
"description": "The service configuration",
"schema": {
"type": "object"
}
},
"errors": [
{
"code": -32000,
"message": "Service not found",
"data": "Service 'name' not found"
},
{
"code": -32008,
"message": "Service file error",
"data": "Failed to read service file"
}
]
},
{
"name": "service_stats",
"description": "Get memory and CPU usage statistics for a service",
"params": [
{
"name": "name",
"description": "The name of the service to get stats for",
"required": true,
"schema": {
"type": "string"
}
}
],
"result": {
"name": "ServiceStats",
"description": "Memory and CPU usage statistics for the service",
"schema": {
"type": "object",
"properties": {
"name": {
"type": "string",
"description": "Service name"
},
"pid": {
"type": "integer",
"description": "Process ID of the service"
},
"memory_usage": {
"type": "integer",
"description": "Memory usage in bytes"
},
"cpu_usage": {
"type": "number",
"description": "CPU usage as a percentage (0-100)"
},
"children": {
"type": "array",
"description": "Stats for child processes",
"items": {
"type": "object",
"properties": {
"pid": {
"type": "integer",
"description": "Process ID of the child process"
},
"memory_usage": {
"type": "integer",
"description": "Memory usage in bytes"
},
"cpu_usage": {
"type": "number",
"description": "CPU usage as a percentage (0-100)"
}
}
}
}
}
}
},
"examples": [
{
"name": "Get stats for redis service",
"params": [
{
"name": "name",
"value": "redis"
}
],
"result": {
"name": "ServiceStatsResult",
"value": {
"name": "redis",
"pid": 1234,
"memory_usage": 10485760,
"cpu_usage": 2.5,
"children": [
{
"pid": 1235,
"memory_usage": 5242880,
"cpu_usage": 1.2
}
]
}
}
}
],
"errors": [
{
"code": -32000,
"message": "Service not found",
"data": "service name \"unknown\" unknown"
},
{
"code": -32003,
"message": "Service is down",
"data": "service \"redis\" is down"
}
]
},
{
"name": "system_start_http_server",
"description": "Start an HTTP/RPC server at the specified address",
"params": [
{
"name": "address",
"description": "The network address to bind the server to (e.g., '127.0.0.1:8080')",
"required": true,
"schema": {
"type": "string"
}
}
],
"result": {
"name": "StartHttpServerResult",
"description": "Result of the start HTTP server operation",
"schema": {
"type": "string"
}
},
"examples": [
{
"name": "Start HTTP server on localhost:8080",
"params": [
{
"name": "address",
"value": "127.0.0.1:8080"
}
],
"result": {
"name": "StartHttpServerResult",
"value": "HTTP server started at 127.0.0.1:8080"
}
}
],
"errors": [
{
"code": -32602,
"message": "Invalid address",
"data": "Invalid network address format"
}
]
},
{
"name": "system_stop_http_server",
"description": "Stop the HTTP/RPC server if running",
"params": [],
"result": {
"name": "StopHttpServerResult",
"description": "Result of the stop HTTP server operation",
"schema": {
"type": "null"
}
},
"examples": [
{
"name": "Stop the HTTP server",
"params": [],
"result": {
"name": "StopHttpServerResult",
"value": null
}
}
],
"errors": [
{
"code": -32602,
"message": "Server not running",
"data": "No HTTP server is currently running"
}
]
},
{
"name": "stream_currentLogs",
"description": "Get current logs from zinit and monitored services",
"params": [
{
"name": "name",
"description": "Optional service name filter. If provided, only logs from this service will be returned",
"required": false,
"schema": {
"type": "string"
}
}
],
"result": {
"name": "LogsResult",
"description": "Array of log strings",
"schema": {
"type": "array",
"items": {
"type": "string"
}
}
},
"examples": [
{
"name": "Get all logs",
"params": [],
"result": {
"name": "LogsResult",
"value": [
"2023-01-01T12:00:00 redis: Starting service",
"2023-01-01T12:00:01 nginx: Starting service"
]
}
},
{
"name": "Get logs for a specific service",
"params": [
{
"name": "name",
"value": "redis"
}
],
"result": {
"name": "LogsResult",
"value": [
"2023-01-01T12:00:00 redis: Starting service",
"2023-01-01T12:00:02 redis: Service started"
]
}
}
]
},
{
"name": "stream_subscribeLogs",
"description": "Subscribe to log messages generated by zinit and monitored services",
"params": [
{
"name": "name",
"description": "Optional service name filter. If provided, only logs from this service will be returned",
"required": false,
"schema": {
"type": "string"
}
}
],
"result": {
"name": "LogSubscription",
"description": "A subscription to log messages",
"schema": {
"type": "string"
}
},
"examples": [
{
"name": "Subscribe to all logs",
"params": [],
"result": {
"name": "LogSubscription",
"value": "2023-01-01T12:00:00 redis: Service started"
}
},
{
"name": "Subscribe to filtered logs",
"params": [
{
"name": "name",
"value": "redis"
}
],
"result": {
"name": "LogSubscription",
"value": "2023-01-01T12:00:00 redis: Service started"
}
}
]
}
]
}

181
lib/clients/zinit/service.v Normal file
View File

@@ -0,0 +1,181 @@
module zinit
import freeflowuniverse.herolib.schemas.jsonrpc
// ServiceConfig represents the configuration for a zinit service
pub struct ServiceConfig {
pub mut:
exec string // Command to run
oneshot bool // Whether the service should be restarted
after []string // Services that must be running before this one starts
log string // How to handle service output (null, ring, stdout)
env map[string]string // Environment variables for the service
shutdown_timeout int // Maximum time to wait for service to stop during shutdown
}
// KillParams represents the parameters for the service_kill method
pub struct KillParams {
pub:
name string // Name of the service to kill
signal string // Signal to send (e.g., SIGTERM, SIGKILL)
}
// RpcDiscoverResponse represents the response from rpc.discover
pub struct RpcDiscoverResponse {
pub mut:
spec map[string]string // OpenRPC specification
}
// rpc_discover returns the OpenRPC specification for the API
pub fn (mut c Client) rpc_discover() !RpcDiscoverResponse {
request := jsonrpc.new_request_generic('rpc.discover', []string{})
response := c.rpc_client.send[[]string, map[string]string](request)!
return RpcDiscoverResponse{
spec: response
}
}
// // Response Models for Zinit API
// //
// // This file contains all the response models used by the Zinit API.
// // These models are used as type parameters in the response generics.
// // ServiceListResponse represents the response from service_list
// pub struct ServiceListResponse {
// pub mut:
// // Map of service names to their current states
// services map[string]string
// }
// service_list lists all services managed by Zinit
// Returns a map of service names to their current states
pub fn (mut c Client) service_list() !map[string]string {
request := jsonrpc.new_request_generic('service_list',map[string]string )
services := c.rpc_client.send[map[string]string, map[string]string](request)!
// return ServiceListResponse{
// services: services
// }
return services
}
// ServiceStatusResponse represents the response from service_status
pub struct ServiceStatusResponse {
pub mut:
name string // Service name
pid int // Process ID of the running service (if running)
state string // Current state of the service (Running, Success, Error, etc.)
target string // Target state of the service (Up, Down)
after map[string]string // Dependencies of the service and their states
}
// service_status shows detailed status information for a specific service
// name: the name of the service
pub fn (mut c Client) service_status(name string) !ServiceStatusResponse {
request := jsonrpc.new_request_generic('service_status', name)
// Use a direct struct mapping instead of manual conversion
return c.rpc_client.send[string, ServiceStatusResponse](request)!
}
// service_start starts a service
// name: the name of the service to start
pub fn (mut c Client) service_start(name string) ! {
request := jsonrpc.new_request_generic('service_start', name)
c.rpc_client.send[string, string](request)!
}
// service_stop stops a service
// name: the name of the service to stop
pub fn (mut c Client) service_stop(name string) ! {
request := jsonrpc.new_request_generic('service_stop', name)
c.rpc_client.send[string, string](request)!
}
// service_monitor starts monitoring a service
// The service configuration is loaded from the config directory
// name: the name of the service to monitor
pub fn (mut c Client) service_monitor(name string) ! {
request := jsonrpc.new_request_generic('service_monitor', name)
c.rpc_client.send[string, string](request)!
}
// service_delete deletes a service configuration file
// name: the name of the service to delete
pub fn (mut c Client) service_delete(name string) !ServiceDeleteResponse {
request := jsonrpc.new_request_generic('service_delete', name)
result := c.rpc_client.send[string, string](request)!
return ServiceDeleteResponse{
result: result
}
}
// service_forget stops monitoring a service
// You can only forget a stopped service
// name: the name of the service to forget
pub fn (mut c Client) service_forget(name string) ! {
request := jsonrpc.new_request_generic('service_forget', name)
c.rpc_client.send[string, string](request)!
}
//TODO: make sure the signal is a valid signal and enumerator do as @[params] so its optional
// service_kill sends a signal to a running service
// name: the name of the service to send the signal to
// signal: the signal to send (e.g., SIGTERM, SIGKILL)
pub fn (mut c Client) service_kill(name string, signal string) ! {
params := KillParams{
name: name
signal: signal
}
request := jsonrpc.new_request_generic('service_kill', params)
c.rpc_client.send[KillParams, string](request)!
}
// CreateServiceParams represents the parameters for the service_create method
struct CreateServiceParams {
name string // Name of the service to create
content ServiceConfig // Configuration for the service
}
// service_create creates a new service configuration file
// name: the name of the service to create
// config: the service configuration
pub fn (mut c Client) service_create(name string, config ServiceConfig) !ServiceCreateResponse {
params := CreateServiceParams{
name: name
content: config
}
request := jsonrpc.new_request_generic('service_create', params)
path := c.rpc_client.send[CreateServiceParams, string](request)!
return ServiceCreateResponse{
path: path
}
}
// service_get gets a service configuration file
// name: the name of the service to get
pub fn (mut c Client) service_get(name string) !ServiceConfigResponse {
request := jsonrpc.new_request_generic('service_get', {"name":name})
// We need to handle the conversion from ServiceConfig to ServiceConfigResponse
config := c.rpc_client.send[map[string]string, ServiceConfig](request)!
return ServiceConfigResponse{
exec: config.exec
oneshot: config.oneshot
after: config.after
log: config.log
env: config.env
shutdown_timeout: config.shutdown_timeout
}
}

View File

@@ -0,0 +1,35 @@
module zinit
pub struct ServiceConfigResponse {
pub mut:
exec string // Command to run
oneshot bool // Whether the service should be restarted
after []string // Services that must be running before this one starts
log string // How to handle service output (null, ring, stdout)
env map[string]string // Environment variables for the service
shutdown_timeout int // Maximum time to wait for service to stop during shutdown
}
// Helper function to create a basic service configuration
pub fn new_service_config(exec string) ServiceConfig {
return ServiceConfig{
exec: exec
oneshot: false
log: log_stdout
env: map[string]string{}
shutdown_timeout: 30
}
}
// Helper function to create a oneshot service configuration
pub fn new_oneshot_service_config(exec string) ServiceConfig {
return ServiceConfig{
exec: exec
oneshot: true
log: log_stdout
env: map[string]string{}
shutdown_timeout: 30
}
}

View File

@@ -0,0 +1,44 @@
module zinit
import freeflowuniverse.herolib.schemas.jsonrpc
// ServiceStatsResponse represents the response from service_stats
pub struct ServiceStatsResponse {
pub mut:
name string // Service name
pid int // Process ID of the service
memory_usage i64 // Memory usage in bytes
cpu_usage f64 // CPU usage as a percentage (0-100)
children []ChildStatsResponse // Stats for child processes
}
// ChildStatsResponse represents statistics for a child process
pub struct ChildStatsResponse {
pub mut:
pid int // Process ID of the child process
memory_usage i64 // Memory usage in bytes
cpu_usage f64 // CPU usage as a percentage (0-100)
}
// Serv
// service_stats gets memory and CPU usage statistics for a service
// name: the name of the service to get stats for
pub fn (mut c Client) service_stats(name string) !ServiceStatsResponse {
request := jsonrpc.new_request_generic('service_stats', name)
// We need to handle the conversion from the raw response to our model
raw_stats := c.rpc_client.send[string, map[string]string](request)!
// Parse the raw stats into our response model
mut children := []ChildStatsResponse{}
// In a real implementation, we would parse the children from the raw response
return ServiceStatsResponse{
name: raw_stats['name'] or { '' }
pid: raw_stats['pid'].int()
memory_usage: raw_stats['memory_usage'].i64()
cpu_usage: raw_stats['cpu_usage'].f64()
children: children
}
}

View File

@@ -0,0 +1,71 @@
module zinit
import freeflowuniverse.herolib.schemas.jsonrpc
// system_shutdown stops all services and powers off the system
pub fn (mut c Client) system_shutdown() ! {
request := jsonrpc.new_request_generic('system_shutdown', []string{})
c.rpc_client.send[[]string, string](request)!
}
// system_reboot stops all services and reboots the system
pub fn (mut c Client) system_reboot() ! {
request := jsonrpc.new_request_generic('system_reboot', []string{})
c.rpc_client.send[[]string, string](request)!
}
// system_start_http_server starts an HTTP/RPC server at the specified address
// address: the network address to bind the server to (e.g., '127.0.0.1:8080')
pub fn (mut c Client) system_start_http_server(address string) !SystemStartHttpServerResponse {
request := jsonrpc.new_request_generic('system_start_http_server', address)
result := c.rpc_client.send[string, string](request)!
return SystemStartHttpServerResponse{
result: result
}
}
// system_stop_http_server stops the HTTP/RPC server if running
pub fn (mut c Client) system_stop_http_server() ! {
request := jsonrpc.new_request_generic('system_stop_http_server', []string{})
c.rpc_client.send[[]string, string](request)!
}
@[params]
pub struct LogParams {
name string
}
// stream_current_logs gets current logs from zinit and monitored services
// name: optional service name filter. If provided, only logs from this service will be returned
pub fn (mut c Client) stream_current_logs(args LogParams) ![]string {
mut logs := []string{}
if args.name != '' {
request := jsonrpc.new_request_generic('stream_currentLogs', {"name":args.name})
logs = c.rpc_client.send[map[string]string, map[string]string](request)!
} else {
request := jsonrpc.new_request_generic('stream_currentLogs', map[string]string{})
logs = c.rpc_client.send[[]map[string]string, map[string]string](request)!
}
return logs
}
// stream_subscribe_logs subscribes to log messages generated by zinit and monitored services
// name: optional service name filter. If provided, only logs from this service will be returned
pub fn (mut c Client) stream_subscribe_logs(name ?string) !StreamSubscribeLogsResponse {
mut subscription_id := ''
if service_name := name {
request := jsonrpc.new_request_generic('stream_subscribeLogs', service_name)
subscription_id = c.rpc_client.send[string, string](request)!
} else {
request := jsonrpc.new_request_generic('stream_subscribeLogs', []string{})
subscription_id = c.rpc_client.send[[]string, string](request)!
}
return StreamSubscribeLogsResponse{
subscription_id: subscription_id
}
}

20
lib/clients/zinit/tools.v Normal file
View File

@@ -0,0 +1,20 @@
module zinit
// Helper function to format memory usage in human-readable format
pub fn format_memory_usage(bytes i64) string {
if bytes < 1024 {
return '${bytes} B'
} else if bytes < 1024 * 1024 {
return '${bytes / 1024} KB'
} else if bytes < 1024 * 1024 * 1024 {
return '${bytes / 1024 / 1024} MB'
} else {
return '${bytes / 1024 / 1024 / 1024} GB'
}
}
// Helper function to format CPU usage
pub fn format_cpu_usage(cpu_percent f64) string {
return '${cpu_percent:.1f}%'
}

View File

@@ -0,0 +1,8 @@
!!hero_code.generate_client
name:'zinit_rpc'
classname:'ZinitRPC'
singleton:1
default:0
hasconfig:1
reset:0

View File

@@ -0,0 +1,873 @@
{
"openrpc": "1.2.6",
"info": {
"version": "1.0.0",
"title": "Zinit JSON-RPC API",
"description": "JSON-RPC 2.0 API for controlling and querying Zinit services",
"license": {
"name": "MIT"
}
},
"servers": [
{
"name": "Unix Socket",
"url": "unix:///tmp/zinit.sock"
}
],
"methods": [
{
"name": "rpc.discover",
"description": "Returns the OpenRPC specification for the API",
"params": [],
"result": {
"name": "OpenRPCSpec",
"description": "The OpenRPC specification",
"schema": {
"type": "object"
}
},
"examples": [
{
"name": "Get API specification",
"params": [],
"result": {
"name": "OpenRPCSpecResult",
"value": {
"openrpc": "1.2.6",
"info": {
"version": "1.0.0",
"title": "Zinit JSON-RPC API"
}
}
}
}
]
},
{
"name": "service_list",
"description": "Lists all services managed by Zinit",
"params": [],
"result": {
"name": "ServiceList",
"description": "A map of service names to their current states",
"schema": {
"type": "object",
"additionalProperties": {
"type": "string",
"description": "Service state (Running, Success, Error, etc.)"
}
}
},
"examples": [
{
"name": "List all services",
"params": [],
"result": {
"name": "ServiceListResult",
"value": {
"service1": "Running",
"service2": "Success",
"service3": "Error"
}
}
}
]
},
{
"name": "service_status",
"description": "Shows detailed status information for a specific service",
"params": [
{
"name": "name",
"description": "The name of the service",
"required": true,
"schema": {
"type": "string"
}
}
],
"result": {
"name": "ServiceStatus",
"description": "Detailed status information for the service",
"schema": {
"type": "object",
"properties": {
"name": {
"type": "string",
"description": "Service name"
},
"pid": {
"type": "integer",
"description": "Process ID of the running service (if running)"
},
"state": {
"type": "string",
"description": "Current state of the service (Running, Success, Error, etc.)"
},
"target": {
"type": "string",
"description": "Target state of the service (Up, Down)"
},
"after": {
"type": "object",
"description": "Dependencies of the service and their states",
"additionalProperties": {
"type": "string",
"description": "State of the dependency"
}
}
}
}
},
"examples": [
{
"name": "Get status of redis service",
"params": [
{
"name": "name",
"value": "redis"
}
],
"result": {
"name": "ServiceStatusResult",
"value": {
"name": "redis",
"pid": 1234,
"state": "Running",
"target": "Up",
"after": {
"dependency1": "Success",
"dependency2": "Running"
}
}
}
}
],
"errors": [
{
"code": -32000,
"message": "Service not found",
"data": "service name \"unknown\" unknown"
}
]
},
{
"name": "service_start",
"description": "Starts a service",
"params": [
{
"name": "name",
"description": "The name of the service to start",
"required": true,
"schema": {
"type": "string"
}
}
],
"result": {
"name": "StartResult",
"description": "Result of the start operation",
"schema": {
"type": "null"
}
},
"examples": [
{
"name": "Start redis service",
"params": [
{
"name": "name",
"value": "redis"
}
],
"result": {
"name": "StartResult",
"value": null
}
}
],
"errors": [
{
"code": -32000,
"message": "Service not found",
"data": "service name \"unknown\" unknown"
}
]
},
{
"name": "service_stop",
"description": "Stops a service",
"params": [
{
"name": "name",
"description": "The name of the service to stop",
"required": true,
"schema": {
"type": "string"
}
}
],
"result": {
"name": "StopResult",
"description": "Result of the stop operation",
"schema": {
"type": "null"
}
},
"examples": [
{
"name": "Stop redis service",
"params": [
{
"name": "name",
"value": "redis"
}
],
"result": {
"name": "StopResult",
"value": null
}
}
],
"errors": [
{
"code": -32000,
"message": "Service not found",
"data": "service name \"unknown\" unknown"
},
{
"code": -32003,
"message": "Service is down",
"data": "service \"redis\" is down"
}
]
},
{
"name": "service_monitor",
"description": "Starts monitoring a service. The service configuration is loaded from the config directory.",
"params": [
{
"name": "name",
"description": "The name of the service to monitor",
"required": true,
"schema": {
"type": "string"
}
}
],
"result": {
"name": "MonitorResult",
"description": "Result of the monitor operation",
"schema": {
"type": "null"
}
},
"examples": [
{
"name": "Monitor redis service",
"params": [
{
"name": "name",
"value": "redis"
}
],
"result": {
"name": "MonitorResult",
"value": null
}
}
],
"errors": [
{
"code": -32001,
"message": "Service already monitored",
"data": "service \"redis\" already monitored"
},
{
"code": -32005,
"message": "Config error",
"data": "failed to load service configuration"
}
]
},
{
"name": "service_forget",
"description": "Stops monitoring a service. You can only forget a stopped service.",
"params": [
{
"name": "name",
"description": "The name of the service to forget",
"required": true,
"schema": {
"type": "string"
}
}
],
"result": {
"name": "ForgetResult",
"description": "Result of the forget operation",
"schema": {
"type": "null"
}
},
"examples": [
{
"name": "Forget redis service",
"params": [
{
"name": "name",
"value": "redis"
}
],
"result": {
"name": "ForgetResult",
"value": null
}
}
],
"errors": [
{
"code": -32000,
"message": "Service not found",
"data": "service name \"unknown\" unknown"
},
{
"code": -32002,
"message": "Service is up",
"data": "service \"redis\" is up"
}
]
},
{
"name": "service_kill",
"description": "Sends a signal to a running service",
"params": [
{
"name": "name",
"description": "The name of the service to send the signal to",
"required": true,
"schema": {
"type": "string"
}
},
{
"name": "signal",
"description": "The signal to send (e.g., SIGTERM, SIGKILL)",
"required": true,
"schema": {
"type": "string"
}
}
],
"result": {
"name": "KillResult",
"description": "Result of the kill operation",
"schema": {
"type": "null"
}
},
"examples": [
{
"name": "Send SIGTERM to redis service",
"params": [
{
"name": "name",
"value": "redis"
},
{
"name": "signal",
"value": "SIGTERM"
}
],
"result": {
"name": "KillResult",
"value": null
}
}
],
"errors": [
{
"code": -32000,
"message": "Service not found",
"data": "service name \"unknown\" unknown"
},
{
"code": -32003,
"message": "Service is down",
"data": "service \"redis\" is down"
},
{
"code": -32004,
"message": "Invalid signal",
"data": "invalid signal: INVALID"
}
]
},
{
"name": "system_shutdown",
"description": "Stops all services and powers off the system",
"params": [],
"result": {
"name": "ShutdownResult",
"description": "Result of the shutdown operation",
"schema": {
"type": "null"
}
},
"examples": [
{
"name": "Shutdown the system",
"params": [],
"result": {
"name": "ShutdownResult",
"value": null
}
}
],
"errors": [
{
"code": -32006,
"message": "Shutting down",
"data": "system is already shutting down"
}
]
},
{
"name": "system_reboot",
"description": "Stops all services and reboots the system",
"params": [],
"result": {
"name": "RebootResult",
"description": "Result of the reboot operation",
"schema": {
"type": "null"
}
},
"examples": [
{
"name": "Reboot the system",
"params": [],
"result": {
"name": "RebootResult",
"value": null
}
}
],
"errors": [
{
"code": -32006,
"message": "Shutting down",
"data": "system is already shutting down"
}
]
},
{
"name": "service_create",
"description": "Creates a new service configuration file",
"params": [
{
"name": "name",
"description": "The name of the service to create",
"required": true,
"schema": {
"type": "string"
}
},
{
"name": "content",
"description": "The service configuration content",
"required": true,
"schema": {
"type": "object",
"properties": {
"exec": {
"type": "string",
"description": "Command to run"
},
"oneshot": {
"type": "boolean",
"description": "Whether the service should be restarted"
},
"after": {
"type": "array",
"items": {
"type": "string"
},
"description": "Services that must be running before this one starts"
},
"log": {
"type": "string",
"enum": ["null", "ring", "stdout"],
"description": "How to handle service output"
},
"env": {
"type": "object",
"additionalProperties": {
"type": "string"
},
"description": "Environment variables for the service"
},
"shutdown_timeout": {
"type": "integer",
"description": "Maximum time to wait for service to stop during shutdown"
}
}
}
}
],
"result": {
"name": "CreateServiceResult",
"description": "Result of the create operation",
"schema": {
"type": "string"
}
},
"errors": [
{
"code": -32007,
"message": "Service already exists",
"data": "Service 'name' already exists"
},
{
"code": -32008,
"message": "Service file error",
"data": "Failed to create service file"
}
]
},
{
"name": "service_delete",
"description": "Deletes a service configuration file",
"params": [
{
"name": "name",
"description": "The name of the service to delete",
"required": true,
"schema": {
"type": "string"
}
}
],
"result": {
"name": "DeleteServiceResult",
"description": "Result of the delete operation",
"schema": {
"type": "string"
}
},
"errors": [
{
"code": -32000,
"message": "Service not found",
"data": "Service 'name' not found"
},
{
"code": -32008,
"message": "Service file error",
"data": "Failed to delete service file"
}
]
},
{
"name": "service_get",
"description": "Gets a service configuration file",
"params": [
{
"name": "name",
"description": "The name of the service to get",
"required": true,
"schema": {
"type": "string"
}
}
],
"result": {
"name": "GetServiceResult",
"description": "The service configuration",
"schema": {
"type": "object"
}
},
"errors": [
{
"code": -32000,
"message": "Service not found",
"data": "Service 'name' not found"
},
{
"code": -32008,
"message": "Service file error",
"data": "Failed to read service file"
}
]
},
{
"name": "service_stats",
"description": "Get memory and CPU usage statistics for a service",
"params": [
{
"name": "name",
"description": "The name of the service to get stats for",
"required": true,
"schema": {
"type": "string"
}
}
],
"result": {
"name": "ServiceStats",
"description": "Memory and CPU usage statistics for the service",
"schema": {
"type": "object",
"properties": {
"name": {
"type": "string",
"description": "Service name"
},
"pid": {
"type": "integer",
"description": "Process ID of the service"
},
"memory_usage": {
"type": "integer",
"description": "Memory usage in bytes"
},
"cpu_usage": {
"type": "number",
"description": "CPU usage as a percentage (0-100)"
},
"children": {
"type": "array",
"description": "Stats for child processes",
"items": {
"type": "object",
"properties": {
"pid": {
"type": "integer",
"description": "Process ID of the child process"
},
"memory_usage": {
"type": "integer",
"description": "Memory usage in bytes"
},
"cpu_usage": {
"type": "number",
"description": "CPU usage as a percentage (0-100)"
}
}
}
}
}
}
},
"examples": [
{
"name": "Get stats for redis service",
"params": [
{
"name": "name",
"value": "redis"
}
],
"result": {
"name": "ServiceStatsResult",
"value": {
"name": "redis",
"pid": 1234,
"memory_usage": 10485760,
"cpu_usage": 2.5,
"children": [
{
"pid": 1235,
"memory_usage": 5242880,
"cpu_usage": 1.2
}
]
}
}
}
],
"errors": [
{
"code": -32000,
"message": "Service not found",
"data": "service name \"unknown\" unknown"
},
{
"code": -32003,
"message": "Service is down",
"data": "service \"redis\" is down"
}
]
},
{
"name": "system_start_http_server",
"description": "Start an HTTP/RPC server at the specified address",
"params": [
{
"name": "address",
"description": "The network address to bind the server to (e.g., '127.0.0.1:8080')",
"required": true,
"schema": {
"type": "string"
}
}
],
"result": {
"name": "StartHttpServerResult",
"description": "Result of the start HTTP server operation",
"schema": {
"type": "string"
}
},
"examples": [
{
"name": "Start HTTP server on localhost:8080",
"params": [
{
"name": "address",
"value": "127.0.0.1:8080"
}
],
"result": {
"name": "StartHttpServerResult",
"value": "HTTP server started at 127.0.0.1:8080"
}
}
],
"errors": [
{
"code": -32602,
"message": "Invalid address",
"data": "Invalid network address format"
}
]
},
{
"name": "system_stop_http_server",
"description": "Stop the HTTP/RPC server if running",
"params": [],
"result": {
"name": "StopHttpServerResult",
"description": "Result of the stop HTTP server operation",
"schema": {
"type": "null"
}
},
"examples": [
{
"name": "Stop the HTTP server",
"params": [],
"result": {
"name": "StopHttpServerResult",
"value": null
}
}
],
"errors": [
{
"code": -32602,
"message": "Server not running",
"data": "No HTTP server is currently running"
}
]
},
{
"name": "stream_currentLogs",
"description": "Get current logs from zinit and monitored services",
"params": [
{
"name": "name",
"description": "Optional service name filter. If provided, only logs from this service will be returned",
"required": false,
"schema": {
"type": "string"
}
}
],
"result": {
"name": "LogsResult",
"description": "Array of log strings",
"schema": {
"type": "array",
"items": {
"type": "string"
}
}
},
"examples": [
{
"name": "Get all logs",
"params": [],
"result": {
"name": "LogsResult",
"value": [
"2023-01-01T12:00:00 redis: Starting service",
"2023-01-01T12:00:01 nginx: Starting service"
]
}
},
{
"name": "Get logs for a specific service",
"params": [
{
"name": "name",
"value": "redis"
}
],
"result": {
"name": "LogsResult",
"value": [
"2023-01-01T12:00:00 redis: Starting service",
"2023-01-01T12:00:02 redis: Service started"
]
}
}
]
},
{
"name": "stream_subscribeLogs",
"description": "Subscribe to log messages generated by zinit and monitored services",
"params": [
{
"name": "name",
"description": "Optional service name filter. If provided, only logs from this service will be returned",
"required": false,
"schema": {
"type": "string"
}
}
],
"result": {
"name": "LogSubscription",
"description": "A subscription to log messages",
"schema": {
"type": "string"
}
},
"examples": [
{
"name": "Subscribe to all logs",
"params": [],
"result": {
"name": "LogSubscription",
"value": "2023-01-01T12:00:00 redis: Service started"
}
},
{
"name": "Subscribe to filtered logs",
"params": [
{
"name": "name",
"value": "redis"
}
],
"result": {
"name": "LogSubscription",
"value": "2023-01-01T12:00:00 redis: Service started"
}
}
]
}
]
}

View File

@@ -0,0 +1,222 @@
# Zinit RPC Client
This is a V language client for the Zinit process manager, implementing the JSON-RPC API specification for service management operations.
## Overview
Zinit is a process manager that provides service monitoring, dependency management, and system control capabilities. This client provides a comprehensive API to interact with Zinit via its JSON-RPC interface for administrative tasks such as:
- Service lifecycle management (start, stop, monitor, forget)
- Service configuration management (create, delete, get)
- Service status and statistics monitoring
- System operations (shutdown, reboot, HTTP server control)
- Log streaming and monitoring
## Features
- **✅ 100% API Coverage**: Complete implementation of all 18 methods in the Zinit JSON-RPC specification
- **✅ Production Tested**: All methods tested and working against real Zinit instances
- **✅ Type-safe API**: Proper V struct definitions with comprehensive error handling
- **✅ Subscription Support**: Proper handling of streaming/subscription methods
- **✅ Unix Socket Transport**: Reliable communication via Unix domain sockets
- **✅ Comprehensive Documentation**: Extensive documentation with working examples
## Usage
### Basic Example
```v
import freeflowuniverse.herolib.clients.zinit_rpc
// Create a new client
mut client := zinit_rpc.new_client(
name: 'my_client'
socket_path: '/tmp/zinit.sock'
)!
// List all services
services := client.service_list()!
for service_name, state in services {
println('Service: ${service_name}, State: ${state}')
}
// Get detailed status of a specific service
status := client.service_status('redis')!
println('Service: ${status.name}')
println('PID: ${status.pid}')
println('State: ${status.state}')
println('Target: ${status.target}')
// Start a service
client.service_start('redis')!
// Stop a service
client.service_stop('redis')!
```
### Service Configuration Management
```v
import freeflowuniverse.herolib.clients.zinit_rpc
mut client := zinit_rpc.new_client()!
// Create a new service configuration
config := zinit_rpc.ServiceConfig{
exec: '/usr/bin/redis-server'
oneshot: false
log: 'stdout'
env: {
'REDIS_PORT': '6379'
'REDIS_HOST': '0.0.0.0'
}
shutdown_timeout: 30
}
// Create the service
path := client.service_create('redis', config)!
println('Service created at: ${path}')
// Get service configuration
retrieved_config := client.service_get('redis')!
println('Service exec: ${retrieved_config.exec}')
// Delete service configuration
result := client.service_delete('redis')!
println('Delete result: ${result}')
```
### Service Statistics
```v
import freeflowuniverse.herolib.clients.zinit_rpc
mut client := zinit_rpc.new_client()!
// Get service statistics
stats := client.service_stats('redis')!
println('Service: ${stats.name}')
println('PID: ${stats.pid}')
println('Memory Usage: ${stats.memory_usage} bytes')
println('CPU Usage: ${stats.cpu_usage}%')
// Print child process statistics
for child in stats.children {
println('Child PID: ${child.pid}, Memory: ${child.memory_usage}, CPU: ${child.cpu_usage}%')
}
```
### Log Streaming
```v
import freeflowuniverse.herolib.clients.zinit_rpc
mut client := zinit_rpc.new_client()!
// Get current logs for all services
logs := client.stream_current_logs(name: '')!
for log in logs {
println(log)
}
// Get current logs for a specific service
redis_logs := client.stream_current_logs(name: 'redis')!
for log in redis_logs {
println('Redis: ${log}')
}
// Subscribe to log stream (returns subscription ID)
subscription_id := client.stream_subscribe_logs(name: 'redis')!
println('Subscribed to logs with ID: ${subscription_id}')
```
## API Reference
### Service Management Methods
- `service_list()` - List all services and their states
- `service_status(name)` - Get detailed status of a service
- `service_start(name)` - Start a service
- `service_stop(name)` - Stop a service
- `service_monitor(name)` - Start monitoring a service
- `service_forget(name)` - Stop monitoring a service
- `service_kill(name, signal)` - Send signal to a service
### Service Configuration Methods
- `service_create(name, config)` - Create service configuration
- `service_delete(name)` - Delete service configuration
- `service_get(name)` - Get service configuration
### Monitoring Methods
- `service_stats(name)` - Get service statistics
### System Methods
- `system_shutdown()` - Shutdown the system
- `system_reboot()` - Reboot the system
- `system_start_http_server(address)` - Start HTTP server
- `system_stop_http_server()` - Stop HTTP server
### Streaming Methods
- `stream_current_logs(args)` - Get current logs (returns array of log lines)
- `stream_subscribe_logs(args)` - Subscribe to log stream (returns subscription ID)
### Discovery Methods
- `rpc_discover()` - Get OpenRPC specification
## Configuration
### Using the Factory Pattern
```v
import freeflowuniverse.herolib.clients.zinit_rpc
// Get client using factory (recommended)
mut client := zinit_rpc.get()!
// Use the client
services := client.service_list()!
```
### Example Heroscript Configuration
```hero
!!zinit_rpc.configure
name: 'production'
socket_path: '/tmp/zinit.sock'
```
## Error Handling
The client provides comprehensive error handling for all Zinit-specific error codes:
- `-32000`: Service not found
- `-32001`: Service already monitored
- `-32002`: Service is up
- `-32003`: Service is down
- `-32004`: Invalid signal
- `-32005`: Config error
- `-32006`: Shutting down
- `-32007`: Service already exists
- `-32008`: Service file error
```v
import freeflowuniverse.herolib.clients.zinit_rpc
mut client := zinit_rpc.new_client()!
// Handle specific errors
client.service_start('nonexistent') or {
if err.msg().contains('Service not found') {
println('Service does not exist')
} else {
println('Other error: ${err}')
}
}
```

View File

@@ -0,0 +1,201 @@
module zinit_rpc
import freeflowuniverse.herolib.schemas.jsonrpc
// Helper function to get or create the RPC client
fn (mut c ZinitRPC) get_client() !&jsonrpc.Client {
if client := c.rpc_client {
return client
}
// Create Unix socket client
mut client := jsonrpc.new_unix_socket_client(c.socket_path)
c.rpc_client = client
return client
}
// Admin methods
// rpc_discover returns the OpenRPC specification for the API
pub fn (mut c ZinitRPC) rpc_discover() !OpenRPCSpec {
mut client := c.get_client()!
request := jsonrpc.new_request_generic('rpc.discover', []string{})
return client.send[[]string, OpenRPCSpec](request)!
}
// service_list lists all services managed by Zinit
// Returns a map of service names to their current states
pub fn (mut c ZinitRPC) service_list() !map[string]string {
mut client := c.get_client()!
request := jsonrpc.new_request_generic('service_list', []string{})
return client.send[[]string, map[string]string](request)!
}
// service_status shows detailed status information for a specific service
pub fn (mut c ZinitRPC) service_status(name string) !ServiceStatus {
mut client := c.get_client()!
params := {
'name': name
}
request := jsonrpc.new_request_generic('service_status', params)
return client.send[map[string]string, ServiceStatus](request)!
}
// service_start starts a service
pub fn (mut c ZinitRPC) service_start(name string) ! {
mut client := c.get_client()!
params := {
'name': name
}
request := jsonrpc.new_request_generic('service_start', params)
client.send[map[string]string, string](request)!
}
// service_stop stops a service
pub fn (mut c ZinitRPC) service_stop(name string) ! {
mut client := c.get_client()!
params := {
'name': name
}
request := jsonrpc.new_request_generic('service_stop', params)
client.send[map[string]string, string](request)!
}
// service_monitor starts monitoring a service
// The service configuration is loaded from the config directory
pub fn (mut c ZinitRPC) service_monitor(name string) ! {
mut client := c.get_client()!
params := {
'name': name
}
request := jsonrpc.new_request_generic('service_monitor', params)
client.send[map[string]string, string](request)!
}
// service_forget stops monitoring a service
// You can only forget a stopped service
pub fn (mut c ZinitRPC) service_forget(name string) ! {
mut client := c.get_client()!
params := {
'name': name
}
request := jsonrpc.new_request_generic('service_forget', params)
client.send[map[string]string, string](request)!
}
// service_kill sends a signal to a running service
pub fn (mut c ZinitRPC) service_kill(name string, signal string) ! {
mut client := c.get_client()!
params := ServiceKillParams{
name: name
signal: signal
}
request := jsonrpc.new_request_generic('service_kill', params)
client.send[ServiceKillParams, string](request)!
}
// service_create creates a new service configuration file
pub fn (mut c ZinitRPC) service_create(name string, config ServiceConfig) !string {
mut client := c.get_client()!
params := ServiceCreateParams{
name: name
content: config
}
request := jsonrpc.new_request_generic('service_create', params)
return client.send[ServiceCreateParams, string](request)!
}
// service_delete deletes a service configuration file
pub fn (mut c ZinitRPC) service_delete(name string) !string {
mut client := c.get_client()!
params := {
'name': name
}
request := jsonrpc.new_request_generic('service_delete', params)
return client.send[map[string]string, string](request)!
}
// service_get gets a service configuration file
pub fn (mut c ZinitRPC) service_get(name string) !ServiceConfig {
mut client := c.get_client()!
params := {
'name': name
}
request := jsonrpc.new_request_generic('service_get', params)
return client.send[map[string]string, ServiceConfig](request)!
}
// service_stats gets memory and CPU usage statistics for a service
pub fn (mut c ZinitRPC) service_stats(name string) !ServiceStats {
mut client := c.get_client()!
params := {
'name': name
}
request := jsonrpc.new_request_generic('service_stats', params)
return client.send[map[string]string, ServiceStats](request)!
}
// System methods
// system_shutdown stops all services and powers off the system
pub fn (mut c ZinitRPC) system_shutdown() ! {
mut client := c.get_client()!
request := jsonrpc.new_request_generic('system_shutdown', []string{})
client.send[[]string, string](request)!
}
// system_reboot stops all services and reboots the system
pub fn (mut c ZinitRPC) system_reboot() ! {
mut client := c.get_client()!
request := jsonrpc.new_request_generic('system_reboot', []string{})
client.send[[]string, string](request)!
}
// system_start_http_server starts an HTTP/RPC server at the specified address
pub fn (mut c ZinitRPC) system_start_http_server(address string) !string {
mut client := c.get_client()!
params := {
'address': address
}
request := jsonrpc.new_request_generic('system_start_http_server', params)
return client.send[map[string]string, string](request)!
}
// system_stop_http_server stops the HTTP/RPC server if running
pub fn (mut c ZinitRPC) system_stop_http_server() ! {
mut client := c.get_client()!
request := jsonrpc.new_request_generic('system_stop_http_server', []string{})
client.send[[]string, string](request)!
}
// Streaming methods
// stream_current_logs gets current logs from zinit and monitored services
pub fn (mut c ZinitRPC) stream_current_logs(args LogParams) ![]string {
mut client := c.get_client()!
if args.name != '' {
params := {
'name': args.name
}
request := jsonrpc.new_request_generic('stream_currentLogs', params)
return client.send[map[string]string, []string](request)!
} else {
request := jsonrpc.new_request_generic('stream_currentLogs', []string{})
return client.send[[]string, []string](request)!
}
}
// stream_subscribe_logs subscribes to log messages generated by zinit and monitored services
// Returns a subscription ID that can be used to manage the subscription
pub fn (mut c ZinitRPC) stream_subscribe_logs(args LogParams) !u64 {
mut client := c.get_client()!
if args.name != '' {
params := {
'name': args.name
}
request := jsonrpc.new_request_generic('stream_subscribeLogs', params)
return client.send[map[string]string, u64](request)!
} else {
request := jsonrpc.new_request_generic('stream_subscribeLogs', []string{})
return client.send[[]string, u64](request)!
}
}

View File

@@ -0,0 +1,114 @@
module zinit_rpc
import freeflowuniverse.herolib.core.base
import freeflowuniverse.herolib.core.playbook
import freeflowuniverse.herolib.ui.console
__global (
zinit_rpc_global map[string]&ZinitRPC
zinit_rpc_default string
)
/////////FACTORY
@[params]
pub struct ArgsGet {
pub mut:
name string
}
fn args_get(args_ ArgsGet) ArgsGet {
mut args := args_
if args.name == '' {
args.name = 'default'
}
return args
}
pub fn get(args_ ArgsGet) !&ZinitRPC {
mut context := base.context()!
mut args := args_get(args_)
mut obj := ZinitRPC{
name: args.name
}
if args.name !in zinit_rpc_global {
if !exists(args)! {
set(obj)!
} else {
heroscript := context.hero_config_get('zinit_rpc', args.name)!
mut obj_ := heroscript_loads(heroscript)!
set_in_mem(obj_)!
}
}
return zinit_rpc_global[args.name] or {
println(zinit_rpc_global)
// bug if we get here because should be in globals
panic('could not get config for zinit_rpc with name, is bug:${args.name}')
}
}
// register the config for the future
pub fn set(o ZinitRPC) ! {
set_in_mem(o)!
mut context := base.context()!
heroscript := heroscript_dumps(o)!
context.hero_config_set('zinit_rpc', o.name, heroscript)!
}
// does the config exists?
pub fn exists(args_ ArgsGet) !bool {
mut context := base.context()!
mut args := args_get(args_)
return context.hero_config_exists('zinit_rpc', args.name)
}
pub fn delete(args_ ArgsGet) ! {
mut args := args_get(args_)
mut context := base.context()!
context.hero_config_delete('zinit_rpc', args.name)!
if args.name in zinit_rpc_global {
// del zinit_rpc_global[args.name]
}
}
// only sets in mem, does not set as config
fn set_in_mem(o ZinitRPC) ! {
mut o2 := obj_init(o)!
zinit_rpc_global[o.name] = &o2
zinit_rpc_default = o.name
}
@[params]
pub struct PlayArgs {
pub mut:
heroscript string // if filled in then plbook will be made out of it
plbook ?playbook.PlayBook
reset bool
}
pub fn play(args_ PlayArgs) ! {
mut args := args_
mut plbook := args.plbook or { playbook.new(text: args.heroscript)! }
mut install_actions := plbook.find(filter: 'zinit_rpc.configure')!
if install_actions.len > 0 {
for install_action in install_actions {
heroscript := install_action.heroscript()
mut obj2 := heroscript_loads(heroscript)!
set(obj2)!
}
}
}
// switch instance to be used for zinit_rpc
pub fn switch(name string) {
zinit_rpc_default = name
}
// helpers
@[params]
pub struct DefaultConfigArgs {
instance string = 'default'
}

View File

@@ -0,0 +1,163 @@
module zinit_rpc
import freeflowuniverse.herolib.data.encoderhero
import freeflowuniverse.herolib.schemas.jsonrpc
pub const version = '0.0.0'
const singleton = true
const default = false
// Default configuration for Zinit JSON-RPC API
pub const default_socket_path = '/tmp/zinit.sock'
// THIS THE THE SOURCE OF THE INFORMATION OF THIS FILE, HERE WE HAVE THE CONFIG OBJECT CONFIGURED AND MODELLED
@[heap]
pub struct ZinitRPC {
pub mut:
name string = 'default'
socket_path string = default_socket_path // Unix socket path for RPC server
rpc_client ?&jsonrpc.Client @[skip]
}
// your checking & initialization code if needed
fn obj_init(mycfg_ ZinitRPC) !ZinitRPC {
mut mycfg := mycfg_
if mycfg.socket_path == '' {
mycfg.socket_path = default_socket_path
}
// For now, we'll initialize the client when needed
// The actual client will be created in the factory
return mycfg
}
// Response structs based on OpenRPC specification
// OpenRPCSpec represents the OpenRPC specification structure
pub struct OpenRPCSpec {
pub mut:
openrpc string @[json: 'openrpc'] // OpenRPC version
info OpenRPCInfo @[json: 'info'] // API information
methods []OpenRPCMethod @[json: 'methods'] // Available methods
servers []OpenRPCServer @[json: 'servers'] // Server information
}
// OpenRPCInfo represents API information
pub struct OpenRPCInfo {
pub mut:
version string @[json: 'version'] // API version
title string @[json: 'title'] // API title
description string @[json: 'description'] // API description
license OpenRPCLicense @[json: 'license'] // License information
}
// OpenRPCLicense represents license information
pub struct OpenRPCLicense {
pub mut:
name string @[json: 'name'] // License name
}
// OpenRPCMethod represents an RPC method
pub struct OpenRPCMethod {
pub mut:
name string @[json: 'name'] // Method name
description string @[json: 'description'] // Method description
// Note: params and result are dynamic and would need more complex handling
}
// OpenRPCServer represents server information
pub struct OpenRPCServer {
pub mut:
name string @[json: 'name'] // Server name
url string @[json: 'url'] // Server URL
}
// ServiceStatus represents detailed status information for a service
pub struct ServiceStatus {
pub mut:
name string @[json: 'name'] // Service name
pid u32 @[json: 'pid'] // Process ID of the running service (if running)
state string @[json: 'state'] // Current state of the service (Running, Success, Error, etc.)
target string @[json: 'target'] // Target state of the service (Up, Down)
after map[string]string @[json: 'after'] // Dependencies of the service and their states
}
// ServiceConfig represents the configuration for a zinit service
pub struct ServiceConfig {
pub mut:
exec string @[json: 'exec'] // Command to run
test string @[json: 'test'] // Test command (optional)
oneshot bool @[json: 'oneshot'] // Whether the service should be restarted (maps to one_shot in Zinit)
after []string @[json: 'after'] // Services that must be running before this one starts
log string @[json: 'log'] // How to handle service output (null, ring, stdout)
env map[string]string @[json: 'env'] // Environment variables for the service
dir string @[json: 'dir'] // Working directory for the service
shutdown_timeout u64 @[json: 'shutdown_timeout'] // Maximum time to wait for service to stop during shutdown
}
// ServiceStats represents memory and CPU usage statistics for a service
pub struct ServiceStats {
pub mut:
name string @[json: 'name'] // Service name
pid u32 @[json: 'pid'] // Process ID of the service
memory_usage u64 @[json: 'memory_usage'] // Memory usage in bytes
cpu_usage f32 @[json: 'cpu_usage'] // CPU usage as a percentage (0-100)
children []ChildStats @[json: 'children'] // Stats for child processes
}
// ChildStats represents statistics for a child process
pub struct ChildStats {
pub mut:
pid u32 @[json: 'pid'] // Process ID of the child process
memory_usage u64 @[json: 'memory_usage'] // Memory usage in bytes
cpu_usage f32 @[json: 'cpu_usage'] // CPU usage as a percentage (0-100)
}
// ServiceCreateParams represents parameters for service_create method
pub struct ServiceCreateParams {
pub mut:
name string @[json: 'name'] // Name of the service to create
content ServiceConfig @[json: 'content'] // Configuration for the service
}
// ServiceKillParams represents parameters for service_kill method
pub struct ServiceKillParams {
pub mut:
name string @[json: 'name'] // Name of the service to kill
signal string @[json: 'signal'] // Signal to send (e.g., SIGTERM, SIGKILL)
}
// LogParams represents parameters for log streaming methods
@[params]
pub struct LogParams {
pub mut:
name string // Optional service name filter
}
/////////////NORMALLY NO NEED TO TOUCH
pub fn heroscript_dumps(obj ZinitRPC) !string {
return encoderhero.encode[ZinitRPC](obj)!
}
pub fn heroscript_loads(heroscript string) !ZinitRPC {
mut obj := encoderhero.decode[ZinitRPC](heroscript)!
return obj
}
// Factory function to create a new ZinitRPC client instance
@[params]
pub struct NewClientArgs {
pub mut:
name string = 'default'
socket_path string = default_socket_path
}
pub fn new_client(args NewClientArgs) !&ZinitRPC {
mut client := ZinitRPC{
name: args.name
socket_path: args.socket_path
}
client = obj_init(client)!
return &client
}

Binary file not shown.

View File

@@ -1,9 +1,7 @@
module code module code
import freeflowuniverse.herolib.mcp import freeflowuniverse.herolib.ui.console
import freeflowuniverse.herolib.mcp.logger
import os import os
import log
// ===== FILE AND DIRECTORY OPERATIONS ===== // ===== FILE AND DIRECTORY OPERATIONS =====
@@ -90,7 +88,7 @@ pub fn get_function_from_module(module_path string, function_name string) !Funct
return error('Failed to list V files in ${module_path}: ${err}') return error('Failed to list V files in ${module_path}: ${err}')
} }
log.error('Found ${v_files} V files in ${module_path}') console.print_stderr('Found ${v_files} V files in ${module_path}')
for v_file in v_files { for v_file in v_files {
// Read the file content // Read the file content
content := os.read_file(v_file) or { continue } content := os.read_file(v_file) or { continue }
@@ -114,13 +112,13 @@ pub fn get_function_from_module(module_path string, function_name string) !Funct
// RETURNS: // RETURNS:
// string - the type definition if found, or error if not found // string - the type definition if found, or error if not found
pub fn get_type_from_module(module_path string, type_name string) !string { pub fn get_type_from_module(module_path string, type_name string) !string {
println('Looking for type ${type_name} in module ${module_path}') console.print_debug('Looking for type ${type_name} in module ${module_path}')
v_files := list_v_files(module_path) or { v_files := list_v_files(module_path) or {
return error('Failed to list V files in ${module_path}: ${err}') return error('Failed to list V files in ${module_path}: ${err}')
} }
for v_file in v_files { for v_file in v_files {
println('Checking file: ${v_file}') console.print_debug('Checking file: ${v_file}')
content := os.read_file(v_file) or { return error('Failed to read file ${v_file}: ${err}') } content := os.read_file(v_file) or { return error('Failed to read file ${v_file}: ${err}') }
// Look for both regular and pub struct declarations // Look for both regular and pub struct declarations
@@ -139,13 +137,12 @@ pub fn get_type_from_module(module_path string, type_name string) !string {
type_import := content.split_into_lines().filter(it.contains('import') type_import := content.split_into_lines().filter(it.contains('import')
&& it.contains(type_name)) && it.contains(type_name))
if type_import.len > 0 { if type_import.len > 0 {
log.debug('debugzoooo')
mod := type_import[0].trim_space().trim_string_left('import ').all_before(' ') mod := type_import[0].trim_space().trim_string_left('import ').all_before(' ')
return get_type_from_module(get_module_dir(mod), type_name) return get_type_from_module(get_module_dir(mod), type_name)
} }
continue continue
} }
println('Found type ${type_name} in ${v_file} at position ${i}') console.print_debug('Found type ${type_name} in ${v_file} at position ${i}')
// Find the start of the struct definition including comments // Find the start of the struct definition including comments
mut comment_start := i mut comment_start := i
@@ -186,7 +183,7 @@ pub fn get_type_from_module(module_path string, type_name string) !string {
// Get the full struct definition including the struct declaration line // Get the full struct definition including the struct declaration line
full_struct := content.substr(line_start, closing_i + 1) full_struct := content.substr(line_start, closing_i + 1)
println('Found struct definition:\n${full_struct}') console.print_debug('Found struct definition:\n${full_struct}')
// Return the full struct definition // Return the full struct definition
return full_struct return full_struct
@@ -203,7 +200,7 @@ pub fn get_type_from_module(module_path string, type_name string) !string {
// RETURNS: // RETURNS:
// string - test results output, or error if test fails // string - test results output, or error if test fails
pub fn vtest(fullpath string) !string { pub fn vtest(fullpath string) !string {
logger.info('test ${fullpath}') console.print_item('test ${fullpath}')
if !os.exists(fullpath) { if !os.exists(fullpath) {
return error('File or directory does not exist: ${fullpath}') return error('File or directory does not exist: ${fullpath}')
} }
@@ -216,12 +213,12 @@ pub fn vtest(fullpath string) !string {
return results return results
} else { } else {
cmd := 'v -gc none -stats -enable-globals -show-c-output -keepc -n -w -cg -o /tmp/tester.c -g -cc tcc test ${fullpath}' cmd := 'v -gc none -stats -enable-globals -show-c-output -keepc -n -w -cg -o /tmp/tester.c -g -cc tcc test ${fullpath}'
logger.debug('Executing command: ${cmd}') console.print_debug('Executing command: ${cmd}')
result := os.execute(cmd) result := os.execute(cmd)
if result.exit_code != 0 { if result.exit_code != 0 {
return error('Test failed for ${fullpath} with exit code ${result.exit_code}\n${result.output}') return error('Test failed for ${fullpath} with exit code ${result.exit_code}\n${result.output}')
} else { } else {
logger.info('Test completed for ${fullpath}') console.print_item('Test completed for ${fullpath}')
} }
return 'Command: ${cmd}\nExit code: ${result.exit_code}\nOutput:\n${result.output}' return 'Command: ${cmd}\nExit code: ${result.exit_code}\nOutput:\n${result.output}'
} }
@@ -234,12 +231,12 @@ pub fn vtest(fullpath string) !string {
// string - vet results output, or error if vet fails // string - vet results output, or error if vet fails
fn vet_file(file string) !string { fn vet_file(file string) !string {
cmd := 'v vet -v -w ${file}' cmd := 'v vet -v -w ${file}'
logger.debug('Executing command: ${cmd}') console.print_debug('Executing command: ${cmd}')
result := os.execute(cmd) result := os.execute(cmd)
if result.exit_code != 0 { if result.exit_code != 0 {
return error('Vet failed for ${file} with exit code ${result.exit_code}\n${result.output}') return error('Vet failed for ${file} with exit code ${result.exit_code}\n${result.output}')
} else { } else {
logger.info('Vet completed for ${file}') console.print_item('Vet completed for ${file}')
} }
return 'Command: ${cmd}\nExit code: ${result.exit_code}\nOutput:\n${result.output}' return 'Command: ${cmd}\nExit code: ${result.exit_code}\nOutput:\n${result.output}'
} }
@@ -250,7 +247,7 @@ fn vet_file(file string) !string {
// RETURNS: // RETURNS:
// string - vet results output, or error if vet fails // string - vet results output, or error if vet fails
pub fn vvet(fullpath string) !string { pub fn vvet(fullpath string) !string {
logger.info('vet ${fullpath}') console.print_item('vet ${fullpath}')
if !os.exists(fullpath) { if !os.exists(fullpath) {
return error('File or directory does not exist: ${fullpath}') return error('File or directory does not exist: ${fullpath}')
} }
@@ -260,7 +257,7 @@ pub fn vvet(fullpath string) !string {
files := list_v_files(fullpath) or { return error('Error listing V files: ${err}') } files := list_v_files(fullpath) or { return error('Error listing V files: ${err}') }
for file in files { for file in files {
results += vet_file(file) or { results += vet_file(file) or {
logger.error('Failed to vet ${file}: ${err}') console.print_stderr('Failed to vet ${file}: ${err}')
return error('Failed to vet ${file}: ${err}') return error('Failed to vet ${file}: ${err}')
} }
results += '\n-----------------------\n' results += '\n-----------------------\n'

View File

@@ -1,10 +1,11 @@
module herocmds module herocmds
import freeflowuniverse.herolib.web.docusaurus import freeflowuniverse.herolib.web.docusaurus
import freeflowuniverse.herolib.core.pathlib
import os import os
import cli { Command, Flag } import cli { Command, Flag }
pub fn cmd_docusaurus(mut cmdroot Command) { pub fn cmd_docusaurus(mut cmdroot Command) Command {
mut cmd_run := Command{ mut cmd_run := Command{
name: 'docusaurus' name: 'docusaurus'
description: 'Generate, build, run docusaurus sites.' description: 'Generate, build, run docusaurus sites.'
@@ -35,7 +36,16 @@ pub fn cmd_docusaurus(mut cmdroot Command) {
name: 'path' name: 'path'
abbrev: 'p' abbrev: 'p'
// default: '' // default: ''
description: 'Path where docusaurus source is.' description: 'Path where docusaurus configuration is.'
})
cmd_run.add_flag(Flag{
flag: .string
required: false
name: 'buildpath'
abbrev: 'b'
// default: ''
description: 'Path where docusaurus build is.'
}) })
cmd_run.add_flag(Flag{ cmd_run.add_flag(Flag{
@@ -71,6 +81,14 @@ pub fn cmd_docusaurus(mut cmdroot Command) {
description: 'build dev version and publish.' description: 'build dev version and publish.'
}) })
cmd_run.add_flag(Flag{
flag: .bool
required: false
name: 'open'
abbrev: 'o'
description: 'open the site in browser.'
})
cmd_run.add_flag(Flag{ cmd_run.add_flag(Flag{
flag: .bool flag: .bool
required: false required: false
@@ -95,36 +113,86 @@ pub fn cmd_docusaurus(mut cmdroot Command) {
}) })
cmdroot.add_command(cmd_run) cmdroot.add_command(cmd_run)
return cmdroot
} }
fn cmd_docusaurus_execute(cmd Command) ! { fn cmd_docusaurus_execute(cmd Command) ! {
mut update := cmd.flags.get_bool('update') or { false } mut update := cmd.flags.get_bool('update') or { false }
mut init := cmd.flags.get_bool('new') or { false } mut init := cmd.flags.get_bool('new') or { false }
mut open := cmd.flags.get_bool('open') or { false }
mut url := cmd.flags.get_string('url') or { '' } mut url := cmd.flags.get_string('url') or { '' }
mut publish_path := cmd.flags.get_string('publish') or { '' } mut publish_path := cmd.flags.get_string('publish') or { '' }
mut deploykey := cmd.flags.get_string('deploykey') or { '' } mut deploykey := cmd.flags.get_string('deploykey') or { '' }
mut path := cmd.flags.get_string('path') or { '' } // --- Build Path Logic ---
mut build_path := cmd.flags.get_string('buildpath') or { '' }
if build_path == '' {
// Default build path if not provided (e.g., use CWD or a specific temp dir)
// Using CWD for now based on previous edits, adjust if needed
build_path = '${os.home_dir()}/hero/var/docusaurus'
}
// --- Start: Heroscript Path Logic ---
mut provided_path := cmd.flags.get_string('path') or { '' }
mut heroscript_source_path := ''
build_cfg_dir := os.join_path(build_path, 'cfg')
// target_heroscript_path := os.join_path(build_cfg_dir, 'config.heroscript')
if provided_path != '' {
if !os.exists(provided_path) || !os.is_file(provided_path) {
return error('Provided path "${provided_path}" does not exist or is not a file.')
}
// heroscript_source_path = provided_path
// // --- Copy Heroscript to Build Location ---
// os.mkdir_all(build_cfg_dir)!
// os.cp(heroscript_source_path, target_heroscript_path)!
} else {
// Path not provided, look in ./cfg/
mut cwd := os.getwd()
cfg_dir := os.join_path(cwd, 'cfg')
if !os.exists(cfg_dir) || !os.is_dir(cfg_dir) {
return error('Flag -path not provided and directory "./cfg" not found in the current working directory.')
}
// mut found_files := []string
// for file in os.ls(cfg_dir) or { []string{} } {
// if file.ends_with('.heroscript') {
// found_files << os.join_path(cfg_dir, file)
// }
// }
// if found_files.len == 1 {
// heroscript_source_path = found_files[0]
// os.mkdir_all(build_cfg_dir)!
// os.cp(heroscript_source_path, target_heroscript_path)!
// } else if found_files.len == 0 {
// return error('Flag -path not provided and no *.heroscript file found in "./cfg".')
// } else {
// return error('Flag -path not provided and multiple *.heroscript files found in "./cfg". Please specify one using -path.')
// }
}
// --- End: Heroscript Path Logic ---
mut buildpublish := cmd.flags.get_bool('buildpublish') or { false } mut buildpublish := cmd.flags.get_bool('buildpublish') or { false }
mut builddevpublish := cmd.flags.get_bool('builddevpublish') or { false } mut builddevpublish := cmd.flags.get_bool('builddevpublish') or { false }
mut dev := cmd.flags.get_bool('dev') or { false } mut dev := cmd.flags.get_bool('dev') or { false }
// if build== false && build== false && build== false { mut docs := docusaurus.new(
// eprintln("specify build, builddev or dev") update: update
// exit(1) build_path: build_path
// } // heroscript: os.read_file(target_heroscript_path)! // Read the copied heroscript
)!
mut docs := docusaurus.new(update: update)!
mut site := docs.get( mut site := docs.get(
url: url url: url
path: path build_path: build_path
update: update update: update
publish_path: publish_path publish_path: publish_path
deploykey: deploykey deploykey: deploykey
init: init init: init
open: open
)! )!
site.generate()!
if publish_path.len > 0 { if publish_path.len > 0 {
site.build()! site.build()!
} }
@@ -140,4 +208,8 @@ fn cmd_docusaurus_execute(cmd Command) ! {
if dev { if dev {
site.dev()! site.dev()!
} }
if open {
site.open()!
}
} }

View File

@@ -19,7 +19,7 @@ pub fn cmd_mdbook(mut cmdroot Command) {
example: example:
hero mdbook -u https://git.ourworld.tf/tfgrid/info_tfgrid/src/branch/main/heroscript hero mdbook -u https://git.threefold.info/tfgrid/info_tfgrid/src/branch/main/heroscript
If you do -gp it will pull newest book content from git and give error if there are local changes. If you do -gp it will pull newest book content from git and give error if there are local changes.
If you do -gr it will pull newest book content from git and overwrite local changes (careful). If you do -gr it will pull newest book content from git and overwrite local changes (careful).

View File

@@ -17,7 +17,7 @@ heroscript has numerous ways to execute actions using your hero tool.
example: example:
hero run -u https://git.ourworld.tf/threefold_coop/info_asimov/src/branch/main/heroscript hero run -u https://git.threefold.info/threefold_coop/info_asimov/src/branch/main/heroscript
Can also do -e or -st to see sourcetree Can also do -e or -st to see sourcetree

View File

@@ -13,7 +13,7 @@ pub mut:
git_reset bool git_reset bool
prio int = 50 prio int = 50
priorities map[int]string // filter and give priority, see filtersort method to know how to use priorities map[int]string // filter and give priority, see filtersort method to know how to use
session ?&base.Session // session ?&base.Session
} }
// get a new playbook, can scan a directory or just add text // get a new playbook, can scan a directory or just add text
@@ -45,7 +45,6 @@ pub fn new(args_ PlayBookNewArgs) !PlayBook {
git_branch: args.git_branch git_branch: args.git_branch
git_reset: args.git_reset git_reset: args.git_reset
prio: args.prio prio: args.prio
session: args.session
)! )!
} }

View File

@@ -47,10 +47,10 @@ fn test_parser() {
fn test_parser2() { fn test_parser2() {
mut pb := new( mut pb := new(
text: "!!play.run url:'https://git.ourworld.tf/despiegk/cfg/src/branch/main/myit/hetzner.md'" text: "!!play.run url:'https://git.threefold.info/despiegk/cfg/src/branch/main/myit/hetzner.md'"
) or { panic(err) } ) or { panic(err) }
mut a := pb.actions[0] mut a := pb.actions[0]
assert a.actor == 'play' assert a.actor == 'play'
assert a.name == 'run' assert a.name == 'run'
assert a.params.get('url')! == 'https://git.ourworld.tf/despiegk/cfg/src/branch/main/myit/hetzner.md' assert a.params.get('url')! == 'https://git.threefold.info/despiegk/cfg/src/branch/main/myit/hetzner.md'
} }

View File

@@ -36,7 +36,7 @@ pub fn (mut plbook PlayBook) add(args_ PlayBookNewArgs) ! {
} }
if p.is_file() { if p.is_file() {
c := p.read()! c := p.read()!
plbook.add(text: c, prio: args.prio, session: args_.session)! plbook.add(text: c, prio: args.prio)!
return return
} else if p.is_dir() { } else if p.is_dir() {
// get .md and .hero files from dir // get .md and .hero files from dir
@@ -44,10 +44,11 @@ pub fn (mut plbook PlayBook) add(args_ PlayBookNewArgs) ! {
mut paths := ol0.paths.clone() mut paths := ol0.paths.clone()
mut ol1 := p.list(recursive: true, regex: [r'.*\.hero$'])! mut ol1 := p.list(recursive: true, regex: [r'.*\.hero$'])!
paths << ol1.paths paths << ol1.paths
mut ol2 := p.list(recursive: true, regex: [r'.*\.heroscript$'])!
paths << ol2.paths
for mut p2 in paths { for mut p2 in paths {
c2 := p2.read()! c2 := p2.read()!
plbook.add(text: c2, prio: args.prio, session: args_.session)! plbook.add(text: c2, prio: args.prio)!
} }
return return
} }

View File

@@ -27,7 +27,7 @@ pub fn play_juggler(mut plbook playbook.PlayBook) ! {
port := p.get_int_default('port', 8000)! port := p.get_int_default('port', 8000)!
j = juggler.configure( j = juggler.configure(
url: 'https://git.ourworld.tf/projectmycelium/itenv' url: 'https://git.threefold.info/projectmycelium/itenv'
username: username username: username
password: password password: password
reset: true reset: true

View File

@@ -22,7 +22,7 @@ pub fn run(mut plbook playbook.PlayBook, dagu bool) ! {
play_core(mut plbook)! play_core(mut plbook)!
play_ssh(mut plbook)! play_ssh(mut plbook)!
play_git(mut plbook)! play_git(mut plbook)!
play_publisher(mut plbook)! // play_publisher(mut plbook)!
// play_zola(mut plbook)! // play_zola(mut plbook)!
// play_caddy(mut plbook)! // play_caddy(mut plbook)!
// play_juggler(mut plbook)! // play_juggler(mut plbook)!

View File

@@ -1,8 +1,8 @@
module playcmds module playcmds
import freeflowuniverse.herolib.core.playbook import freeflowuniverse.herolib.core.playbook
import freeflowuniverse.herolib.hero.publishing // import freeflowuniverse.herolib.hero.publishing
pub fn play_publisher(mut plbook playbook.PlayBook) ! { // pub fn play_publisher(mut plbook playbook.PlayBook) ! {
publishing.play(mut plbook)! // publishing.play(mut plbook)!
} // }

View File

@@ -11,14 +11,14 @@ fn test_play_publisher() {
s2 := " s2 := "
!!publisher.new_collection !!publisher.new_collection
url:'https://git.ourworld.tf/tfgrid/info_tfgrid/src/branch/main/collections' url:'https://git.threefold.info/tfgrid/info_tfgrid/src/branch/main/collections'
reset: false reset: false
pull: true pull: true
!!book.define !!book.define
name:'info_tfgrid' name:'info_tfgrid'
summary_url:'https://git.ourworld.tf/tfgrid/info_tfgrid/src/branch/development/books/tech/SUMMARY.md' summary_url:'https://git.threefold.info/tfgrid/info_tfgrid/src/branch/development/books/tech/SUMMARY.md'
title:'ThreeFold Technology' title:'ThreeFold Technology'
collections: 'about,dashboard,farmers,library,partners_utilization,tech,p2p' collections: 'about,dashboard,farmers,library,partners_utilization,tech,p2p'

View File

@@ -10,14 +10,29 @@ mut s:=base.session_new(
)! )!
// path string // Path to the code execution directory
// text string path string
// git_url string
// git_pull bool // Command text to execute (e.g., "ls -la")
// git_branch string text string
// git_reset bool
// execute bool = true // Git repository URL for version control
// session ?&base.Session is optional git_url string
// Pull latest changes from git
git_pull bool
// Git branch to use
git_branch string
// Reset repository before pull
git_reset bool
// Execute command after setup
execute bool = true
// Optional session object for state management
session ?&base.Session
mut plbook := playbook.new(text: "....",session:s) or { panic(err) } mut plbook := playbook.new(text: "....",session:s) or { panic(err) }

View File

@@ -1,14 +1,14 @@
security add-internet-password -s git.ourworld.tf -a despiegk -w mypassword security add-internet-password -s git.threefold.info -a despiegk -w mypassword
-s: The server (e.g., git.ourworld.tf). -s: The server (e.g., git.threefold.info).
-a: The account or username (e.g., despiegk). -a: The account or username (e.g., despiegk).
-w: The password (e.g., mypassword). -w: The password (e.g., mypassword).
security find-internet-password -s git.ourworld.tf -w security find-internet-password -s git.threefold.info -w
security delete-internet-password -s git.ourworld.tf security delete-internet-password -s git.threefold.info

View File

@@ -1,7 +1,6 @@
module currency module currency
//see encoder for binary // see encoder for binary
// import freeflowuniverse.herolib.data.encoder // import freeflowuniverse.herolib.data.encoder

View File

@@ -0,0 +1,86 @@
# Doctree Module
The `doctree` module is a V language library designed for scanning, processing, and exporting collections of documents. It provides a structured way to manage document-based content, making it suitable for generating documentation, building static websites, or processing any content organized into collections.
## Purpose
The primary goal of this module is to transform structured document collections into a format suitable for various outputs. It handles the complexities of finding collections, loading their content, processing includes, definitions, and macros, and exporting the final result while managing assets like images and files.
## Key Concepts
* **Tree:** The central component (`doctree.Tree`) that holds one or more `Collection` instances. It orchestrates the scanning, processing, and exporting of all contained collections.
* **Collection:** A directory that is marked as a collection by the presence of a `.collection` file. A collection groups related documents (pages, images, files) and can have its own configuration defined within the `.collection` file.
* **.collection file:** A file placed in a directory to designate it as a collection. This file can optionally contain parameters (using the `paramsparser` format) such as a custom name for the collection.
## How it Works (Workflow)
The typical workflow involves creating a `Tree`, scanning for collections, and then exporting the processed content.å
1. **Create Tree:** Initialize a `doctree.Tree` instance using `doctree.new()`.
2. **Scan:** Use the `tree.scan()` or `tree.scan_concurrent()` method, providing a path to a directory or a Git repository URL. The scanner recursively looks for directories containing a `.collection` file.
3. **Load Content:** For each identified collection, the module loads its content, including markdown pages, images, and other files.
4. **Process Content:** The loaded content is processed. This includes handling definitions, includes (content from other files), and macros (dynamic content generation or transformation).
5. **Generate Output Paths:** The module determines the final paths for all processed files and assets in the destination directory.
6. **Export:** The `tree.export()` method writes the processed content and assets to the specified destination directory, maintaining the desired structure.
## Usage (For Developers)
Here's a basic example of how to use the `doctree` module in your V project:
```v
import freeflowuniverse.herolib.data.doctree
// 1. Create a new Tree instance
mut tree := doctree.new(name: 'my_documentation')!
// 2. Scan a directory containing your collections
// Replace './docs' with the actual path to your document collections
tree.scan(path: './docs')!
// use from URL
//git_url string
//git_reset bool
//git_pull bool
tree.scan(git_url: 'https://git.threefold.info/tfgrid/docs_tfgrid4/src/branch/main/collections')!
// 3. Export the processed content to a destination directory
// Replace './output' with your desired output path
// if redis then the metadata will be put in redis
tree.export(destination: './output', redis:true)!
println('Documentation successfully exported to ./output')
```
## Structure of a Collection
A collection is a directory containing a `.collection` file. Inside a collection directory, you would typically organize your content like this:
```
my_collection/
├── .collection
├── page1.md
├── page2.md
├── images/
│ ├── image1.png
│ └── image2.jpg
└── files/
├── document.pdf
└── data.csv
```
Markdown files (`.md`) are treated as pages.
## Redis Structure
when using the export redis:true argument, which is default
in redis we will find
```bash
#redis hsets:
doctree:$collectionname $pagename $rel_path_in_collection
doctree:$collectionname $filename.$ext $rel_path_in_collection
doctree:meta $collectionname $collectionpath_on_disk
```

View File

@@ -1,6 +1,7 @@
module collection module collection
import freeflowuniverse.herolib.core.pathlib { Path } import freeflowuniverse.herolib.core.pathlib { Path }
import freeflowuniverse.herolib.core.base
import freeflowuniverse.herolib.ui.console import freeflowuniverse.herolib.ui.console
pub enum CollectionErrorCat { pub enum CollectionErrorCat {
@@ -52,8 +53,10 @@ pub fn (err ObjNotFound) msg() string {
} }
// write errors.md in the collection, this allows us to see what the errors are // write errors.md in the collection, this allows us to see what the errors are
pub fn (collection Collection) errors_report(dest_ string) ! { pub fn (collection Collection) errors_report(col_name string, dest_ string) ! {
// console.print_debug("====== errors report: ${dest_} : ${collection.errors.len}\n${collection.errors}") // console.print_debug("====== errors report: ${dest_} : ${collection.errors.len}\n${collection.errors}")
mut context := base.context()!
mut redis := context.redis()!
mut dest := pathlib.get_file(path: dest_, create: true)! mut dest := pathlib.get_file(path: dest_, create: true)!
if collection.errors.len == 0 { if collection.errors.len == 0 {
dest.delete()! dest.delete()!
@@ -61,4 +64,5 @@ pub fn (collection Collection) errors_report(dest_ string) ! {
} }
c := $tmpl('template/errors.md') c := $tmpl('template/errors.md')
dest.write(c)! dest.write(c)!
redis.hset('doctree:${col_name}', 'errors', 'errors.md')!
} }

View File

@@ -1,6 +1,7 @@
module collection module collection
import freeflowuniverse.herolib.core.pathlib import freeflowuniverse.herolib.core.pathlib
import freeflowuniverse.herolib.core.base
import freeflowuniverse.herolib.core.texttools.regext import freeflowuniverse.herolib.core.texttools.regext
import os import os
import freeflowuniverse.herolib.data.doctree.pointer import freeflowuniverse.herolib.data.doctree.pointer
@@ -15,6 +16,7 @@ pub mut:
keep_structure bool // wether the structure of the src collection will be preserved or not keep_structure bool // wether the structure of the src collection will be preserved or not
exclude_errors bool // wether error reporting should be exported as well exclude_errors bool // wether error reporting should be exported as well
replacer ?regext.ReplaceInstructions replacer ?regext.ReplaceInstructions
redis bool = true
} }
pub fn (mut c Collection) export(args CollectionExportArgs) ! { pub fn (mut c Collection) export(args CollectionExportArgs) ! {
@@ -23,19 +25,24 @@ pub fn (mut c Collection) export(args CollectionExportArgs) ! {
mut cfile := pathlib.get_file(path: dir_src.path + '/.collection', create: true)! // will auto save it mut cfile := pathlib.get_file(path: dir_src.path + '/.collection', create: true)! // will auto save it
cfile.write("name:${c.name} src:'${c.path.path}'")! cfile.write("name:${c.name} src:'${c.path.path}'")!
c.errors << export_pages(c.path.path, c.pages.values(), mut context := base.context()!
mut redis := context.redis()!
redis.hset('doctree:path', '${c.name}', dir_src.path)!
c.errors << export_pages(c.name, c.path.path, c.pages.values(),
dir_src: dir_src dir_src: dir_src
file_paths: args.file_paths file_paths: args.file_paths
keep_structure: args.keep_structure keep_structure: args.keep_structure
replacer: args.replacer replacer: args.replacer
redis: args.redis
)! )!
c.export_files(dir_src, args.reset)! c.export_files(c.name, dir_src, args.reset)!
c.export_images(dir_src, args.reset)! c.export_images(c.name, dir_src, args.reset)!
c.export_linked_pages(dir_src)! c.export_linked_pages(c.name, dir_src)!
if !args.exclude_errors { if !args.exclude_errors {
c.errors_report('${dir_src.path}/errors.md')! c.errors_report(c.name, '${dir_src.path}/errors.md')!
} }
} }
@@ -46,11 +53,16 @@ pub mut:
file_paths map[string]string file_paths map[string]string
keep_structure bool // wether the structure of the src collection will be preserved or not keep_structure bool // wether the structure of the src collection will be preserved or not
replacer ?regext.ReplaceInstructions replacer ?regext.ReplaceInstructions
redis bool = true
} }
// creates page file, processes page links, then writes page // creates page file, processes page links, then writes page
fn export_pages(col_path string, pages []&data.Page, args ExportPagesArgs) ![]CollectionError { fn export_pages(col_name string, col_path string, pages []&data.Page, args ExportPagesArgs) ![]CollectionError {
mut errors := []CollectionError{} mut errors := []CollectionError{}
mut context := base.context()!
mut redis := context.redis()!
for page in pages { for page in pages {
dest := if args.keep_structure { dest := if args.keep_structure {
relpath := page.path.path.trim_string_left(col_path) relpath := page.path.path.trim_string_left(col_path)
@@ -86,33 +98,42 @@ fn export_pages(col_path string, pages []&data.Page, args ExportPagesArgs) ![]Co
if mut replacer := args.replacer { if mut replacer := args.replacer {
markdown = replacer.replace(text: markdown)! markdown = replacer.replace(text: markdown)!
} }
dest_path.write(markdown)! dest_path.write(markdown)!
redis.hset('doctree:${col_name}', page.name, '${page.name}.md')!
} }
return errors return errors
} }
fn (c Collection) export_files(dir_src pathlib.Path, reset bool) ! { fn (c Collection) export_files(col_name string, dir_src pathlib.Path, reset bool) ! {
mut context := base.context()!
mut redis := context.redis()!
for _, file in c.files { for _, file in c.files {
mut d := '${dir_src.path}/img/${file.name}.${file.ext}' mut d := '${dir_src.path}/img/${file.name}.${file.ext}'
if reset || !os.exists(d) { if reset || !os.exists(d) {
file.copy(d)! file.copy(d)!
} }
redis.hset('doctree:${col_name}', file.name, 'img/${file.name}.${file.ext}')!
} }
} }
fn (c Collection) export_images(dir_src pathlib.Path, reset bool) ! { fn (c Collection) export_images(col_name string, dir_src pathlib.Path, reset bool) ! {
mut context := base.context()!
mut redis := context.redis()!
for _, file in c.images { for _, file in c.images {
mut d := '${dir_src.path}/img/${file.name}.${file.ext}' mut d := '${dir_src.path}/img/${file.name}.${file.ext}'
redis.hset('doctree:${col_name}', file.name, 'img/${file.name}.${file.ext}')!
if reset || !os.exists(d) { if reset || !os.exists(d) {
file.copy(d)! file.copy(d)!
} }
} }
} }
fn (c Collection) export_linked_pages(dir_src pathlib.Path) ! { fn (c Collection) export_linked_pages(col_name string, dir_src pathlib.Path) ! {
mut context := base.context()!
mut redis := context.redis()!
collection_linked_pages := c.get_collection_linked_pages()! collection_linked_pages := c.get_collection_linked_pages()!
mut linked_pages_file := pathlib.get_file(path: dir_src.path + '/.linkedpages', create: true)! mut linked_pages_file := pathlib.get_file(path: dir_src.path + '/.linkedpages', create: true)!
redis.hset('doctree:${col_name}', 'linkedpages', '${linked_pages_file.name()}.md')!
linked_pages_file.write(collection_linked_pages.join_lines())! linked_pages_file.write(collection_linked_pages.join_lines())!
} }

View File

@@ -14,6 +14,7 @@ pub mut:
exclude_errors bool // wether error reporting should be exported as well exclude_errors bool // wether error reporting should be exported as well
toreplace string toreplace string
concurrent bool = true concurrent bool = true
redis bool = true
} }
// export all collections to chosen directory . // export all collections to chosen directory .
@@ -50,6 +51,7 @@ pub fn (mut tree Tree) export(args TreeExportArgs) ! {
reset: args.reset reset: args.reset
keep_structure: args.keep_structure keep_structure: args.keep_structure
exclude_errors: args.exclude_errors exclude_errors: args.exclude_errors
redis: args.redis
// TODO: replacer: tree.replacer // TODO: replacer: tree.replacer
)! )!
}(mut col, dest_path, file_paths, args) }(mut col, dest_path, file_paths, args)
@@ -66,6 +68,7 @@ pub fn (mut tree Tree) export(args TreeExportArgs) ! {
keep_structure: args.keep_structure keep_structure: args.keep_structure
exclude_errors: args.exclude_errors exclude_errors: args.exclude_errors
replacer: tree.replacer replacer: tree.replacer
redis: args.redis
)! )!
} }
} }

View File

@@ -150,14 +150,14 @@ pub fn (mut d Decoder) get_ourtime() !ourtime.OurTime {
} }
pub fn (mut d Decoder) get_currency() !currency.Amount { pub fn (mut d Decoder) get_currency() !currency.Amount {
curstring:=d.get_string()! curstring := d.get_string()!
if curstring.len == 0 { if curstring.len == 0 {
return error('currency string is empty') return error('currency string is empty')
} }
currencyo := currency.get(curstring)! currencyo := currency.get(curstring)!
return currency.Amount{ return currency.Amount{
currency: currencyo currency: currencyo
val: d.get_f64()! val: d.get_f64()!
} }
} }

View File

@@ -108,7 +108,6 @@ pub fn (mut b Encoder) add_currency(data currency.Amount) {
b.add_f64(data.val) b.add_f64(data.val)
} }
// adds a float64 value // adds a float64 value
pub fn (mut b Encoder) add_f64(data f64) { pub fn (mut b Encoder) add_f64(data f64) {
// Convert f64 to bits first, then store as u64 // Convert f64 to bits first, then store as u64

View File

@@ -310,17 +310,17 @@ fn test_matchhashmap() {
} }
fn test_url() { fn test_url() {
text := "url:'https://git.ourworld.tf/despiegk/cfg/src/branch/main/myit/hetzner.md'" text := "url:'https://git.threefold.info/despiegk/cfg/src/branch/main/myit/hetzner.md'"
params := new(text)! params := new(text)!
myurl := params.get('url')! myurl := params.get('url')!
assert myurl == 'https://git.ourworld.tf/despiegk/cfg/src/branch/main/myit/hetzner.md' assert myurl == 'https://git.threefold.info/despiegk/cfg/src/branch/main/myit/hetzner.md'
} }
fn test_url2() { fn test_url2() {
text := "url: 'https://git.ourworld.tf/despiegk/cfg/src/branch/main/myit/hetzner.md'" text := "url: 'https://git.threefold.info/despiegk/cfg/src/branch/main/myit/hetzner.md'"
params := new(text)! params := new(text)!
myurl := params.get('url')! myurl := params.get('url')!
assert myurl == 'https://git.ourworld.tf/despiegk/cfg/src/branch/main/myit/hetzner.md' assert myurl == 'https://git.threefold.info/despiegk/cfg/src/branch/main/myit/hetzner.md'
} }

Some files were not shown because too many files have changed in this diff Show More