Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 0 additions & 49 deletions .github/notes/issue-12-phase-1-musl-static-linux.md

This file was deleted.

7 changes: 7 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,13 @@ set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_EXTENSIONS OFF)
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)

# Enable strict compiler warnings
if(MSVC)
add_compile_options(/W4 /WX)
else()
add_compile_options(-Wall -Wextra -Wpedantic -Werror)
endif()

find_package(CURL REQUIRED)
find_package(nlohmann_json CONFIG REQUIRED)
find_package(fmt CONFIG REQUIRED)
Expand Down
15 changes: 15 additions & 0 deletions docs/lua.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,13 +43,28 @@ See [Lua API Reference](modules/index.md) for the full runtime module surface. I
`require("yaaf")` exposes:

- `yaaf.args`: direct script arguments.
- `yaaf.platform`: current platform name: `"windows"`, `"linux"`, or `"osx"`.
- `yaaf.options`: parsed command options for command modules.
- `yaaf.positionals`: parsed command positionals for command modules.
- `yaaf.defaults.endpoint`: endpoint resolved from `.env` or the built-in default.
- `yaaf.defaults.model`: command default model.
- `yaaf.command(metadata)`: command metadata wrapper.
- `yaaf.read_line()`, `yaaf.write(text)`, and `yaaf.flush()`.

`require("process")` exposes process spawning and inter-process communication. Start child processes with configurable command, args, working directory, and environment variables. Read and write to stdin/stdout, check process status, and gracefully shut down processes:

```lua
local process = require("process")

local handle = process.start({
command = "echo",
args = { "Hello" },
})

local line, err = handle:read(5000)
handle:close()
```

`require("json")` exposes native JSON encode/decode helpers.

`require("http")` exposes the native low-level, request-based HTTP bridge for direct requests and future Lua-side provider implementations.
Expand Down
3 changes: 2 additions & 1 deletion docs/modules/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@ For built-in tool names, schemas, and a custom tool authoring guide, see [Tool R

## Runtime Modules

- [yaaf](yaaf.md): process, command metadata, stdin/stdout, and runtime defaults.
- [yaaf](yaaf.md): script arguments, command metadata, platform detection, stdin/stdout, and runtime defaults.
- [process](process.md): process spawning and inter-process communication.
- [json](json.md): JSON encode/decode helpers.
- [http](http.md): low-level request-based HTTP transport bridge.
- [llm](llm.md): provider-neutral generate, chat, and embed bridge.
Expand Down
150 changes: 150 additions & 0 deletions docs/modules/process.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
# process

`process` is a built-in module that provides process spawning and inter-process communication capabilities for Lua scripts.

Load it with:

```lua
local process = require("process")
```

## API Reference

### process.start(config) → handle

Spawns a new child process and returns a handle for interaction.

**Parameters:**

- `config` (table, required): Configuration table with the following fields:
- `command` (string, required): The executable to run (e.g., `"echo"`, `"/usr/bin/python3"`).
- `args` (array of strings, optional): Command-line arguments to pass to the executable.
- `cwd` (string, optional): Working directory for the child process. If not specified, inherits from parent.
- `env` (table of key-value pairs, optional): Environment variables to set. If `inherit_env` is `true`, these override parent environment.
- `inherit_env` (boolean, optional, default: `true`): If `true`, inherit parent environment variables. If `false`, only use `env` variables.

**Returns:** A process handle object for reading, writing, and managing the child process.

**Errors:** Throws an error if:
- `command` is missing or not a string
- `command` is an empty string
- The process fails to start (e.g., executable not found, permission denied)

### handle:write(data)

Writes data to the child process's stdin.

**Parameters:**

- `data` (string, required): Data to write to stdin.

**Errors:** Throws an error on I/O failure.

### handle:read(timeout_ms) → (line, error_string)

Reads one line from the child process's stdout.

**Parameters:**

- `timeout_ms` (integer, optional, default: `5000`): Timeout in milliseconds. Use `0` for non-blocking read or `-1` for indefinite wait.

**Returns:** A pair of values:
- On success: `(line, nil)` where `line` is a string.
- On timeout: `(nil, "timeout")` if no data arrives within the timeout period.
- On process exit: `(nil, "exited")` if the process has exited and no more data is available.
- On I/O error: `(nil, error_message)` where `error_message` describes the error.

### handle:is_alive() → bool

Checks whether the child process is still running.

**Returns:** `true` if the process is alive, `false` if it has exited.

### handle:shutdown(timeout_ms)

Gracefully shuts down the child process.

**Parameters:**

- `timeout_ms` (integer, optional, default: `1000`): Timeout in milliseconds for graceful shutdown. After this period, forceful termination may occur.

**Errors:** Throws an error on failure (e.g., if the process handle is invalid).

### handle:close()

Explicitly closes and cleans up the process handle. This is called automatically by the garbage collector if not invoked manually.

**Errors:** Throws an error on failure (e.g., if the process handle is invalid).

## Errors

Common error messages and conditions:

- `"process handle is nil or invalid"`: The handle object is corrupted or has been already closed.
- `"process.start() requires a table argument"`: The argument to `process.start()` was not a table.
- `"process.start() requires 'command' field (string)"`: The `command` field is missing or not a string.
- `"process.start() 'command' cannot be empty"`: The `command` field is an empty string.
- `"timeout"`: The `read()` call timed out waiting for data.
- `"exited"`: The process has exited and no more data is available.
- Platform-specific I/O errors: May occur during `write()`, `read()`, or `shutdown()`.

## Example

The following example runs the `echo` command with arguments, captures its output, and performs cleanup:

```lua
local process = require("process")

-- Start echo with two arguments
local handle = process.start({
command = "echo",
args = { "Hello", "World" },
})

-- Read output with 2-second timeout
local line, err = handle:read(2000)

if err then
print("Error: " .. err)
else
print("Output: " .. line)
end

-- Check if process is still alive
if handle:is_alive() then
print("Process still running")
else
print("Process has exited")
end

-- Graceful shutdown (if still running)
if handle:is_alive() then
handle:shutdown(1000)
end

-- Cleanup
handle:close()
```

For platform-specific process spawning, combine with [yaaf.platform](yaaf.md#fields) to adjust commands:

```lua
local process = require("process")
local yaaf = require("yaaf")

-- Use different commands based on platform
local cmd
if yaaf.platform == "windows" then
cmd = "cmd"
else
cmd = "/bin/sh"
end

local handle = process.start({
command = cmd,
args = { "-c", "echo Hello" },
})

local line, err = handle:read(2000)
handle:close()
```
25 changes: 25 additions & 0 deletions docs/modules/yaaf.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ local yaaf = require("yaaf")
## Fields

- `yaaf.args`: array of direct script arguments.
- `yaaf.platform`: current platform name: `"windows"`, `"linux"`, or `"osx"`.
- `yaaf.options`: parsed option table for a Lua command module.
- `yaaf.positionals`: parsed positional table for a Lua command module.
- `yaaf.defaults.endpoint`: resolved Ollama endpoint.
Expand Down Expand Up @@ -44,3 +45,27 @@ return yaaf.command({
end,
})
```

## Platform Detection

Use `yaaf.platform` to detect the operating system at runtime:

```lua
local yaaf = require("yaaf")
local process = require("process")

-- Use different commands based on platform
if yaaf.platform == "windows" then
-- Windows-specific behavior
local handle = process.start({
command = "cmd",
args = { "/c", "dir" },
})
else
-- Unix-like systems (Linux and macOS)
local handle = process.start({
command = "/bin/sh",
args = { "-c", "ls" },
})
end
```
4 changes: 4 additions & 0 deletions libyaaf/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -28,10 +28,12 @@ target_sources(libyaaf
mcp/mcp_schema_generated.cpp
${MCP_SCHEMA_SOURCES}
platform/executable_path.cpp
platform/platform_name.cpp
script/lua_runtime.cpp
script/modules/agent.cpp
script/modules/lua_module_utils.cpp
script/modules/script_http.cpp
script/modules/script_process.cpp
script/modules/script_yaaf.cpp
script/modules/script_json.cpp
script/modules/script_llm.cpp
Expand All @@ -41,6 +43,7 @@ target_sources(libyaaf

add_subdirectory(pch)
add_subdirectory(http)
add_subdirectory(process)

target_include_directories(libyaaf
PUBLIC
Expand All @@ -58,6 +61,7 @@ target_link_libraries(libyaaf
PUBLIC
CURL::libcurl
fmt::fmt
yaaf-process
PRIVATE
CLI11::CLI11
${LUA_LIBRARIES}
Expand Down
Loading
Loading