clean up OLLIE_* env vars: replace with XDG conventions
- Remove all OLLIE_*_PATH vars (TOOLS_PATH, CFG_PATH, DATA_PATH, etc.)
Use XDG_CONFIG_HOME/ollie/* and XDG_DATA_HOME/ollie/* instead
- Replace OLLIE_<TAG>_LOG per-component logging with single OLLIE_LOG={level}
- Replace OLLIE_USAGE_LOG with XDG_DATA_HOME-based path
- Replace OLLIE_OLLAMA_URL with standard OLLAMA_HOST (or omit entirely)
- Rename protocol markers: OLLIE_LISTEN_READY → ListenReady, OLLIE_9P_OPEN → Open
- Remove freeloader hooks from agent configs
- Update sandbox YAMLs to use XDG paths instead of OLLIE_*_PATH tokens
- Update shell tools to use $(dirname "$0") fallback instead of OLLIE_TOOLS_PATH
- Update docs and prompts accordingly
This commit is contained in:
parent
e93affe2e2
commit
d80a87eb86
10
AGENTS.md
10
AGENTS.md
|
|
@ -26,7 +26,7 @@ ollie/ ← you are here
|
||||||
├── contrib/elisp (Elisp) Emacs frontend (ellie.el)
|
├── contrib/elisp (Elisp) Emacs frontend (ellie.el)
|
||||||
├── data/agents/ Agent config JSONs (loaded at runtime)
|
├── data/agents/ Agent config JSONs (loaded at runtime)
|
||||||
├── data/prompts/ System prompt templates (markdown)
|
├── data/prompts/ System prompt templates (markdown)
|
||||||
├── data/tools/ Tool executables + .meta sidecar files (installed to $OLLIE_TOOLS_PATH)
|
├── data/tools/ Tool executables + .meta sidecar files (installed to $XDG_CONFIG_HOME/ollie/tools)
|
||||||
├── data/skills/ Domain knowledge modules (markdown)
|
├── data/skills/ Domain knowledge modules (markdown)
|
||||||
├── sandbox/ Landlock sandbox config YAML
|
├── sandbox/ Landlock sandbox config YAML
|
||||||
├── doc/ Architecture docs, usage guide
|
├── doc/ Architecture docs, usage guide
|
||||||
|
|
@ -94,7 +94,7 @@ just test-remote
|
||||||
## Architecture (key concepts)
|
## Architecture (key concepts)
|
||||||
1. **One integration surface: 9P filesystem (sessions at `session/{sname}/agent/{aname}/`). Tools, skills, memory on physical filesystem via env vars.
|
1. **One integration surface: 9P filesystem (sessions at `session/{sname}/agent/{aname}/`). Tools, skills, memory on physical filesystem via env vars.
|
||||||
2. **Agent loop** (`agent/loop.go`): Streaming LLM call → parse tool calls → dispatch → loop until no more tool calls or max steps.
|
2. **Agent loop** (`agent/loop.go`): Streaming LLM call → parse tool calls → dispatch → loop until no more tool calls or max steps.
|
||||||
3. **Tool dispatch** (`toolsrv/`): `shell` is built-in. All others are external scripts resolved from `OLLIE_TOOLS_PATH`.
|
3. **Tool dispatch** (`toolsrv/`): `shell` is built-in. All others are external scripts resolved from `$XDG_CONFIG_HOME/ollie/tools`.
|
||||||
4. **Sandbox** (`sandbox/`): Landlock-based. Config in `sandbox/*.yaml` defines filesystem access per profile.
|
4. **Sandbox** (`sandbox/`): Landlock-based. Config in `sandbox/*.yaml` defines filesystem access per profile.
|
||||||
5. **Backends** (`backend/`): Ollama, OpenAI-compatible, Anthropic, Copilot, Kiro, Gemini, CodeWhisperer. Selectable per-session.
|
5. **Backends** (`backend/`): Ollama, OpenAI-compatible, Anthropic, Copilot, Kiro, Gemini, CodeWhisperer. Selectable per-session.
|
||||||
6. **Prompts assembled at runtime**: Agent JSON `prompt` array specifies which prompt files to concatenate. Static prompt files can be included directly; the base system prompt is embedded in the binary and always prepended.
|
6. **Prompts assembled at runtime**: Agent JSON `prompt` array specifies which prompt files to concatenate. Static prompt files can be included directly; the base system prompt is embedded in the binary and always prepended.
|
||||||
|
|
@ -117,8 +117,8 @@ just test-remote
|
||||||
Config lives in `~/.config/ollie/env`. Key variables:
|
Config lives in `~/.config/ollie/env`. Key variables:
|
||||||
- `OLLIE_BACKEND` — default backend (ollama, openai, anthropic, copilot, kiro)
|
- `OLLIE_BACKEND` — default backend (ollama, openai, anthropic, copilot, kiro)
|
||||||
- `OLLIE_MODEL` — default model
|
- `OLLIE_MODEL` — default model
|
||||||
- `OLLIE_TOOLS_PATH` — where tool scripts live (default: `~/.config/ollie/tools`)
|
- Tools live at `$XDG_CONFIG_HOME/ollie/tools` (default: `~/.config/ollie/tools`)
|
||||||
- `OLLIE_MEMORY_PATH` — persistent memory directory
|
- Memory lives at `$XDG_CONFIG_HOME/ollie/memory` (default: `~/.config/ollie/memory`)
|
||||||
## Adding a new tool
|
## Adding a new tool
|
||||||
|
|
||||||
### Script-based tool (Python/Bash)
|
### Script-based tool (Python/Bash)
|
||||||
|
|
@ -139,7 +139,7 @@ Config lives in `~/.config/ollie/env`. Key variables:
|
||||||
|
|
||||||
The `.meta` file lives with the code that produces the tool, not in `data/tools/`. See the `lsp-tools` just target for the canonical pattern.
|
The `.meta` file lives with the code that produces the tool, not in `data/tools/`. See the `lsp-tools` just target for the canonical pattern.
|
||||||
|
|
||||||
Both paths produce the same result: an executable + `.meta` in `$OLLIE_TOOLS_PATH`.
|
Both paths produce the same result: an executable + `.meta` in `$XDG_CONFIG_HOME/ollie/tools`.
|
||||||
The registry doesn't distinguish between scripts and binaries.
|
The registry doesn't distinguish between scripts and binaries.
|
||||||
## Adding a new prompt
|
## Adding a new prompt
|
||||||
1. Write the markdown file in `data/prompts/`
|
1. Write the markdown file in `data/prompts/`
|
||||||
|
|
|
||||||
|
|
@ -27,7 +27,6 @@ See [`doc/usage.md`](doc/usage.md) for usage instructions.
|
||||||
|
|
||||||
```
|
```
|
||||||
OLLIE_BACKEND=openai # ollama | openai | anthropic | copilot | kiro (default: ollama)
|
OLLIE_BACKEND=openai # ollama | openai | anthropic | copilot | kiro (default: ollama)
|
||||||
OLLIE_OLLAMA_URL= # base URL for Ollama (default: http://localhost:11434)
|
|
||||||
OLLIE_OPENAI_URL=https://openrouter.ai/api
|
OLLIE_OPENAI_URL=https://openrouter.ai/api
|
||||||
OLLIE_OPENAI_KEY=sk-or-...
|
OLLIE_OPENAI_KEY=sk-or-...
|
||||||
OLLIE_ANTHROPIC_KEY=sk-ant-...
|
OLLIE_ANTHROPIC_KEY=sk-ant-...
|
||||||
|
|
@ -36,11 +35,9 @@ OLLIE_KIRO_TOKEN=... # bearer token or sqlite:// path (auto-detected f
|
||||||
OLLIE_MODEL=qwen/qwen3-235b-a22b
|
OLLIE_MODEL=qwen/qwen3-235b-a22b
|
||||||
OLLIE_COMPLETE_BACKEND=ollama # backend for code completion
|
OLLIE_COMPLETE_BACKEND=ollama # backend for code completion
|
||||||
OLLIE_COMPLETE_MODEL=qwen3:latest # model for code completion
|
OLLIE_COMPLETE_MODEL=qwen3:latest # model for code completion
|
||||||
OLLIE_ROUTE_BACKEND=ollama # backend for the /route classifier
|
|
||||||
OLLIE_ROUTE_MODEL=qwen3:8b # model for task routing
|
|
||||||
```
|
```
|
||||||
|
|
||||||
Everything lives under `~/.config/ollie/` by convention — tools in `tools/`, memory in `memory/`, agents in `agents/`, prompts in `prompts/`, skills in `skills/`. Shell environment variables take precedence over the env file.
|
Everything lives under `$XDG_CONFIG_HOME/ollie/` (default: `~/.config/ollie/`) — tools in `tools/`, memory in `memory/`, agents in `agents/`, prompts in `prompts/`, skills in `skills/`. Shell environment variables take precedence over the env file.
|
||||||
|
|
||||||
## What you can do
|
## What you can do
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -22,12 +22,8 @@ type usageEntry struct {
|
||||||
CostUSD float64 `json:"cost_usd"`
|
CostUSD float64 `json:"cost_usd"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// usageLogPath returns the path for the usage log file.
|
// usageLogPath returns the path for the usage log file: $XDG_DATA_HOME/ollie/usage.jsonl.
|
||||||
// Respects OLLIE_USAGE_LOG env var; defaults to ~/.local/share/ollie/usage.jsonl.
|
|
||||||
func usageLogPath() string {
|
func usageLogPath() string {
|
||||||
if p := os.Getenv("OLLIE_USAGE_LOG"); p != "" {
|
|
||||||
return p
|
|
||||||
}
|
|
||||||
return filepath.Join(paths.DataDir(), "usage.jsonl")
|
return filepath.Join(paths.DataDir(), "usage.jsonl")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -49,7 +49,6 @@ func loadEnvFile(path string) {
|
||||||
// precedence.
|
// precedence.
|
||||||
//
|
//
|
||||||
// OLLIE_BACKEND ollama | openai | openrouter | anthropic | copilot | kiro | gemini
|
// OLLIE_BACKEND ollama | openai | openrouter | anthropic | copilot | kiro | gemini
|
||||||
// OLLIE_OLLAMA_URL base URL for ollama (default: http://localhost:11434)
|
|
||||||
// OLLIE_OPENAI_URL base URL for openai-compatible backends
|
// OLLIE_OPENAI_URL base URL for openai-compatible backends
|
||||||
// OLLIE_OPENAI_KEY API key (required for openai/openrouter)
|
// OLLIE_OPENAI_KEY API key (required for openai/openrouter)
|
||||||
// OLLIE_ANTHROPIC_KEY API key (required for anthropic)
|
// OLLIE_ANTHROPIC_KEY API key (required for anthropic)
|
||||||
|
|
@ -89,7 +88,7 @@ func newFromEnv(envFile string) (Backend, error) {
|
||||||
func newBackend(which string) (Backend, error) {
|
func newBackend(which string) (Backend, error) {
|
||||||
switch which {
|
switch which {
|
||||||
case "ollama":
|
case "ollama":
|
||||||
return NewOllama(os.Getenv("OLLIE_OLLAMA_URL"))
|
return NewOllama(os.Getenv("OLLAMA_HOST"))
|
||||||
case "openai", "openrouter":
|
case "openai", "openrouter":
|
||||||
url := os.Getenv("OLLIE_OPENAI_URL")
|
url := os.Getenv("OLLIE_OPENAI_URL")
|
||||||
key := os.Getenv("OLLIE_OPENAI_KEY")
|
key := os.Getenv("OLLIE_OPENAI_KEY")
|
||||||
|
|
|
||||||
|
|
@ -40,7 +40,7 @@ func TestNewFromEnv_NoBackendErrors(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestNewFromEnv_Ollama(t *testing.T) {
|
func TestNewFromEnv_Ollama(t *testing.T) {
|
||||||
setEnv(t, map[string]string{"OLLIE_BACKEND": "ollama", "OLLIE_OLLAMA_URL": "http://myhost:11434"})
|
setEnv(t, map[string]string{"OLLIE_BACKEND": "ollama"})
|
||||||
b, err := newFromEnv("/nonexistent")
|
b, err := newFromEnv("/nonexistent")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
|
|
|
||||||
|
|
@ -192,7 +192,7 @@ func cmdRead(path string, openMarker bool) {
|
||||||
// connection-aware GUI clients distinguish an opened operation from a
|
// connection-aware GUI clients distinguish an opened operation from a
|
||||||
// merely started local helper process.
|
// merely started local helper process.
|
||||||
if openMarker {
|
if openMarker {
|
||||||
fmt.Fprintln(os.Stderr, "OLLIE_9P_OPEN")
|
fmt.Fprintln(os.Stderr, "Open")
|
||||||
}
|
}
|
||||||
if _, err := io.Copy(os.Stdout, fid); err != nil {
|
if _, err := io.Copy(os.Stdout, fid); err != nil {
|
||||||
fatalf("read %s: %v", path, err)
|
fatalf("read %s: %v", path, err)
|
||||||
|
|
|
||||||
|
|
@ -112,7 +112,7 @@ func serveSocket(ctx context.Context, srv *toolsrv.Server, sockPath string) {
|
||||||
defer ln.Close()
|
defer ln.Close()
|
||||||
defer os.Remove(sockPath)
|
defer os.Remove(sockPath)
|
||||||
|
|
||||||
fmt.Fprintln(os.Stderr, "OLLIE_LISTEN_READY")
|
fmt.Fprintln(os.Stderr, "ListenReady")
|
||||||
|
|
||||||
var mu sync.Mutex
|
var mu sync.Mutex
|
||||||
var activeConns []net.Conn
|
var activeConns []net.Conn
|
||||||
|
|
|
||||||
|
|
@ -26,7 +26,7 @@ filesystem:
|
||||||
- "{HOME}/.local/bin"
|
- "{HOME}/.local/bin"
|
||||||
- "{HOME}/.nvm"
|
- "{HOME}/.nvm"
|
||||||
- "{HOME}/.pyenv"
|
- "{HOME}/.pyenv"
|
||||||
- "{OLLIE_TOOLS_PATH}"
|
- "{XDG_CONFIG_HOME}/ollie/tools"
|
||||||
rwx:
|
rwx:
|
||||||
- "{CWD}"
|
- "{CWD}"
|
||||||
- "{TMPDIR}"
|
- "{TMPDIR}"
|
||||||
|
|
@ -41,7 +41,8 @@ env:
|
||||||
- TMPDIR
|
- TMPDIR
|
||||||
- GOPATH
|
- GOPATH
|
||||||
- GOBIN
|
- GOBIN
|
||||||
- OLLIE_TOOLS_PATH
|
- XDG_CONFIG_HOME
|
||||||
|
- XDG_DATA_HOME
|
||||||
- SSH_AUTH_SOCK
|
- SSH_AUTH_SOCK
|
||||||
network:
|
network:
|
||||||
unrestricted: true
|
unrestricted: true
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
"prompt": [
|
"prompt": [
|
||||||
"$OLLIE_CFG_PATH/prompts/user-preferences.md",
|
"$XDG_CONFIG_HOME/ollie/prompts/user-preferences.md",
|
||||||
"$OLLIE_CFG_PATH/prompts/agent-copilot.md"
|
"$XDG_CONFIG_HOME/ollie/prompts/agent-copilot.md"
|
||||||
],
|
],
|
||||||
"autoLoad": [
|
"autoLoad": [
|
||||||
"shell",
|
"shell",
|
||||||
|
|
@ -10,10 +10,12 @@
|
||||||
"file_glob",
|
"file_glob",
|
||||||
"file_grep"
|
"file_grep"
|
||||||
],
|
],
|
||||||
"allowTools": ["file_read", "file_glob", "file_grep"],
|
"allowTools": [
|
||||||
|
"file_read",
|
||||||
|
"file_glob",
|
||||||
|
"file_grep"
|
||||||
|
],
|
||||||
"hooks": {
|
"hooks": {
|
||||||
"turnError": [
|
"turnError": []
|
||||||
"$OLLIE_CFG_PATH/scripts/x/freeloader $OLLIE_SESSION_ID"
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
"prompt": [
|
"prompt": [
|
||||||
"$OLLIE_CFG_PATH/prompts/user-preferences.md",
|
"$XDG_CONFIG_HOME/ollie/prompts/user-preferences.md",
|
||||||
"$OLLIE_CFG_PATH/prompts/agent-coding.md",
|
"$XDG_CONFIG_HOME/ollie/prompts/agent-coding.md",
|
||||||
"bash -c 'd=$PWD; while [ \"$d\" != / ]; do [ -d \"$d/.beads\" ] && exec bd prime; [ ! -e \"$d/.git\" ] && break; d=$(dirname \"$d\"); done' 2>/dev/null || true"
|
"bash -c 'd=$PWD; while [ \"$d\" != / ]; do [ -d \"$d/.beads\" ] && exec bd prime; [ ! -e \"$d/.git\" ] && break; d=$(dirname \"$d\"); done' 2>/dev/null || true"
|
||||||
],
|
],
|
||||||
"autoLoad": [
|
"autoLoad": [
|
||||||
|
|
@ -16,12 +16,8 @@
|
||||||
"temperature": 0.5,
|
"temperature": 0.5,
|
||||||
"maxTokens": 16384,
|
"maxTokens": 16384,
|
||||||
"hooks": {
|
"hooks": {
|
||||||
"agentSpawn": [
|
"agentSpawn": [],
|
||||||
],
|
"postTurn": [],
|
||||||
"postTurn": [
|
"turnError": []
|
||||||
],
|
|
||||||
"turnError": [
|
|
||||||
"$OLLIE_CFG_PATH/scripts/x/freeloader $OLLIE_SESSION_ID"
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
"prompt": [
|
"prompt": [
|
||||||
"$OLLIE_CFG_PATH/prompts/user-preferences.md",
|
"$XDG_CONFIG_HOME/ollie/prompts/user-preferences.md",
|
||||||
"$OLLIE_CFG_PATH/prompts/agent-coding.md",
|
"$XDG_CONFIG_HOME/ollie/prompts/agent-coding.md",
|
||||||
"bash -c 'd=$PWD; while [ \"$d\" != / ]; do [ -d \"$d/.beads\" ] && exec bd prime; [ ! -e \"$d/.git\" ] && break; d=$(dirname \"$d\"); done' 2>/dev/null || true"
|
"bash -c 'd=$PWD; while [ \"$d\" != / ]; do [ -d \"$d/.beads\" ] && exec bd prime; [ ! -e \"$d/.git\" ] && break; d=$(dirname \"$d\"); done' 2>/dev/null || true"
|
||||||
],
|
],
|
||||||
"autoLoad": [
|
"autoLoad": [
|
||||||
|
|
@ -42,8 +42,6 @@
|
||||||
"temperature": 0.4,
|
"temperature": 0.4,
|
||||||
"maxTokens": 16384,
|
"maxTokens": 16384,
|
||||||
"hooks": {
|
"hooks": {
|
||||||
"turnError": [
|
"turnError": []
|
||||||
"$OLLIE_CFG_PATH/scripts/x/freeloader $OLLIE_SESSION_ID"
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
"prompt": [
|
"prompt": [
|
||||||
"$OLLIE_CFG_PATH/prompts/user-preferences.md",
|
"$XDG_CONFIG_HOME/ollie/prompts/user-preferences.md",
|
||||||
"$OLLIE_CFG_PATH/prompts/agent-explorer.md"
|
"$XDG_CONFIG_HOME/ollie/prompts/agent-explorer.md"
|
||||||
],
|
],
|
||||||
"autoLoad": [
|
"autoLoad": [
|
||||||
"shell",
|
"shell",
|
||||||
|
|
@ -19,8 +19,6 @@
|
||||||
"temperature": 0.3,
|
"temperature": 0.3,
|
||||||
"maxTokens": 16384,
|
"maxTokens": 16384,
|
||||||
"hooks": {
|
"hooks": {
|
||||||
"turnError": [
|
"turnError": []
|
||||||
"$OLLIE_CFG_PATH/scripts/x/freeloader $OLLIE_SESSION_ID"
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
"prompt": [
|
"prompt": [
|
||||||
"$OLLIE_CFG_PATH/prompts/user-preferences.md",
|
"$XDG_CONFIG_HOME/ollie/prompts/user-preferences.md",
|
||||||
"$OLLIE_CFG_PATH/prompts/agent-librarian.md"
|
"$XDG_CONFIG_HOME/ollie/prompts/agent-librarian.md"
|
||||||
],
|
],
|
||||||
"autoLoad": [
|
"autoLoad": [
|
||||||
"shell",
|
"shell",
|
||||||
|
|
@ -23,8 +23,6 @@
|
||||||
"temperature": 0.3,
|
"temperature": 0.3,
|
||||||
"maxTokens": 16384,
|
"maxTokens": 16384,
|
||||||
"hooks": {
|
"hooks": {
|
||||||
"turnError": [
|
"turnError": []
|
||||||
"$OLLIE_CFG_PATH/scripts/x/freeloader $OLLIE_SESSION_ID"
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
"prompt": [
|
"prompt": [
|
||||||
"$OLLIE_CFG_PATH/prompts/user-preferences.md",
|
"$XDG_CONFIG_HOME/ollie/prompts/user-preferences.md",
|
||||||
"$OLLIE_CFG_PATH/prompts/agent-navigator.md"
|
"$XDG_CONFIG_HOME/ollie/prompts/agent-navigator.md"
|
||||||
],
|
],
|
||||||
"autoLoad": [
|
"autoLoad": [
|
||||||
"shell",
|
"shell",
|
||||||
|
|
@ -20,8 +20,6 @@
|
||||||
"maxTokens": 8192,
|
"maxTokens": 8192,
|
||||||
"maxSteps": 30,
|
"maxSteps": 30,
|
||||||
"hooks": {
|
"hooks": {
|
||||||
"turnError": [
|
"turnError": []
|
||||||
"$OLLIE_CFG_PATH/scripts/x/freeloader $OLLIE_SESSION_ID"
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
"prompt": [
|
"prompt": [
|
||||||
"$OLLIE_CFG_PATH/prompts/user-preferences.md",
|
"$XDG_CONFIG_HOME/ollie/prompts/user-preferences.md",
|
||||||
"$OLLIE_CFG_PATH/prompts/agent-taskmanager.md"
|
"$XDG_CONFIG_HOME/ollie/prompts/agent-taskmanager.md"
|
||||||
],
|
],
|
||||||
"autoLoad": [
|
"autoLoad": [
|
||||||
"shell",
|
"shell",
|
||||||
|
|
@ -31,8 +31,6 @@
|
||||||
"temperature": 0.3,
|
"temperature": 0.3,
|
||||||
"maxTokens": 8192,
|
"maxTokens": 8192,
|
||||||
"hooks": {
|
"hooks": {
|
||||||
"turnError": [
|
"turnError": []
|
||||||
"$OLLIE_CFG_PATH/scripts/x/freeloader $OLLIE_SESSION_ID"
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
"prompt": [
|
"prompt": [
|
||||||
"$OLLIE_CFG_PATH/prompts/user-preferences.md",
|
"$XDG_CONFIG_HOME/ollie/prompts/user-preferences.md",
|
||||||
"$OLLIE_CFG_PATH/prompts/agent-theo.md"
|
"$XDG_CONFIG_HOME/ollie/prompts/agent-theo.md"
|
||||||
],
|
],
|
||||||
"autoLoad": [
|
"autoLoad": [
|
||||||
"shell",
|
"shell",
|
||||||
|
|
@ -17,8 +17,6 @@
|
||||||
"temperature": 0.3,
|
"temperature": 0.3,
|
||||||
"maxTokens": 8192,
|
"maxTokens": 8192,
|
||||||
"hooks": {
|
"hooks": {
|
||||||
"turnError": [
|
"turnError": []
|
||||||
"$OLLIE_CFG_PATH/scripts/x/freeloader $OLLIE_SESSION_ID"
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -3,7 +3,7 @@
|
||||||
# Tools receive a JSON object on stdin.
|
# Tools receive a JSON object on stdin.
|
||||||
#
|
#
|
||||||
# Usage:
|
# Usage:
|
||||||
# source "${OLLIE_TOOLS_PATH:-$(dirname "$0")}/_lib/args.sh"
|
# source "${XDG_CONFIG_HOME:-$HOME/.config}/ollie/tools/_lib/args.sh"
|
||||||
# path=$(arg_require "path")
|
# path=$(arg_require "path")
|
||||||
# start=$(arg_get "start" "")
|
# start=$(arg_get "start" "")
|
||||||
#
|
#
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,7 @@
|
||||||
# ollie:end
|
# ollie:end
|
||||||
set -e
|
set -e
|
||||||
|
|
||||||
source "${OLLIE_TOOLS_PATH:-$(dirname "$0")}/_lib/args.sh"
|
source "$(dirname "$0")/_lib/args.sh"
|
||||||
|
|
||||||
OUTPUT=$(arg_get "output" "${TMPDIR:-/tmp}/browser_capture.png")
|
OUTPUT=$(arg_get "output" "${TMPDIR:-/tmp}/browser_capture.png")
|
||||||
URL_PATTERN=$(arg_get "url_pattern" "localhost")
|
URL_PATTERN=$(arg_get "url_pattern" "localhost")
|
||||||
|
|
|
||||||
|
|
@ -19,7 +19,7 @@ import sys
|
||||||
import os
|
import os
|
||||||
import difflib
|
import difflib
|
||||||
|
|
||||||
sys.path.insert(0, os.environ.get('OLLIE_TOOLS_PATH', os.path.dirname(os.path.abspath(__file__))))
|
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
|
||||||
from _lib.args import parse_args
|
from _lib.args import parse_args
|
||||||
|
|
||||||
def format_diff(path, old, new):
|
def format_diff(path, old, new):
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,7 @@
|
||||||
# ```
|
# ```
|
||||||
# ollie:end
|
# ollie:end
|
||||||
|
|
||||||
source "${OLLIE_TOOLS_PATH:-$(dirname "$0")}/_lib/args.sh"
|
source "$(dirname "$0")/_lib/args.sh"
|
||||||
|
|
||||||
pattern=$(arg_require "pattern")
|
pattern=$(arg_require "pattern")
|
||||||
search_path=$(arg_get "search_dir" "$PWD")
|
search_path=$(arg_get "search_dir" "$PWD")
|
||||||
|
|
|
||||||
|
|
@ -26,7 +26,7 @@ import os
|
||||||
import subprocess
|
import subprocess
|
||||||
import shutil
|
import shutil
|
||||||
|
|
||||||
sys.path.insert(0, os.environ.get('OLLIE_TOOLS_PATH', os.path.dirname(os.path.abspath(__file__))))
|
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
|
||||||
from _lib.args import parse_args
|
from _lib.args import parse_args
|
||||||
|
|
||||||
args = parse_args()
|
args = parse_args()
|
||||||
|
|
|
||||||
|
|
@ -29,7 +29,7 @@ import sys
|
||||||
import os
|
import os
|
||||||
from itertools import islice
|
from itertools import islice
|
||||||
|
|
||||||
sys.path.insert(0, os.environ.get('OLLIE_TOOLS_PATH', os.path.dirname(os.path.abspath(__file__))))
|
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
|
||||||
from _lib.args import parse_args
|
from _lib.args import parse_args
|
||||||
|
|
||||||
MAX_LINES = 500
|
MAX_LINES = 500
|
||||||
|
|
|
||||||
|
|
@ -18,7 +18,7 @@ import sys
|
||||||
import os
|
import os
|
||||||
import difflib
|
import difflib
|
||||||
|
|
||||||
sys.path.insert(0, os.environ.get('OLLIE_TOOLS_PATH', os.path.dirname(os.path.abspath(__file__))))
|
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
|
||||||
from _lib.args import parse_args
|
from _lib.args import parse_args
|
||||||
|
|
||||||
def format_diff(path, old, new):
|
def format_diff(path, old, new):
|
||||||
|
|
|
||||||
|
|
@ -391,7 +391,7 @@ def main():
|
||||||
print("Also need: gir1.2-atspi-2.0 (Debian/Ubuntu) or gobject-introspection with Atspi typelib", file=sys.stderr)
|
print("Also need: gir1.2-atspi-2.0 (Debian/Ubuntu) or gobject-introspection with Atspi typelib", file=sys.stderr)
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
sys.path.insert(0, os.environ.get('OLLIE_TOOLS_PATH', os.path.dirname(os.path.abspath(__file__))))
|
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
|
||||||
from _lib.args import parse_args as _parse
|
from _lib.args import parse_args as _parse
|
||||||
_args = _parse()
|
_args = _parse()
|
||||||
cmd = _args.require("action")
|
cmd = _args.require("action")
|
||||||
|
|
|
||||||
|
|
@ -19,7 +19,7 @@ SERVICE="org.kde.ActivityManager"
|
||||||
OBJ_PATH="/ActivityManager/Activities"
|
OBJ_PATH="/ActivityManager/Activities"
|
||||||
IFACE="org.kde.ActivityManager.Activities"
|
IFACE="org.kde.ActivityManager.Activities"
|
||||||
|
|
||||||
source "${OLLIE_TOOLS_PATH:-$(dirname "$0")}/_lib/args.sh"
|
source "$(dirname "$0")/_lib/args.sh"
|
||||||
|
|
||||||
cmd=$(arg_get "action" "list")
|
cmd=$(arg_get "action" "list")
|
||||||
name=$(arg_get "name" "")
|
name=$(arg_get "name" "")
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,7 @@
|
||||||
# ollie:end
|
# ollie:end
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
|
|
||||||
source "${OLLIE_TOOLS_PATH:-$(dirname "$0")}/_lib/args.sh"
|
source "$(dirname "$0")/_lib/args.sh"
|
||||||
|
|
||||||
cmd=$(arg_get "action" "list")
|
cmd=$(arg_get "action" "list")
|
||||||
name=$(arg_get "name" "")
|
name=$(arg_get "name" "")
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,7 @@
|
||||||
# ollie:end
|
# ollie:end
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
|
|
||||||
source "${OLLIE_TOOLS_PATH:-$(dirname "$0")}/_lib/args.sh"
|
source "$(dirname "$0")/_lib/args.sh"
|
||||||
|
|
||||||
SERVICE="org.kde.Solid.PowerManagement"
|
SERVICE="org.kde.Solid.PowerManagement"
|
||||||
OBJ_PATH="/org/kde/Solid/PowerManagement/Actions/BrightnessControl"
|
OBJ_PATH="/org/kde/Solid/PowerManagement/Actions/BrightnessControl"
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,7 @@
|
||||||
# ollie:end
|
# ollie:end
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
|
|
||||||
source "${OLLIE_TOOLS_PATH:-$(dirname "$0")}/_lib/args.sh"
|
source "$(dirname "$0")/_lib/args.sh"
|
||||||
|
|
||||||
SERVICE="org.kde.klipper"
|
SERVICE="org.kde.klipper"
|
||||||
PATH_OBJ="/klipper"
|
PATH_OBJ="/klipper"
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,7 @@
|
||||||
# ollie:end
|
# ollie:end
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
|
|
||||||
source "${OLLIE_TOOLS_PATH:-$(dirname "$0")}/_lib/args.sh"
|
source "$(dirname "$0")/_lib/args.sh"
|
||||||
|
|
||||||
query=$(arg_require "query")
|
query=$(arg_require "query")
|
||||||
file_type=$(arg_get "type" "")
|
file_type=$(arg_get "type" "")
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,7 @@
|
||||||
# ollie:end
|
# ollie:end
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
|
|
||||||
source "${OLLIE_TOOLS_PATH:-$(dirname "$0")}/_lib/args.sh"
|
source "$(dirname "$0")/_lib/args.sh"
|
||||||
|
|
||||||
cmd=$(arg_require "action")
|
cmd=$(arg_require "action")
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -258,7 +258,7 @@ def build_playlist(dest, description, root, count, output=None):
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
import os as _os
|
import os as _os
|
||||||
sys.path.insert(0, _os.environ.get('OLLIE_TOOLS_PATH', _os.path.dirname(_os.path.abspath(__file__))))
|
sys.path.insert(0, _os.path.dirname(_os.path.abspath(__file__)))
|
||||||
from _lib.args import parse_args as _parse_args
|
from _lib.args import parse_args as _parse_args
|
||||||
_a = _parse_args()
|
_a = _parse_args()
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,7 @@ import sys
|
||||||
import os
|
import os
|
||||||
import subprocess
|
import subprocess
|
||||||
|
|
||||||
sys.path.insert(0, os.environ.get('OLLIE_TOOLS_PATH', os.path.dirname(os.path.abspath(__file__))))
|
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
|
||||||
from _lib.args import parse_args
|
from _lib.args import parse_args
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
|
|
|
||||||
|
|
@ -35,7 +35,7 @@ IFACE = "org.kde.KWin.ScreenShot2"
|
||||||
|
|
||||||
|
|
||||||
def parse_args():
|
def parse_args():
|
||||||
sys.path.insert(0, os.environ.get('OLLIE_TOOLS_PATH', os.path.dirname(os.path.abspath(__file__))))
|
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
|
||||||
from _lib.args import parse_args as _parse
|
from _lib.args import parse_args as _parse
|
||||||
_a = _parse()
|
_a = _parse()
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -23,7 +23,7 @@ IFACE="org.kde.KGlobalAccel"
|
||||||
COMP_OBJ="/component"
|
COMP_OBJ="/component"
|
||||||
COMP_IFACE="org.kde.kglobalaccel.Component"
|
COMP_IFACE="org.kde.kglobalaccel.Component"
|
||||||
|
|
||||||
source "${OLLIE_TOOLS_PATH:-$(dirname "$0")}/_lib/args.sh"
|
source "$(dirname "$0")/_lib/args.sh"
|
||||||
|
|
||||||
cmd=$(arg_get "action" "list")
|
cmd=$(arg_get "action" "list")
|
||||||
component=$(arg_get "component" "")
|
component=$(arg_get "component" "")
|
||||||
|
|
|
||||||
|
|
@ -373,7 +373,7 @@ def switch_desktop(num):
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
sys.path.insert(0, os.environ.get('OLLIE_TOOLS_PATH', os.path.dirname(os.path.abspath(__file__))))
|
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
|
||||||
from _lib.args import parse_args as _parse
|
from _lib.args import parse_args as _parse
|
||||||
args = _parse()
|
args = _parse()
|
||||||
cmd = args.require("action")
|
cmd = args.require("action")
|
||||||
|
|
|
||||||
|
|
@ -18,7 +18,7 @@ import base64
|
||||||
import json
|
import json
|
||||||
import mimetypes
|
import mimetypes
|
||||||
|
|
||||||
sys.path.insert(0, os.environ.get('OLLIE_TOOLS_PATH', os.path.dirname(os.path.abspath(__file__))))
|
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
|
||||||
from _lib.args import parse_args
|
from _lib.args import parse_args
|
||||||
|
|
||||||
args = parse_args()
|
args = parse_args()
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@ import os
|
||||||
import subprocess
|
import subprocess
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
sys.path.insert(0, os.environ.get('OLLIE_TOOLS_PATH', os.path.dirname(os.path.abspath(__file__))))
|
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
|
||||||
from _lib.args import parse_args
|
from _lib.args import parse_args
|
||||||
|
|
||||||
LOGSEQ_URL = os.environ.get("LOGSEQ_MCP_URL", "http://127.0.0.1:12315")
|
LOGSEQ_URL = os.environ.get("LOGSEQ_MCP_URL", "http://127.0.0.1:12315")
|
||||||
|
|
|
||||||
|
|
@ -22,11 +22,11 @@
|
||||||
# - If recall returns nothing relevant, proceed without it.
|
# - If recall returns nothing relevant, proceed without it.
|
||||||
# ollie:end
|
# ollie:end
|
||||||
|
|
||||||
source "${OLLIE_TOOLS_PATH:-$(dirname "$0")}/_lib/args.sh"
|
source "$(dirname "$0")/_lib/args.sh"
|
||||||
|
|
||||||
query=$(arg_require "query")
|
query=$(arg_require "query")
|
||||||
|
|
||||||
mem_dir="${OLLIE_MEMORY_PATH:-$HOME/.config/ollie/memory}"
|
mem_dir="${XDG_CONFIG_HOME:-$HOME/.config}/ollie/memory"
|
||||||
|
|
||||||
[ ! -d "$mem_dir" ] && echo "error: memory directory does not exist: $mem_dir" && exit 1
|
[ ! -d "$mem_dir" ] && echo "error: memory directory does not exist: $mem_dir" && exit 1
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -30,7 +30,7 @@ import os
|
||||||
import re
|
import re
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
|
|
||||||
sys.path.insert(0, os.environ.get('OLLIE_TOOLS_PATH', os.path.dirname(os.path.abspath(__file__))))
|
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
|
||||||
from _lib.args import parse_args
|
from _lib.args import parse_args
|
||||||
|
|
||||||
args = parse_args()
|
args = parse_args()
|
||||||
|
|
@ -38,7 +38,7 @@ title = args.require("title")
|
||||||
tags_csv = args.require("tags")
|
tags_csv = args.require("tags")
|
||||||
body = args.require("body")
|
body = args.require("body")
|
||||||
|
|
||||||
mem_dir = os.environ.get("OLLIE_MEMORY_PATH", os.path.expanduser("~/.config/ollie/memory"))
|
mem_dir = os.path.join(os.environ.get("XDG_CONFIG_HOME", os.path.expanduser("~/.config")), "ollie", "memory")
|
||||||
|
|
||||||
if not os.path.isdir(mem_dir):
|
if not os.path.isdir(mem_dir):
|
||||||
os.makedirs(mem_dir, exist_ok=True)
|
os.makedirs(mem_dir, exist_ok=True)
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,7 @@
|
||||||
|
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
|
|
||||||
source "${OLLIE_TOOLS_PATH:-$(dirname "$0")}/_lib/args.sh"
|
source "$(dirname "$0")/_lib/args.sh"
|
||||||
|
|
||||||
pid=$(arg_require "pid")
|
pid=$(arg_require "pid")
|
||||||
session="${OLLIE_SESSION_ID:?OLLIE_SESSION_ID not set}"
|
session="${OLLIE_SESSION_ID:?OLLIE_SESSION_ID not set}"
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,7 @@
|
||||||
|
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
|
|
||||||
source "${OLLIE_TOOLS_PATH:-$(dirname "$0")}/_lib/args.sh"
|
source "$(dirname "$0")/_lib/args.sh"
|
||||||
|
|
||||||
session="${OLLIE_SESSION_ID:?OLLIE_SESSION_ID not set}"
|
session="${OLLIE_SESSION_ID:?OLLIE_SESSION_ID not set}"
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,7 @@
|
||||||
|
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
|
|
||||||
source "${OLLIE_TOOLS_PATH:-$(dirname "$0")}/_lib/args.sh"
|
source "$(dirname "$0")/_lib/args.sh"
|
||||||
|
|
||||||
pid=$(arg_require "pid")
|
pid=$(arg_require "pid")
|
||||||
session="${OLLIE_SESSION_ID:?OLLIE_SESSION_ID not set}"
|
session="${OLLIE_SESSION_ID:?OLLIE_SESSION_ID not set}"
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,7 @@
|
||||||
|
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
|
|
||||||
source "${OLLIE_TOOLS_PATH:-$(dirname "$0")}/_lib/args.sh"
|
source "$(dirname "$0")/_lib/args.sh"
|
||||||
|
|
||||||
pid=$(arg_require "pid")
|
pid=$(arg_require "pid")
|
||||||
signal=$(arg_get "signal" "TERM")
|
signal=$(arg_get "signal" "TERM")
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@
|
||||||
# skill_list — list available skill modules
|
# skill_list — list available skill modules
|
||||||
set -e
|
set -e
|
||||||
|
|
||||||
skills_path="${OLLIE_SKILLS_PATH:-${HOME}/.config/ollie/skills}"
|
skills_path="${XDG_CONFIG_HOME:-$HOME/.config}/ollie/skills"
|
||||||
|
|
||||||
if [ ! -d "$skills_path" ]; then
|
if [ ! -d "$skills_path" ]; then
|
||||||
echo "(no skills directory found)"
|
echo "(no skills directory found)"
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@
|
||||||
# skill_load — load a skill module, outputting its content
|
# skill_load — load a skill module, outputting its content
|
||||||
set -e
|
set -e
|
||||||
|
|
||||||
skills_path="${OLLIE_SKILLS_PATH:-${HOME}/.config/ollie/skills}"
|
skills_path="${XDG_CONFIG_HOME:-$HOME/.config}/ollie/skills"
|
||||||
|
|
||||||
# Read name from JSON stdin
|
# Read name from JSON stdin
|
||||||
name=$(python3 -c "import sys,json; print(json.load(sys.stdin).get('name',''))")
|
name=$(python3 -c "import sys,json; print(json.load(sys.stdin).get('name',''))")
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,7 @@ import os
|
||||||
import secrets
|
import secrets
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
sys.path.insert(0, os.environ.get('OLLIE_TOOLS_PATH', os.path.dirname(os.path.abspath(__file__))))
|
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
|
||||||
from _lib.args import parse_args
|
from _lib.args import parse_args
|
||||||
|
|
||||||
AGENTS_DIR = os.path.expanduser("~/.config/ollie/agents")
|
AGENTS_DIR = os.path.expanduser("~/.config/ollie/agents")
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,7 @@
|
||||||
# ollie:end
|
# ollie:end
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
|
|
||||||
source "${OLLIE_TOOLS_PATH:-$(dirname "$0")}/_lib/args.sh"
|
source "$(dirname "$0")/_lib/args.sh"
|
||||||
|
|
||||||
SERVICE="org.ollie.SessionManager"
|
SERVICE="org.ollie.SessionManager"
|
||||||
OBJ="/org/ollie/SessionManager"
|
OBJ="/org/ollie/SessionManager"
|
||||||
|
|
|
||||||
|
|
@ -13,23 +13,16 @@
|
||||||
# Default agent config (default: default)
|
# Default agent config (default: default)
|
||||||
#OLLIE_DEFAULT_AGENT=default
|
#OLLIE_DEFAULT_AGENT=default
|
||||||
|
|
||||||
# Tool scripts directory, colon-separated (default: ~/.config/ollie/tools)
|
# All paths follow XDG Base Directory conventions:
|
||||||
#OLLIE_TOOLS_PATH=~/.config/ollie/tools
|
# Tools: $XDG_CONFIG_HOME/ollie/tools (default: ~/.config/ollie/tools)
|
||||||
|
# Skills: $XDG_CONFIG_HOME/ollie/skills (default: ~/.config/ollie/skills)
|
||||||
# Skills directory, colon-separated (default: ~/.config/ollie/skills)
|
# Prompts: $XDG_CONFIG_HOME/ollie/prompts (default: ~/.config/ollie/prompts)
|
||||||
#OLLIE_SKILLS_PATH=~/.config/ollie/skills
|
# Memory: $XDG_CONFIG_HOME/ollie/memory (default: ~/.config/ollie/memory)
|
||||||
|
# Data: $XDG_DATA_HOME/ollie (default: ~/.local/share/ollie)
|
||||||
# Prompt templates directory, colon-separated (default: ~/.config/ollie/prompts)
|
# Override XDG_CONFIG_HOME or XDG_DATA_HOME if you want non-default locations.
|
||||||
#OLLIE_PROMPTS_PATH=~/.config/ollie/prompts
|
|
||||||
|
|
||||||
# Memory directory (default: ~/.config/ollie/memory)
|
|
||||||
#OLLIE_MEMORY_PATH=~/.config/ollie/memory
|
|
||||||
|
|
||||||
# Backend-specific settings
|
# Backend-specific settings
|
||||||
|
|
||||||
# ollama
|
|
||||||
#OLLIE_OLLAMA_URL=http://localhost:11434
|
|
||||||
|
|
||||||
# openai / openrouter
|
# openai / openrouter
|
||||||
#OLLIE_OPENAI_URL=
|
#OLLIE_OPENAI_URL=
|
||||||
#OLLIE_OPENAI_KEY=
|
#OLLIE_OPENAI_KEY=
|
||||||
|
|
|
||||||
|
|
@ -25,8 +25,7 @@ Log level is controlled by `OLLIE_LOG` (default: `warn`). Set to `debug` for ver
|
||||||
OLLIE_LOG=debug olliesrv
|
OLLIE_LOG=debug olliesrv
|
||||||
```
|
```
|
||||||
|
|
||||||
Valid levels: `debug`, `info`, `warn`, `error`. Per-subsystem overrides follow the pattern
|
Valid levels: `debug`, `info`, `warn`, `error`.
|
||||||
`OLLIE_<TAG>_LOG=debug` where `<TAG>` matches the logger's tag (e.g. `OLLIE_SESSION_LOG`).
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
// Package env manages the daemon-global environment for ollie.
|
// Package env manages the daemon-global environment for ollie.
|
||||||
// It owns the set of known OLLIE_* variables, provides
|
// It loads $XDG_CONFIG_HOME/ollie/env and provides defaults,
|
||||||
// defaults, and formats them for export to frontends via ollie/env.
|
// formatted for export to frontends via ollie/env.
|
||||||
package env
|
package env
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
|
@ -14,23 +14,12 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
// managed is the ordered list of env vars that ollie exposes to frontends.
|
// managed is the ordered list of env vars that ollie exposes to frontends.
|
||||||
var managed = []string{
|
var managed = []string{}
|
||||||
"OLLIE_CFG_PATH",
|
|
||||||
}
|
|
||||||
|
|
||||||
// EnsureDefaults loads ~/.config/ollie/env and sets default values for any
|
// EnsureDefaults loads $XDG_CONFIG_HOME/ollie/env and sets default values for any
|
||||||
// OLLIE_* vars not already present in the process environment.
|
// OLLIE_* vars not already present in the process environment.
|
||||||
func EnsureDefaults() {
|
func EnsureDefaults() {
|
||||||
home, _ := os.UserHomeDir()
|
LoadFile(filepath.Join(paths.CfgDir(), "env"))
|
||||||
LoadFile(filepath.Join(home, ".config", "ollie", "env"))
|
|
||||||
defaults := map[string]string{
|
|
||||||
"OLLIE_CFG_PATH": filepath.Join(home, ".config", "ollie"),
|
|
||||||
}
|
|
||||||
for k, v := range defaults {
|
|
||||||
if os.Getenv(k) == "" {
|
|
||||||
os.Setenv(k, v) //nolint:errcheck
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set sets a variable in the process environment.
|
// Set sets a variable in the process environment.
|
||||||
|
|
|
||||||
|
|
@ -7,25 +7,8 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestEnsureDefaults(t *testing.T) {
|
func TestEnsureDefaults(t *testing.T) {
|
||||||
// Clear managed vars so defaults apply.
|
// EnsureDefaults now only loads the env file; no OLLIE_* vars are set.
|
||||||
for _, k := range managed {
|
|
||||||
t.Setenv(k, "")
|
|
||||||
os.Unsetenv(k)
|
|
||||||
}
|
|
||||||
EnsureDefaults()
|
EnsureDefaults()
|
||||||
for _, k := range []string{"OLLIE_CFG_PATH"} {
|
|
||||||
if v := os.Getenv(k); v == "" {
|
|
||||||
t.Errorf("%s not set after EnsureDefaults", k)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestEnsureDefaultsNoOverwrite(t *testing.T) {
|
|
||||||
t.Setenv("OLLIE_CFG_PATH", "/custom")
|
|
||||||
EnsureDefaults()
|
|
||||||
if v := os.Getenv("OLLIE_CFG_PATH"); v != "/custom" {
|
|
||||||
t.Errorf("OLLIE_CFG_PATH = %q, want /custom (should not overwrite)", v)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestSetGet(t *testing.T) {
|
func TestSetGet(t *testing.T) {
|
||||||
|
|
@ -45,18 +28,29 @@ func TestAll(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestFormat(t *testing.T) {
|
func TestFormat(t *testing.T) {
|
||||||
t.Setenv("OLLIE_CFG_PATH", "/tmp/test-fmt")
|
// managed is empty; Format() should return empty output.
|
||||||
out := string(Format())
|
out := string(Format())
|
||||||
if !strings.Contains(out, "OLLIE_CFG_PATH=/tmp/test-fmt") {
|
if out != "" {
|
||||||
t.Errorf("Format() missing expected var, got:\n%s", out)
|
// Every line should be NAME=VALUE\n
|
||||||
}
|
for _, line := range strings.Split(strings.TrimRight(out, "\n"), "\n") {
|
||||||
// Every line should be NAME=VALUE\n
|
if line == "" {
|
||||||
for _, line := range strings.Split(strings.TrimRight(out, "\n"), "\n") {
|
continue
|
||||||
if line == "" {
|
}
|
||||||
continue
|
if !strings.Contains(line, "=") {
|
||||||
}
|
t.Errorf("malformed line: %q", line)
|
||||||
if !strings.Contains(line, "=") {
|
}
|
||||||
t.Errorf("malformed line: %q", line)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestLoadFile(t *testing.T) {
|
||||||
|
dir := t.TempDir()
|
||||||
|
f := dir + "/env"
|
||||||
|
os.WriteFile(f, []byte("OLLIE_TEST_LOAD=testval\n"), 0600)
|
||||||
|
t.Setenv("OLLIE_TEST_LOAD", "")
|
||||||
|
os.Unsetenv("OLLIE_TEST_LOAD")
|
||||||
|
LoadFile(f)
|
||||||
|
if got := os.Getenv("OLLIE_TEST_LOAD"); got != "testval" {
|
||||||
|
t.Errorf("LoadFile: OLLIE_TEST_LOAD = %q, want testval", got)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
15
log/log.go
15
log/log.go
|
|
@ -92,11 +92,11 @@ func (s *Sink) Logger(tag string, level Level) *Logger {
|
||||||
return &Logger{tag: tag, level: level, out: s.out, errout: s.errout}
|
return &Logger{tag: tag, level: level, out: s.out, errout: s.errout}
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewLogger creates a Logger from this Sink, reading the level from
|
// NewLogger creates a Logger from this Sink, reading the global level from
|
||||||
// OLLIE_{TAG}_LOG and falling back to the Sink's default level.
|
// OLLIE_LOG and falling back to the Sink's default level.
|
||||||
func (s *Sink) NewLogger(tag string) *Logger {
|
func (s *Sink) NewLogger(tag string) *Logger {
|
||||||
l := s.level
|
l := s.level
|
||||||
if env := os.Getenv("OLLIE_" + strings.ToUpper(tag) + "_LOG"); env != "" {
|
if env := os.Getenv("OLLIE_LOG"); env != "" {
|
||||||
l = ParseLevel(env, l)
|
l = ParseLevel(env, l)
|
||||||
}
|
}
|
||||||
return s.Logger(tag, l)
|
return s.Logger(tag, l)
|
||||||
|
|
@ -133,12 +133,7 @@ func (l *Logger) Warn(format string, args ...any) { l.emit(LevelWarn, format, a
|
||||||
func (l *Logger) Error(format string, args ...any) { l.emit(LevelError, format, args...) }
|
func (l *Logger) Error(format string, args ...any) { l.emit(LevelError, format, args...) }
|
||||||
|
|
||||||
// Sub returns a new Logger with the given tag sharing the same output writers
|
// Sub returns a new Logger with the given tag sharing the same output writers
|
||||||
// and inheriting this logger's level. The level can be overridden independently
|
// and inheriting this logger's level (set globally via OLLIE_LOG).
|
||||||
// via the OLLIE_{TAG}_LOG environment variable.
|
|
||||||
func (l *Logger) Sub(tag string) *Logger {
|
func (l *Logger) Sub(tag string) *Logger {
|
||||||
level := l.level
|
return &Logger{tag: tag, level: l.level, out: l.out, errout: l.errout}
|
||||||
if env := os.Getenv("OLLIE_" + strings.ToUpper(tag) + "_LOG"); env != "" {
|
|
||||||
level = ParseLevel(env, level)
|
|
||||||
}
|
|
||||||
return &Logger{tag: tag, level: level, out: l.out, errout: l.errout}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -114,19 +114,19 @@ func TestSinkFlush(t *testing.T) {
|
||||||
func TestSinkNewLogger(t *testing.T) {
|
func TestSinkNewLogger(t *testing.T) {
|
||||||
var out bytes.Buffer
|
var out bytes.Buffer
|
||||||
s := NewSink(&out, &out, LevelWarn)
|
s := NewSink(&out, &out, LevelWarn)
|
||||||
t.Setenv("OLLIE_MYTEST_LOG", "debug")
|
t.Setenv("OLLIE_LOG", "debug")
|
||||||
l := s.NewLogger("mytest")
|
l := s.NewLogger("mytest")
|
||||||
l.Debug("visible")
|
l.Debug("visible")
|
||||||
s.Flush()
|
s.Flush()
|
||||||
if !strings.Contains(out.String(), "visible") {
|
if !strings.Contains(out.String(), "visible") {
|
||||||
t.Errorf("expected debug output with env override, got: %s", out.String())
|
t.Errorf("expected debug output with OLLIE_LOG=debug, got: %s", out.String())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestSinkNewLoggerDefault(t *testing.T) {
|
func TestSinkNewLoggerDefault(t *testing.T) {
|
||||||
var out bytes.Buffer
|
var out bytes.Buffer
|
||||||
s := NewSink(&out, &out, LevelError)
|
s := NewSink(&out, &out, LevelError)
|
||||||
t.Setenv("OLLIE_NOTSET_LOG", "")
|
t.Setenv("OLLIE_LOG", "")
|
||||||
l := s.NewLogger("notset")
|
l := s.NewLogger("notset")
|
||||||
l.Warn("skip")
|
l.Warn("skip")
|
||||||
s.Flush()
|
s.Flush()
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@ package paths
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"os"
|
"os"
|
||||||
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -15,20 +16,30 @@ func ExpandHome(path string) string {
|
||||||
return path
|
return path
|
||||||
}
|
}
|
||||||
|
|
||||||
// CfgDir returns the ollie config root from OLLIE_CFG_PATH, defaulting to ~/.config/ollie.
|
// xdgConfigHome returns $XDG_CONFIG_HOME or ~/.config.
|
||||||
func CfgDir() string {
|
func xdgConfigHome() string {
|
||||||
if p := os.Getenv("OLLIE_CFG_PATH"); p != "" {
|
if p := os.Getenv("XDG_CONFIG_HOME"); p != "" {
|
||||||
return p
|
return p
|
||||||
}
|
}
|
||||||
home, _ := os.UserHomeDir()
|
home, _ := os.UserHomeDir()
|
||||||
return home + "/.config/ollie"
|
return filepath.Join(home, ".config")
|
||||||
}
|
}
|
||||||
|
|
||||||
// DataDir returns the ollie data root from OLLIE_DATA_PATH, defaulting to ~/.local/share/ollie.
|
// xdgDataHome returns $XDG_DATA_HOME or ~/.local/share.
|
||||||
func DataDir() string {
|
func xdgDataHome() string {
|
||||||
if p := os.Getenv("OLLIE_DATA_PATH"); p != "" {
|
if p := os.Getenv("XDG_DATA_HOME"); p != "" {
|
||||||
return p
|
return p
|
||||||
}
|
}
|
||||||
home, _ := os.UserHomeDir()
|
home, _ := os.UserHomeDir()
|
||||||
return home + "/.local/share/ollie"
|
return filepath.Join(home, ".local", "share")
|
||||||
|
}
|
||||||
|
|
||||||
|
// CfgDir returns the ollie config root: $XDG_CONFIG_HOME/ollie.
|
||||||
|
func CfgDir() string {
|
||||||
|
return filepath.Join(xdgConfigHome(), "ollie")
|
||||||
|
}
|
||||||
|
|
||||||
|
// DataDir returns the ollie data root: $XDG_DATA_HOME/ollie.
|
||||||
|
func DataDir() string {
|
||||||
|
return filepath.Join(xdgDataHome(), "ollie")
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -5,30 +5,30 @@ import (
|
||||||
"testing"
|
"testing"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestCfgDirFromEnv(t *testing.T) {
|
func TestCfgDirFromXDG(t *testing.T) {
|
||||||
t.Setenv("OLLIE_CFG_PATH", "/custom/cfg")
|
t.Setenv("XDG_CONFIG_HOME", "/custom/config")
|
||||||
if got := CfgDir(); got != "/custom/cfg" {
|
if got := CfgDir(); got != "/custom/config/ollie" {
|
||||||
t.Errorf("CfgDir() = %q; want /custom/cfg", got)
|
t.Errorf("CfgDir() = %q; want /custom/config/ollie", got)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestCfgDirDefault(t *testing.T) {
|
func TestCfgDirDefault(t *testing.T) {
|
||||||
t.Setenv("OLLIE_CFG_PATH", "")
|
t.Setenv("XDG_CONFIG_HOME", "")
|
||||||
home, _ := os.UserHomeDir()
|
home, _ := os.UserHomeDir()
|
||||||
if got := CfgDir(); got != home+"/.config/ollie" {
|
if got := CfgDir(); got != home+"/.config/ollie" {
|
||||||
t.Errorf("CfgDir() = %q; want %s/.config/ollie", got, home)
|
t.Errorf("CfgDir() = %q; want %s/.config/ollie", got, home)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestDataDirFromEnv(t *testing.T) {
|
func TestDataDirFromXDG(t *testing.T) {
|
||||||
t.Setenv("OLLIE_DATA_PATH", "/custom/data")
|
t.Setenv("XDG_DATA_HOME", "/custom/data")
|
||||||
if got := DataDir(); got != "/custom/data" {
|
if got := DataDir(); got != "/custom/data/ollie" {
|
||||||
t.Errorf("DataDir() = %q; want /custom/data", got)
|
t.Errorf("DataDir() = %q; want /custom/data/ollie", got)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestDataDirDefault(t *testing.T) {
|
func TestDataDirDefault(t *testing.T) {
|
||||||
t.Setenv("OLLIE_DATA_PATH", "")
|
t.Setenv("XDG_DATA_HOME", "")
|
||||||
home, _ := os.UserHomeDir()
|
home, _ := os.UserHomeDir()
|
||||||
if got := DataDir(); got != home+"/.local/share/ollie" {
|
if got := DataDir(); got != home+"/.local/share/ollie" {
|
||||||
t.Errorf("DataDir() = %q; want %s/.local/share/ollie", got, home)
|
t.Errorf("DataDir() = %q; want %s/.local/share/ollie", got, home)
|
||||||
|
|
|
||||||
|
|
@ -101,10 +101,6 @@ func expandPath(pattern, cwd string, getenv EnvFunc) string {
|
||||||
return xdg
|
return xdg
|
||||||
}
|
}
|
||||||
return fmt.Sprintf("/run/user/%d", os.Getuid())
|
return fmt.Sprintf("/run/user/%d", os.Getuid())
|
||||||
case "OLLIE_CFG_PATH":
|
|
||||||
return paths.CfgDir()
|
|
||||||
case "OLLIE_DATA_PATH":
|
|
||||||
return paths.DataDir()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if val := getenv(varName); val != "" {
|
if val := getenv(varName); val != "" {
|
||||||
|
|
|
||||||
|
|
@ -27,11 +27,8 @@ filesystem:
|
||||||
- "{XDG_DATA_HOME}/gh/extensions"
|
- "{XDG_DATA_HOME}/gh/extensions"
|
||||||
- "/dev"
|
- "/dev"
|
||||||
- "{HOME}/img"
|
- "{HOME}/img"
|
||||||
- "{OLLIE_CFG_PATH}"
|
- "{XDG_CONFIG_HOME}/ollie"
|
||||||
- "{OLLIE_SKILLS_PATH}"
|
|
||||||
rw:
|
rw:
|
||||||
- "{OLLIE_MEMORY_PATH}"
|
|
||||||
- "{OLLIE_PLAN_PATH}"
|
|
||||||
- "{HOME}/.ssh/known_hosts"
|
- "{HOME}/.ssh/known_hosts"
|
||||||
- "{HOME}/pCloudDrive"
|
- "{HOME}/pCloudDrive"
|
||||||
- "{HOME}/.pcloud"
|
- "{HOME}/.pcloud"
|
||||||
|
|
@ -65,8 +62,6 @@ filesystem:
|
||||||
- "{XDG_CONFIG_HOME}/vim"
|
- "{XDG_CONFIG_HOME}/vim"
|
||||||
- "{XDG_DATA_HOME}/nvim"
|
- "{XDG_DATA_HOME}/nvim"
|
||||||
- "{XDG_STATE_HOME}/nvim"
|
- "{XDG_STATE_HOME}/nvim"
|
||||||
- "{OLLIE_MEMORY_PATH}"
|
|
||||||
- "{OLLIE_PLAN_PATH}"
|
|
||||||
- "{HOME}/.vimrc"
|
- "{HOME}/.vimrc"
|
||||||
- "{HOME}/.vim"
|
- "{HOME}/.vim"
|
||||||
- "{HOME}/.emacs.d"
|
- "{HOME}/.emacs.d"
|
||||||
|
|
@ -77,10 +72,8 @@ filesystem:
|
||||||
- "/dev/random"
|
- "/dev/random"
|
||||||
- "/dev/null"
|
- "/dev/null"
|
||||||
- "/dev/fuse"
|
- "/dev/fuse"
|
||||||
- "{OLLIE_DATA_PATH}"
|
- "{XDG_DATA_HOME}/ollie"
|
||||||
- "{HOME}/notes"
|
- "{HOME}/notes"
|
||||||
- "{OLLIE_AGENTS_PATH}"
|
|
||||||
- "{OLLIE_PROMPTS_PATH}"
|
|
||||||
rox:
|
rox:
|
||||||
- "/usr"
|
- "/usr"
|
||||||
- "/lib"
|
- "/lib"
|
||||||
|
|
@ -91,7 +84,7 @@ filesystem:
|
||||||
- "{HOME}/env"
|
- "{HOME}/env"
|
||||||
- "/proc/self/fd"
|
- "/proc/self/fd"
|
||||||
- "/proc/self/cmdline"
|
- "/proc/self/cmdline"
|
||||||
- "{OLLIE_TOOLS_PATH}"
|
- "{XDG_CONFIG_HOME}/ollie/tools"
|
||||||
rwx:
|
rwx:
|
||||||
- "{CWD}"
|
- "{CWD}"
|
||||||
- "{HOME}/Qt"
|
- "{HOME}/Qt"
|
||||||
|
|
@ -104,10 +97,7 @@ filesystem:
|
||||||
- "{HOME}/.cache/uv"
|
- "{HOME}/.cache/uv"
|
||||||
- "{HOME}/bin"
|
- "{HOME}/bin"
|
||||||
- "{OLLIE}"
|
- "{OLLIE}"
|
||||||
- "{OLLIE_TMP_PATH}"
|
- "{TMPDIR}/ollie"
|
||||||
# XDG defaults for OLLIE_* paths (in case env vars are unset)
|
|
||||||
- "{XDG_CONFIG_HOME}/ollie"
|
|
||||||
- "{XDG_DATA_HOME}/ollie"
|
|
||||||
- "{HOME}/.pyenv"
|
- "{HOME}/.pyenv"
|
||||||
- "{HOME}/.local/bin"
|
- "{HOME}/.local/bin"
|
||||||
- "{HOME}/.sdkman"
|
- "{HOME}/.sdkman"
|
||||||
|
|
@ -130,18 +120,8 @@ env:
|
||||||
- NAMESPACE
|
- NAMESPACE
|
||||||
- OLLIE
|
- OLLIE
|
||||||
- OLLIE_DEFAULT_AGENT
|
- OLLIE_DEFAULT_AGENT
|
||||||
- OLLIE_CFG_PATH
|
|
||||||
- OLLIE_DATA_PATH
|
|
||||||
- OLLIE_MEMORY_PATH
|
|
||||||
- OLLIE_PLAN_PATH
|
|
||||||
- OLLIE_SESSION_ID
|
- OLLIE_SESSION_ID
|
||||||
- OLLIE_SKILLS_PATH
|
|
||||||
- OLLIE_TMP_PATH
|
|
||||||
- OLLIE_ELEVATE_SOCKET
|
- OLLIE_ELEVATE_SOCKET
|
||||||
- OLLIE_TOOLS_PATH
|
|
||||||
- OLLIE_TRANSCRIPT_PATH
|
|
||||||
- OLLIE_AGENTS_PATH
|
|
||||||
- OLLIE_PROMPTS_PATH
|
|
||||||
- OLLIE_UNAME
|
- OLLIE_UNAME
|
||||||
- OLLIE_LOGSEQ_TOKEN
|
- OLLIE_LOGSEQ_TOKEN
|
||||||
- HOME
|
- HOME
|
||||||
|
|
@ -163,6 +143,10 @@ env:
|
||||||
- SSH_AUTH_SOCK
|
- SSH_AUTH_SOCK
|
||||||
- XDG_SESSION_TYPE
|
- XDG_SESSION_TYPE
|
||||||
- XDG_RUNTIME_DIR
|
- XDG_RUNTIME_DIR
|
||||||
|
- XDG_CONFIG_HOME
|
||||||
|
- XDG_DATA_HOME
|
||||||
|
- XDG_STATE_HOME
|
||||||
|
- XDG_CACHE_HOME
|
||||||
- WAYLAND_DISPLAY
|
- WAYLAND_DISPLAY
|
||||||
network:
|
network:
|
||||||
unrestricted: true
|
unrestricted: true
|
||||||
|
|
|
||||||
|
|
@ -19,12 +19,12 @@ filesystem:
|
||||||
- "{HOME}/.rbenv"
|
- "{HOME}/.rbenv"
|
||||||
- "{HOME}/.nvm"
|
- "{HOME}/.nvm"
|
||||||
rw:
|
rw:
|
||||||
- "{OLLIE_MEMORY_PATH}"
|
|
||||||
- "{OLLIE_PLAN_PATH}"
|
|
||||||
- "/dev/null"
|
- "/dev/null"
|
||||||
- "/tmp"
|
- "/tmp"
|
||||||
- "{NAMESPACE}"
|
- "{NAMESPACE}"
|
||||||
- "{XDG_RUNTIME_DIR}/ollie"
|
- "{XDG_RUNTIME_DIR}/ollie"
|
||||||
|
- "{XDG_CONFIG_HOME}/ollie/memory"
|
||||||
|
- "{XDG_DATA_HOME}/ollie"
|
||||||
rwx:
|
rwx:
|
||||||
- "{CWD}"
|
- "{CWD}"
|
||||||
- "{OLLIE}"
|
- "{OLLIE}"
|
||||||
|
|
@ -32,16 +32,9 @@ filesystem:
|
||||||
env:
|
env:
|
||||||
- NAMESPACE
|
- NAMESPACE
|
||||||
- OLLIE
|
- OLLIE
|
||||||
- OLLIE_CFG_PATH
|
|
||||||
- OLLIE_DATA_PATH
|
|
||||||
- OLLIE_MEMORY_PATH
|
|
||||||
- OLLIE_PLAN_PATH
|
|
||||||
- OLLIE_SESSION_ID
|
- OLLIE_SESSION_ID
|
||||||
- OLLIE_SKILLS_PATH
|
|
||||||
- OLLIE_TMP_PATH
|
|
||||||
- OLLIE_ELEVATE_SOCKET
|
- OLLIE_ELEVATE_SOCKET
|
||||||
- OLLIE_TOOLS_PATH
|
- OLLIE_UNAME
|
||||||
- OLLIE_TRANSCRIPT_PATH
|
|
||||||
- PATH
|
- PATH
|
||||||
- HOME
|
- HOME
|
||||||
- PLAN9
|
- PLAN9
|
||||||
|
|
@ -51,6 +44,8 @@ env:
|
||||||
- USER
|
- USER
|
||||||
- AGENT_ID
|
- AGENT_ID
|
||||||
- XDG_RUNTIME_DIR
|
- XDG_RUNTIME_DIR
|
||||||
|
- XDG_CONFIG_HOME
|
||||||
|
- XDG_DATA_HOME
|
||||||
- TMPDIR
|
- TMPDIR
|
||||||
network:
|
network:
|
||||||
unrestricted: true
|
unrestricted: true
|
||||||
|
|
|
||||||
|
|
@ -363,7 +363,7 @@ filesystem:
|
||||||
rw:
|
rw:
|
||||||
- "{CWD}"
|
- "{CWD}"
|
||||||
rwx:
|
rwx:
|
||||||
- "{OLLIE_CFG_PATH}"
|
- "{XDG_CONFIG_HOME}/ollie"
|
||||||
network:
|
network:
|
||||||
enabled: true
|
enabled: true
|
||||||
unrestricted: false
|
unrestricted: false
|
||||||
|
|
@ -397,8 +397,8 @@ advanced:
|
||||||
if !containsStr(cfg.Filesystem.RW, "{CWD}") {
|
if !containsStr(cfg.Filesystem.RW, "{CWD}") {
|
||||||
t.Error("RW missing {CWD}")
|
t.Error("RW missing {CWD}")
|
||||||
}
|
}
|
||||||
if !containsStr(cfg.Filesystem.RWX, "{OLLIE_CFG_PATH}") {
|
if !containsStr(cfg.Filesystem.RWX, "{XDG_CONFIG_HOME}/ollie") {
|
||||||
t.Error("RWX missing {OLLIE_CFG_PATH}")
|
t.Error("RWX missing {XDG_CONFIG_HOME}/ollie")
|
||||||
}
|
}
|
||||||
if !cfg.Network.Enabled {
|
if !cfg.Network.Enabled {
|
||||||
t.Error("Network.Enabled should be true")
|
t.Error("Network.Enabled should be true")
|
||||||
|
|
@ -511,14 +511,16 @@ func TestExpandPath_XDG_FromEnv(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestExpandPath_OlliePaths(t *testing.T) {
|
func TestExpandPath_XDGPaths(t *testing.T) {
|
||||||
got1 := expandPath("{OLLIE_CFG_PATH}", "/cwd", os.Getenv)
|
t.Setenv("XDG_CONFIG_HOME", "/custom/config")
|
||||||
if got1 == "{OLLIE_CFG_PATH}" || got1 == "" {
|
got1 := expandPath("{XDG_CONFIG_HOME}/ollie", "/cwd", os.Getenv)
|
||||||
t.Errorf("OLLIE_CFG_PATH not expanded: %q", got1)
|
if got1 != "/custom/config/ollie" {
|
||||||
|
t.Errorf("XDG_CONFIG_HOME/ollie not expanded: %q", got1)
|
||||||
}
|
}
|
||||||
got2 := expandPath("{OLLIE_DATA_PATH}", "/cwd", os.Getenv)
|
t.Setenv("XDG_DATA_HOME", "/custom/data")
|
||||||
if got2 == "{OLLIE_DATA_PATH}" || got2 == "" {
|
got2 := expandPath("{XDG_DATA_HOME}/ollie", "/cwd", os.Getenv)
|
||||||
t.Errorf("OLLIE_DATA_PATH not expanded: %q", got2)
|
if got2 != "/custom/data/ollie" {
|
||||||
|
t.Errorf("XDG_DATA_HOME/ollie not expanded: %q", got2)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -625,69 +627,20 @@ func TestWrapCommand_SortTiebreaker(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestWrapCommand_ColonSeparatedPaths(t *testing.T) {
|
func TestWrapCommand_EnvVarExpansion(t *testing.T) {
|
||||||
tmpDir := t.TempDir()
|
tmpDir := t.TempDir()
|
||||||
dir1 := filepath.Join(tmpDir, "skills1")
|
dir1 := filepath.Join(tmpDir, "skills")
|
||||||
dir2 := filepath.Join(tmpDir, "skills2")
|
|
||||||
os.MkdirAll(dir1, 0755)
|
os.MkdirAll(dir1, 0755)
|
||||||
os.MkdirAll(dir2, 0755)
|
|
||||||
|
|
||||||
// Simulate OLLIE_SKILLS_PATH="/path1:/path2"
|
t.Setenv("XDG_CONFIG_HOME", tmpDir)
|
||||||
t.Setenv("OLLIE_SKILLS_PATH", dir1+":"+dir2)
|
|
||||||
|
|
||||||
cfg := &Config{
|
cfg := &Config{
|
||||||
Filesystem: FilesystemConfig{
|
Filesystem: FilesystemConfig{
|
||||||
RWX: []string{"{OLLIE_SKILLS_PATH}"},
|
RWX: []string{"{XDG_CONFIG_HOME}/skills"},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
got := mustWrapCommand(t, cfg, []string{"sh"}, tmpDir)
|
got := mustWrapCommand(t, cfg, []string{"sh"}, tmpDir)
|
||||||
assertFlagValue(t, got, "--rwx", dir1)
|
assertFlagValue(t, got, "--rwx", dir1)
|
||||||
assertFlagValue(t, got, "--rwx", dir2)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestCheckPath_ColonSeparatedPaths(t *testing.T) {
|
|
||||||
tmpDir := t.TempDir()
|
|
||||||
dir1 := filepath.Join(tmpDir, "skills1")
|
|
||||||
dir2 := filepath.Join(tmpDir, "skills2")
|
|
||||||
os.MkdirAll(dir1, 0755)
|
|
||||||
os.MkdirAll(dir2, 0755)
|
|
||||||
|
|
||||||
file1 := filepath.Join(dir1, "test.md")
|
|
||||||
os.WriteFile(file1, []byte("hi"), 0644)
|
|
||||||
file2 := filepath.Join(dir2, "test.md")
|
|
||||||
os.WriteFile(file2, []byte("hi"), 0644)
|
|
||||||
|
|
||||||
getenv := func(k string) string {
|
|
||||||
if k == "OLLIE_SKILLS_PATH" {
|
|
||||||
return dir1 + ":" + dir2
|
|
||||||
}
|
|
||||||
if k == "HOME" {
|
|
||||||
return tmpDir
|
|
||||||
}
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
|
|
||||||
cfg := &Config{
|
|
||||||
Filesystem: FilesystemConfig{
|
|
||||||
RWX: []string{"{OLLIE_SKILLS_PATH}"},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
// Both paths should be allowed for read
|
|
||||||
if err := checkPath(cfg, file1, false, tmpDir, getenv); err != nil {
|
|
||||||
t.Errorf("file in dir1 should be allowed: %v", err)
|
|
||||||
}
|
|
||||||
if err := checkPath(cfg, file2, false, tmpDir, getenv); err != nil {
|
|
||||||
t.Errorf("file in dir2 should be allowed: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Path outside both should be denied
|
|
||||||
outside := filepath.Join(tmpDir, "outside", "file.txt")
|
|
||||||
os.MkdirAll(filepath.Dir(outside), 0755)
|
|
||||||
os.WriteFile(outside, []byte("x"), 0644)
|
|
||||||
if err := checkPath(cfg, outside, false, tmpDir, getenv); err == nil {
|
|
||||||
t.Error("path outside both dirs should be denied")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---- helpers ----
|
// ---- helpers ----
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,6 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"sort"
|
"sort"
|
||||||
"strings"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// pathEntry holds a path with its permission type
|
// pathEntry holds a path with its permission type
|
||||||
|
|
@ -48,12 +47,9 @@ func WrapCommand(cfg *Config, originalCmd []string, cwd string, getenv EnvFunc)
|
||||||
var entries []pathEntry
|
var entries []pathEntry
|
||||||
addPaths := func(paths []string, flag string) {
|
addPaths := func(paths []string, flag string) {
|
||||||
for _, path := range paths {
|
for _, path := range paths {
|
||||||
expanded := expandPath(path, cwd, getenv)
|
p := expandPath(path, cwd, getenv)
|
||||||
// Handle colon-separated paths (e.g. OLLIE_SKILLS_PATH)
|
if p != "" && pathExists(p) {
|
||||||
for _, p := range strings.Split(expanded, ":") {
|
entries = append(entries, pathEntry{p, flag})
|
||||||
if p != "" && pathExists(p) {
|
|
||||||
entries = append(entries, pathEntry{p, flag})
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -508,7 +508,6 @@ func (e *Server) executeWithStdin(ctx context.Context, code, language string, ti
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
cmd.Env = filtered
|
cmd.Env = filtered
|
||||||
cmd.Env = append(cmd.Env, "OLLIE_TOOLS_PATH="+ToolsPath())
|
|
||||||
for k, v := range e.envExtra {
|
for k, v := range e.envExtra {
|
||||||
cmd.Env = append(cmd.Env, k+"="+v)
|
cmd.Env = append(cmd.Env, k+"="+v)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,8 @@ import (
|
||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
"syscall"
|
"syscall"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"ollie/paths"
|
||||||
)
|
)
|
||||||
|
|
||||||
// socketSeq is a monotonic counter to ensure unique socket paths.
|
// socketSeq is a monotonic counter to ensure unique socket paths.
|
||||||
|
|
@ -64,9 +66,7 @@ func localDial(ctx context.Context, cwd string, opts ...Option) (*transport, err
|
||||||
Pdeathsig: syscall.SIGTERM,
|
Pdeathsig: syscall.SIGTERM,
|
||||||
}
|
}
|
||||||
|
|
||||||
// Inherit environment — OLLIE_TOOLS_PATH flows through naturally.
|
// Inherit environment.
|
||||||
// ollie-remote reads it via ToolsPath() at runtime, picking up new
|
|
||||||
// tools from disk without restart.
|
|
||||||
cmd.Env = os.Environ()
|
cmd.Env = os.Environ()
|
||||||
|
|
||||||
// Watch stderr for readiness signal.
|
// Watch stderr for readiness signal.
|
||||||
|
|
@ -79,12 +79,12 @@ func localDial(ctx context.Context, cwd string, opts ...Option) (*transport, err
|
||||||
return nil, fmt.Errorf("local start: %w", err)
|
return nil, fmt.Errorf("local start: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Wait for OLLIE_LISTEN_READY on stderr.
|
// Wait for ListenReady on stderr.
|
||||||
readyCh := make(chan struct{})
|
readyCh := make(chan struct{})
|
||||||
go func() {
|
go func() {
|
||||||
scanner := bufio.NewScanner(stderrPipe)
|
scanner := bufio.NewScanner(stderrPipe)
|
||||||
for scanner.Scan() {
|
for scanner.Scan() {
|
||||||
if strings.TrimSpace(scanner.Text()) == "OLLIE_LISTEN_READY" {
|
if strings.TrimSpace(scanner.Text()) == "ListenReady" {
|
||||||
close(readyCh)
|
close(readyCh)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
@ -131,7 +131,7 @@ func localDial(ctx context.Context, cwd string, opts ...Option) (*transport, err
|
||||||
// Assumes pubkey auth (BatchMode=yes). No password handling.
|
// Assumes pubkey auth (BatchMode=yes). No password handling.
|
||||||
func sshDial(ctx context.Context, cfg RemoteConfig) (*transport, error) {
|
func sshDial(ctx context.Context, cfg RemoteConfig) (*transport, error) {
|
||||||
// Bootstrap: verify local environment has everything needed.
|
// Bootstrap: verify local environment has everything needed.
|
||||||
cacheDir := filepath.Join(os.Getenv("HOME"), ".config", "ollie")
|
cacheDir := paths.CfgDir()
|
||||||
if _, err := os.Stat(cacheDir); err != nil {
|
if _, err := os.Stat(cacheDir); err != nil {
|
||||||
return nil, fmt.Errorf("bootstrap: local environment not found at %s", cacheDir)
|
return nil, fmt.Errorf("bootstrap: local environment not found at %s", cacheDir)
|
||||||
}
|
}
|
||||||
|
|
@ -148,7 +148,9 @@ set -e
|
||||||
CACHE_DIR="${XDG_CACHE_HOME:-$HOME/.cache}/ollie"
|
CACHE_DIR="${XDG_CACHE_HOME:-$HOME/.cache}/ollie"
|
||||||
mkdir -p "$CACHE_DIR"
|
mkdir -p "$CACHE_DIR"
|
||||||
tar xzf - -C "$CACHE_DIR"
|
tar xzf - -C "$CACHE_DIR"
|
||||||
export OLLIE_TOOLS_PATH="$CACHE_DIR/tools"
|
# CACHE_DIR is the extracted ollie config dir; set XDG_CONFIG_HOME so
|
||||||
|
# $XDG_CONFIG_HOME/ollie resolves to it.
|
||||||
|
export XDG_CONFIG_HOME="$(dirname "$CACHE_DIR")"
|
||||||
export PATH="$CACHE_DIR/bin:$PATH"
|
export PATH="$CACHE_DIR/bin:$PATH"
|
||||||
exec "$CACHE_DIR/bin/ollie-remote" serve --cwd %s --listen %s
|
exec "$CACHE_DIR/bin/ollie-remote" serve --cwd %s --listen %s
|
||||||
`, shellEscape(cfg.CWD), shellEscape(remoteSock))
|
`, shellEscape(cfg.CWD), shellEscape(remoteSock))
|
||||||
|
|
@ -195,12 +197,12 @@ exec "$CACHE_DIR/bin/ollie-remote" serve --cwd %s --listen %s
|
||||||
return nil, fmt.Errorf("send bootstrap: %w", err)
|
return nil, fmt.Errorf("send bootstrap: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Monitor stderr for OLLIE_LISTEN_READY.
|
// Monitor stderr for ListenReady.
|
||||||
readyCh := make(chan struct{}, 1)
|
readyCh := make(chan struct{}, 1)
|
||||||
go func() {
|
go func() {
|
||||||
scanner := bufio.NewScanner(stderrPipe)
|
scanner := bufio.NewScanner(stderrPipe)
|
||||||
for scanner.Scan() {
|
for scanner.Scan() {
|
||||||
if strings.TrimSpace(scanner.Text()) == "OLLIE_LISTEN_READY" {
|
if strings.TrimSpace(scanner.Text()) == "ListenReady" {
|
||||||
close(readyCh)
|
close(readyCh)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue